基于Go语言实现的简易api网关的示例代码

浏览器的请求去请求目标地址,然后获得结果它再发送给浏览器。对于Go语言来说,实现转发只需要简单的一行代码即可实现,如下所示:

httputil.NewSingleHostReverseProxy(address)

基于此功能,进行简单包装,实现从远端admin管理中心获取需要转发的路由信息或者可以从本地配置文件中获取,实现动态转发。后续可以根据业务情况,可以实现如下功能:
开发接口,实现动态添加代理规则,进行转发

  • 过滤不合法的接口
  • 接口限流
  • 统一日志记录

代码如下:

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
)

type Respond struct {
	Success bool
	Status  string
	Data    []Proxy
}

type Proxy struct {
	Remark        string //描述
	Prefix        string //转发的前缀判断
	Upstream      string //后端 nginx 地址或者ip地址
	RewritePrefix string //重写
}

var (
	InfoLog  *log.Logger
	ErrorLog *log.Logger
	proxyMap = make(map[string]Proxy)
)

var adminUrl = flag.String("adminUrl", "", "admin的地址")
var profile = flag.String("profile", "", "环境")
var proxyFile = flag.String("proxyFile", "", "测试环境的数据")

//日志初始化
func initLog() {
	errFile, err := os.OpenFile("errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	infoFile, err := os.OpenFile("info.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalln("打开日志文件失败:", err)
	}
	InfoLog = log.New(io.MultiWriter(os.Stderr, infoFile), "Info:", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
	ErrorLog = log.New(io.MultiWriter(os.Stderr, errFile), "Error:", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
}

func main() {
	router := gin.Default() //创建一个router
	flag.Parse()
	initLog()
	if *profile != "" {
		InfoLog.Printf("加载远端数据: %s ", *adminUrl)
		initProxyList()
	} else {
		InfoLog.Printf("加载本地配置数据: %s", *proxyFile)
		loadProxyListFromFile()
	}
	router.Any("/*action", Forward) //所有请求都会经过Forward函数转发

	router.Run(":8000")
}

func initProxyList() {
	resp, _ := http.Get(*adminUrl)
	if resp != nil && resp.StatusCode == 200 {
		bytes, err := ioutil.ReadAll(resp.Body)
		defer resp.Body.Close()
		if err != nil {
			fmt.Println("ioutil.ReadAll err=", err)
			return
		}
		var respond Respond
		err = json.Unmarshal(bytes, &respond)
		if err != nil {
			fmt.Println("json.Unmarshal err=", err)
			return
		}
		proxyList := respond.Data
		for _, proxy := range proxyList {
			//追加 反斜杠,为了动态匹配的时候 防止 /proxy/test  /proxy/test1 无法正确转发
			proxyMap[proxy.Prefix+"/"] = proxy
		}
	}
}

func Forward(c *gin.Context) {
	HostReverseProxy(c.Writer, c.Request)
}

func HostReverseProxy(w http.ResponseWriter, r *http.Request) {
	if r.RequestURI == "/favicon.ico" {
		io.WriteString(w, "Request path Error")
		return
	}
	//从内存里面获取转发的url
	var upstream = ""
	if value, ok := proxyMap[r.RequestURI]; ok {
		//如果转发的地址是 / 开头的,需要去掉
		if strings.HasSuffix(value.Upstream, "/") {
			upstream += strings.TrimRight(value.Upstream, "/")
		} else {
			upstream += value.Upstream
		}
		//如果首位不是/开头,则需要追加
		if !strings.HasPrefix(value.RewritePrefix, "/") {
			upstream += "/" + value.RewritePrefix
		} else {
			upstream += value.RewritePrefix
		}
		//去掉开头
		r.URL.Path = strings.ReplaceAll(r.URL.Path, r.RequestURI, "")
	}

	// parse the url
	remote, err := url.Parse(upstream)
	InfoLog.Printf("RequestURI %s upstream %s remote %s", r.RequestURI, upstream, remote)
	if err != nil {
		panic(err)
	}

	r.URL.Host = remote.Host
	r.URL.Scheme = remote.Scheme
	r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
	r.Host = remote.Host

	httputil.NewSingleHostReverseProxy(remote).ServeHTTP(w, r)
}

func loadProxyListFromFile() {
	file, err := os.Open(*proxyFile)
	if err != nil {
		ErrorLog.Println("err:", err)
	}
	var respond Respond
	// 创建json解码器
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&respond)
	if err != nil {
		fmt.Println("LoadProxyListFromFile failed", err.Error())
	}
	proxyList := respond.Data
	for _, proxy := range proxyList {
		proxyMap[proxy.Prefix+"/"] = proxy
	}
}

proxy_data.json 格式如下:

{
  "success":true,
  "status": "ok",
  "data": [
    {
      "remark": "测试环境",
      "prefix": "/division",
      "upstream": "http://test.xxxxx.cn/",
      "rewritePrefix": "/api/division"
    },
    {
      "remark": "测试环境1",
      "prefix": "/division1",
      "upstream": "http://test.xxxx.cn/",
      "rewritePrefix": ""
    },
    {
      "remark": "测试环境2",
      "prefix": "/division3",
      "upstream": "http://test.xxxxxx.cn/",
      "rewritePrefix": "/api/division"
    }
  ]
}

启动脚本

## 加载本地配置文件数据
go run proxy_agent.go -proxyFile ./proxy_data.json
## 启动从配置中心获取数据
go run proxy_agent.go -profile prod -adminUrl http://localhost:3000/proxy/findAll

到此这篇关于基于Go语言实现的简易api网关的示例代码的文章就介绍到这了,更多相关Go api网关 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于Go语言构建RESTful API服务

    目录 什么是 RESTful API 一个简单的 RESTful API RESTful JSON API Gin 框架 引入 Gin 框架 使用 Gin 框架 新增一个用户 获取特定的用户 总结 在实际开发项目中,你编写的服务可以被其他服务使用,这样就组成了微服务的架构:也可以被前端调用,这样就可以前后端分离.那么,本文主要介绍什么是 RESTful API,以及 Go 语言是如何玩转 RESTful API 的. 什么是 RESTful API RESTful API 是一套规范,它可以规范

  • 使用Golang玩转Docker API的实践

    Docker 提供了一个与 Docker 守护进程交互的 API (称为Docker Engine API),我们可以使用官方提供的 Go 语言的 SDK 进行构建和扩展 Docker 应用程序和解决方案. 安装 SDK 通过下面的命令就可以安装 SDK 了: go get github.com/docker/docker/client 管理本地的 Docker 该部分会介绍如何使用 Golang + Docker API 进行管理本地的 Docker. 运行容器 第一个例子将展示如何运行容器,

  • 基于Go语言实现的简易api网关的示例代码

    浏览器的请求去请求目标地址,然后获得结果它再发送给浏览器.对于Go语言来说,实现转发只需要简单的一行代码即可实现,如下所示: httputil.NewSingleHostReverseProxy(address) 基于此功能,进行简单包装,实现从远端admin管理中心获取需要转发的路由信息或者可以从本地配置文件中获取,实现动态转发.后续可以根据业务情况,可以实现如下功能: 开发接口,实现动态添加代理规则,进行转发 过滤不合法的接口 接口限流 统一日志记录 - 代码如下: package main

  • 利用Java手写一个简易的lombok的示例代码

    目录 1.概述 2.lombok使用方法 3.lombok原理解析 4.手写简易lombok 1.概述 在面向对象编程中,必不可少的需要在代码中定义对象模型,而在基于Java的业务平台开发实践中尤其如此.相信大家在平时开发中也深有感触,本来是没有多少代码开发量的,但是因为定义的业务模型对象比较多,而需要重复写Getter/Setter.构造器方法.字符串输出的ToString方法.Equals/HashCode方法等.我们都知道Lombok能够替大家完成这些繁琐的操作,但是其背后的原理很少有人会

  • 基于原生JS封装的Modal对话框插件的示例代码

    基于原生JS封装Modal对话框插件,具体内容如下所示: 原生JS封装Modal对话框插件,个人用来学习原理与思想,只有简单的基本框架的实现,可在此基础上添加更多配置项 API配置 //基本语法 let modal = ModalPlugin({ //提示的标题信息 title:'系统提示', //内容模板 字符串 /模板字符串/DOM元素对象 template:null, //自定义按钮信息 buttons:[{ //按钮文字 text:'确定', click(){ //this:当前实例 }

  • vue 基于abstract 路由模式 实现页面内嵌的示例代码

    abstract 路由模式 abstract 是vue路由中的第三种模式,本身是用来在不支持浏览器API的环境中,充当fallback,而不论是hash还是history模式都会对浏览器上的url产生作用,本文要实现的功能就是在已存在的路由页面中内嵌其他的路由页面,而保持在浏览器当中依旧显示当前页面的路由path,这就利用到了abstract这种与浏览器分离的路由模式. 路由示例 export const routes = [ { path: "/", redirect: "

  • 基于ElementUI中Table嵌套实现多选的示例代码

    前言: 写这个是因为帮朋友修改项目中的bug 我也是第一次写这个功能,有不对的希望大家指正,如果看完有帮助点个赞! 代码中关键是js中Tree的路径查找这个核心,有不懂的自行百度 多了不说了,有需要的可以私信找我要代码,来看下我怎么实现的 思路: 从头开始看这个需求,我们需要知道用到哪写东西 1.表格Table 2.多选&全选 3.嵌套数据(下拉操作) 正好我们可以找下ElementUI官方文档 找到了我们需要用到的API 在嵌套数据的时候需要使用tree-props 选中数据的时候使用togg

  • Mysql实现简易版搜索引擎的示例代码

    目录 前言 简介 ngram 全文解析器 创建全文索引 检索方式 1.自然语言检索(NATURAL LANGUAGE MODE) 2.布尔检索(BOOLEAN MODE) 与 Like 对比 总结 前言 前段时间,因为项目需求,需要根据关键词搜索聊天记录,这不就是一个搜索引擎的功能吗? 于是我第一时间想到的就是 ElasticSearch 分布式搜索引擎,但是由于一些原因,公司的服务器资源比较紧张,没有额外的机器去部署一套 ElasticSearch 服务,而且上线时间也比较紧张,数据量也不大,

  • Go语言实现切片增删改查的示例代码

    目录 引言 一.切片的基础语法 1. 语法 2. 示例 3. 切片的长度和容量 二.切片的初始化 1. 直接初始化 2. 使用数组初始化 3. 使用数组的部分元素初始化(切片表达式) 4. 空(nil)切片 三.切片的遍历 1. for 循环遍历 2. for range遍历 四.切片元素的添加和删除copy 1. 添加元素 2. 删除元素 3. 修改切片元素 4. 查找切片元素 5. 拷贝切片 引言 Golang 的数组是固定长度,可以容纳相同数据类型的元素的集合. 但是当长度固定了,在使用的

  • 基于JS编写开心消消乐游戏的示例代码

    目录 展示 游戏背景 游戏规则 源码部分 对关卡的地图设置 介绍一下游戏中的一些功能 展示 游戏背景 一天晚上,天空中掉下一颗神奇的豌豆种子,正好落在了梦之森林的村长屋附近,种子落地后吸收了池塘的水分,迅速成长,一夜之间变成参天大藤蔓…… 第二天早上,村民们醒来后看到巨大的藤蔓都惊呆了,聚在一起议论纷纷.有人说他似乎看到村长的房子在高耸入云的藤蔓上,房子似乎还在上升,有人号召说应该爬上去救村长,玩家需要爬到藤曼顶部救出村长 游戏规则 把三个颜色相同的小动物连成一条直线,即可消除.达到指定的目标通

  • C语言实现猜数字小游戏的示例代码

    目录 一.猜数字小游戏的要求 二.猜数字小游戏实现的过程 2.1项目创建 2.2头文件内容 2.3源文件内容 三.猜数字小游戏调试结果如下 四.基于猜数字小游戏的总结 五.完整代码 一.猜数字小游戏的要求 猜数字小游戏是我们小时候喜欢我们一个经典小游戏,在本文中,猜数字小游戏主要的功能如下所示 1.登入猜数字小游戏系统,显示小时欢迎界面. 2.用户猜的数字有系统随机在1-20之间生成. 3.用户可以有5次机会猜这个随机生成的数字. 4.若用户猜大了,则系统会显示猜大了,并提示还有多少猜数字的机会

  • C语言实现ATM自动取款机系统的示例代码

    目录 基于C语言的ATM自动取款机系统项目设计与开发 一.ATM自动取款机系统功能分析与介绍 二.开发ATM自动取款机系统的工具以及创建项目的过程 ATM自动取款机系统的设计与开发的步骤 一.设计登入页面的显示功能 二.设计登入页面退出功能 三.设计登入页面登入和系统主页面显示的功能 四.设计主页面修改用户密码的功能 五.设计主页面查询用户余额的功能 六.设计主页面用户取款的功能 七.设计主页面用户存款的功能 八.返回登入页面的功能 总结 基于C语言的ATM自动取款机系统项目设计与开发 一.AT

随机推荐