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

本文主要介绍了基于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