经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
Go | Go 结合 Consul 实现动态反向代理
来源:cnblogs  作者:双鬼带单  时间:2021/3/1 9:16:37  对本文有异议

Go 结合 Consul 实现动态反向代理

代理的核心功能可以用一句话概括:接受客户端的请求,转发到后端服务器,获得应答之后返回给客户端。


Table of Contents


反向代理

反向代理(Reverse Proxy)实际运行方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

实现逻辑

根据代理的描述一共分成几个步骤:

  1. 代理接收到客户端的请求,复制了原来的请求对象
  2. 根据一些规则,修改新请求的请求指向
  3. 把新请求发送到根据服务器端,并接收到服务器端返回的响应
  4. 将上一步的响应根据需求处理一下,然后返回给客户端

Go 语言实现

原生代码

因为要接收并转发 http 请求,所以要实现 http.Handler

  1. type OriginReverseProxy struct {
  2. servers []*url.URL
  3. }
  4. func NewOriginReverseProxy(targets []*url.URL) *OriginReverseProxy {
  5. return &OriginReverseProxy{
  6. servers: targets,
  7. }
  8. }
  9. // 实现 http.Handler, 用于接收所有的请求
  10. func (proxy *OriginReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
  11. // 1. 复制了原来的请求对象
  12. r2 := clone(req)
  13. // 2. 修改请求的地址,换为对应的服务器地址
  14. target := proxy.servers[rand.Int()%len(proxy.servers)]
  15. r2.URL.Scheme = target.Scheme
  16. r2.URL.Host = target.Host
  17. // 3. 发送复制的新请求
  18. transport := http.DefaultTransport
  19. res, err := transport.RoundTrip(r2)
  20. // 4。处理响应
  21. if err != nil {
  22. rw.WriteHeader(http.StatusBadGateway)
  23. return
  24. }
  25. for key, value := range res.Header {
  26. for _, v := range value {
  27. rw.Header().Add(key, v)
  28. }
  29. }
  30. rw.WriteHeader(res.StatusCode)
  31. io.Copy(rw, res.Body)
  32. res.Body.Close()
  33. }
  34. // 复制原请求,生成新的请求
  35. func clone(req *http.Request) *http.Request {
  36. r2 := new(http.Request)
  37. *r2 = *req
  38. r2.URL = cloneURL(req.URL)
  39. if req.Header != nil {
  40. r2.Header = req.Header.Clone()
  41. }
  42. if req.Trailer != nil {
  43. r2.Trailer = req.Trailer.Clone()
  44. }
  45. if s := req.TransferEncoding; s != nil {
  46. s2 := make([]string, len(s))
  47. copy(s2, s)
  48. r2.TransferEncoding = s2
  49. }
  50. r2.Form = cloneURLValues(req.Form)
  51. r2.PostForm = cloneURLValues(req.PostForm)
  52. return r2
  53. }
  54. func cloneURLValues(v url.Values) url.Values {
  55. if v == nil {
  56. return nil
  57. }
  58. return url.Values(http.Header(v).Clone())
  59. }
  60. func cloneURL(u *url.URL) *url.URL {
  61. if u == nil {
  62. return nil
  63. }
  64. u2 := new(url.URL)
  65. *u2 = *u
  66. if u.User != nil {
  67. u2.User = new(url.Userinfo)
  68. *u2.User = *u.User
  69. }
  70. return u2
  71. }

测试

  1. // 先用 gin 起一个 web 项目,方便转发
  2. func TestGin(t *testing.T) {
  3. r := gin.Default()
  4. r.GET("/ping", func(c *gin.Context) {
  5. c.JSON(200, gin.H{
  6. "message": "pong",
  7. })
  8. })
  9. r.Run(":9091") // listen and serve on 0.0.0.0:9091
  10. }
  11. func main() {
  12. proxy := proxy.NewOriginReverseProxy([]*url.URL{
  13. {
  14. Scheme: "http",
  15. Host: "localhost:9091",
  16. },
  17. })
  18. http.ListenAndServe(":19090", proxy)
  19. }

请求 http://127.0.0.1:19090/ping 返回 {"message":"pong"}

httputil.ReverseProxy 工具实现

在上面的例子中,我们自己实现了请求的接收、复制、转发和处理。自己写的代码还算凑合吧。

