经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
基于Go语言实现的简易api网关的示例代码
来源:jb51  时间:2021/12/8 10:43:03  对本文有异议

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

  1. httputil.NewSingleHostReverseProxy(address)

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

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

代码如下:

  1. package main
  2.  
  3. import (
  4. "encoding/json"
  5. "flag"
  6. "fmt"
  7. "github.com/gin-gonic/gin"
  8. "io"
  9. "io/ioutil"
  10. "log"
  11. "net/http"
  12. "net/http/httputil"
  13. "net/url"
  14. "os"
  15. "strings"
  16. )
  17.  
  18. type Respond struct {
  19. Success bool
  20. Status string
  21. Data []Proxy
  22. }
  23.  
  24. type Proxy struct {
  25. Remark string //描述
  26. Prefix string //转发的前缀判断
  27. Upstream string //后端 nginx 地址或者ip地址
  28. RewritePrefix string //重写
  29. }
  30.  
  31. var (
  32. InfoLog *log.Logger
  33. ErrorLog *log.Logger
  34. proxyMap = make(map[string]Proxy)
  35. )
  36.  
  37. var adminUrl = flag.String("adminUrl", "", "admin的地址")
  38. var profile = flag.String("profile", "", "环境")
  39. var proxyFile = flag.String("proxyFile", "", "测试环境的数据")
  40.  
  41. //日志初始化
  42. func initLog() {
  43. errFile, err := os.OpenFile("errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
  44. infoFile, err := os.OpenFile("info.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
  45. if err != nil {
  46. log.Fatalln("打开日志文件失败:", err)
  47. }
  48. InfoLog = log.New(io.MultiWriter(os.Stderr, infoFile), "Info:", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
  49. ErrorLog = log.New(io.MultiWriter(os.Stderr, errFile), "Error:", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
  50. }
  51.  
  52. func main() {
  53. router := gin.Default() //创建一个router
  54. flag.Parse()
  55. initLog()
  56. if *profile != "" {
  57. InfoLog.Printf("加载远端数据: %s ", *adminUrl)
  58. initProxyList()
  59. } else {
  60. InfoLog.Printf("加载本地配置数据: %s", *proxyFile)
  61. loadProxyListFromFile()
  62. }
  63. router.Any("/*action", Forward) //所有请求都会经过Forward函数转发
  64.  
  65. router.Run(":8000")
  66. }
  67.  
  68. func initProxyList() {
  69. resp, _ := http.Get(*adminUrl)
  70. if resp != nil && resp.StatusCode == 200 {
  71. bytes, err := ioutil.ReadAll(resp.Body)
  72. defer resp.Body.Close()
  73. if err != nil {
  74. fmt.Println("ioutil.ReadAll err=", err)
  75. return
  76. }
  77. var respond Respond
  78. err = json.Unmarshal(bytes, &respond)
  79. if err != nil {
  80. fmt.Println("json.Unmarshal err=", err)
  81. return
  82. }
  83. proxyList := respond.Data
  84. for _, proxy := range proxyList {
  85. //追加 反斜杠,为了动态匹配的时候 防止 /proxy/test /proxy/test1 无法正确转发
  86. proxyMap[proxy.Prefix+"/"] = proxy
  87. }
  88. }
  89. }
  90.  
  91. func Forward(c *gin.Context) {
  92. HostReverseProxy(c.Writer, c.Request)
  93. }
  94.  
  95. func HostReverseProxy(w http.ResponseWriter, r *http.Request) {
  96. if r.RequestURI == "/favicon.ico" {
  97. io.WriteString(w, "Request path Error")
  98. return
  99. }
  100. //从内存里面获取转发的url
  101. var upstream = ""
  102. if value, ok := proxyMap[r.RequestURI]; ok {
  103. //如果转发的地址是 / 开头的,需要去掉
  104. if strings.HasSuffix(value.Upstream, "/") {
  105. upstream += strings.TrimRight(value.Upstream, "/")
  106. } else {
  107. upstream += value.Upstream
  108. }
  109. //如果首位不是/开头,则需要追加
  110. if !strings.HasPrefix(value.RewritePrefix, "/") {
  111. upstream += "/" + value.RewritePrefix
  112. } else {
  113. upstream += value.RewritePrefix
  114. }
  115. //去掉开头
  116. r.URL.Path = strings.ReplaceAll(r.URL.Path, r.RequestURI, "")
  117. }
  118.  
  119. // parse the url
  120. remote, err := url.Parse(upstream)
  121. InfoLog.Printf("RequestURI %s upstream %s remote %s", r.RequestURI, upstream, remote)
  122. if err != nil {
  123. panic(err)
  124. }
  125.  
  126. r.URL.Host = remote.Host
  127. r.URL.Scheme = remote.Scheme
  128. r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
  129. r.Host = remote.Host
  130.  
  131. httputil.NewSingleHostReverseProxy(remote).ServeHTTP(w, r)
  132. }
  133.  
  134. func loadProxyListFromFile() {
  135. file, err := os.Open(*proxyFile)
  136. if err != nil {
  137. ErrorLog.Println("err:", err)
  138. }
  139. var respond Respond
  140. // 创建json解码器
  141. decoder := json.NewDecoder(file)
  142. err = decoder.Decode(&respond)
  143. if err != nil {
  144. fmt.Println("LoadProxyListFromFile failed", err.Error())
  145. }
  146. proxyList := respond.Data
  147. for _, proxy := range proxyList {
  148. proxyMap[proxy.Prefix+"/"] = proxy
  149. }
  150. }

proxy_data.json 格式如下:

  1. {
  2. "success":true,
  3. "status": "ok",
  4. "data": [
  5. {
  6. "remark": "测试环境",
  7. "prefix": "/division",
  8. "upstream": "http://test.xxxxx.cn/",
  9. "rewritePrefix": "/api/division"
  10. },
  11. {
  12. "remark": "测试环境1",
  13. "prefix": "/division1",
  14. "upstream": "http://test.xxxx.cn/",
  15. "rewritePrefix": ""
  16. },
  17. {
  18. "remark": "测试环境2",
  19. "prefix": "/division3",
  20. "upstream": "http://test.xxxxxx.cn/",
  21. "rewritePrefix": "/api/division"
  22. }
  23. ]
  24. }
  25.  

启动脚本

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号