小编给大家分享一下如何使用Go语言实现的api网关,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!
浏览器的请求去请求目标地址,然后获得结果它再发送给浏览器。对于Go语言来说,实现转发只需要简单的一行代码即可实现,如下所示:
httputil.NewSingleHostReverseProxy(address)
基于此功能,进行简单包装,实现从远端admin管理中心获取需要转发的路由信息或者可以从本地配置文件中获取,实现动态转发。后续可以根据业务情况,可以实现如下功能:
开发接口,实现动态添加代理规则,进行转发
过滤不合法的接口
接口限流
统一日志记录
…
代码如下:
package mainimport ("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 boolStatus stringData []Proxy}type Proxy struct {Remark string //描述Prefix string //转发的前缀判断Upstream string //后端 nginx 地址或者ip地址RewritePrefix string //重写}var (InfoLog *log.LoggerErrorLog *log.LoggerproxyMap = 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() //创建一个routerflag.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 Responderr = json.Unmarshal(bytes, &respond)if err != nil {fmt.Println("json.Unmarshal err=", err)return}proxyList := respond.Datafor _, 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}//从内存里面获取转发的urlvar 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 urlremote, 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.Hostr.URL.Scheme = remote.Schemer.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))r.Host = remote.Hosthttputil.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.Datafor _, 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网关”有了一定的了解,如果想了解更多相关知识,欢迎关注编程网行业资讯频道,感谢各位的阅读!