从上面的示例中,其实我们主要关心的是第二步: 修改请求的地址,换为对应的服务器地址, 其余的逻辑都是通用的,还好官方已经帮我们处理了重复逻辑,那我们看看官方是怎么实现的

httputil.ReverseProxy 源码中,可以看出,通过自定义 Director 方法就可以在原请求复制后,新请求转发出之前对复制出的新请求进行修改,这里就是我们真正改动的地方,当然如果有其他定制需求,可以通过自定义 ModifyResponse 实现对响应的修改,自定义 ErrorHandler 来处理异常

  1. type ReverseProxy struct {
  2. // Director must be a function which modifies
  3. // the request into a new request to be sent
  4. // using Transport. Its response is then copied
  5. // back to the original client unmodified.
  6. // Director must not access the provided Request
  7. // after returning.
  8. Director func(*http.Request)
  9. Transport http.RoundTripper
  10. FlushInterval time.Duration
  11. ErrorLog *log.Logger
  12. BufferPool BufferPool
  13. ModifyResponse func(*http.Response) error
  14. ErrorHandler func(http.ResponseWriter, *http.Request, error)
  15. }

这里我们通过自定义 Director 来修改请求地址

  1. func NewMultipleHostsReverseProxy(targets []*url.URL) *httputil.ReverseProxy {
  2. director := func(req *http.Request) {
  3. target := targets[rand.Int()%len(targets)]
  4. req.URL.Scheme = target.Scheme
  5. req.URL.Host = target.Host
  6. req.URL.Path = target.Path
  7. }
  8. return &httputil.ReverseProxy{Director: director}
  9. }

测试

gin 的项目还是要启动的,这里不在赘叙

  1. func TestMultipleHostsReverseProxy(t *testing.T) {
  2. proxy := proxy.NewMultipleHostsReverseProxy([]*url.URL{
  3. {
  4. Scheme: "http",
  5. Host: "localhost:9091",
  6. },
  7. })
  8. http.ListenAndServe(":9090", proxy)
  9. }

接入 consul 实现动态代理

在前面的一篇文章中讲了如何用 Go 实现 Consul 的服务发现, 如果要结合 consul 实现动态代理,需要考虑如何将请求的地址和对应的服务对应上。这里需要在原理的基础上加上一下功能:

  1. 根据请求地址找到对应的服务
  2. 根据服务找到对应的示例

针对第一步先实现最简单的,就以 请求地址开头 为规则

  1. type LoadBalanceRoute interface {
  2. ObtainInstance(path string) *url.URL
  3. }
  4. type Route struct {
  5. Path string
  6. ServiceName string
  7. }
  8. type DiscoveryLoadBalanceRoute struct {
  9. DiscoveryClient DiscoveryClient
  10. Routes []Route
  11. }
  12. func (d DiscoveryLoadBalanceRoute) ObtainInstance(path string) *url.URL {
  13. for _, route := range d.Routes {
  14. if strings.Index(path, route.Path) == 0 {
  15. instances, _ := d.DiscoveryClient.GetInstances(route.ServiceName)
  16. instance := instances[rand.Int()%len(instances)]
  17. scheme := "http"
  18. return &url.URL{
  19. Scheme: scheme,
  20. Host: instance.GetHost(),
  21. }
  22. }
  23. }
  24. return nil
  25. }
  26. func NewLoadBalanceReverseProxy(lb LoadBalanceRoute) *httputil.ReverseProxy {
  27. director := func(req *http.Request) {
  28. target := lb.ObtainInstance(req.URL.Path)
  29. req.URL.Scheme = target.Scheme
  30. req.URL.Host = target.Host
  31. }
  32. return &httputil.ReverseProxy{Director: director}
  33. }

测试

  1. func main() {
  2. registry, _ := proxy.NewConsulServiceRegistry("127.0.0.1", 8500, "")
  3. reverseProxy := proxy.NewLoadBalanceReverseProxy(&proxy.DiscoveryLoadBalanceRoute{
  4. DiscoveryClient: registry,
  5. Routes: []proxy.Route{
  6. {
  7. Path: "abc",
  8. ServiceName: "abc",
  9. },
  10. },
  11. })
  12. http.ListenAndServe(":19090", reverseProxy)
  13. }

参考

白色兔子公众号图片

原文链接:http://www.cnblogs.com/zyndev/p/14454891.html

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

本站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号