半年前,为了解决测试环境多套应用灵活切换来接受外部系统回调问题,利用 nginx 的 dynamic_upstream 插件 + 封装 httpAPI 来随时更新 upstream 的方式解决(https://testerhome.com/topics/32676

但几月前,多套测试环境切换已经改成使用公共代理(nohosts)+ 特殊请求头(标记去请求哪套环境或开发的后端服务)的方式了,原方式已经失效。

为了不修改原回调转发器的功能,给每个测试环境增加了 1 个 nginx,将每个 nginx 的 ip 提供给回调转发器,并且每个 niginx 再二次转发时设置上对应的特殊请求头。
一开始的临时方案如下:

但是这个临时方案增加系统的复杂度,排查错误更加麻烦,需要检查更多的节点。

为了简化结构,google 找了一圈工具,没找到合适、轻量且满足以下要求的工具:

想了想,还是自写工具来代替 nginx,因为原封装的 http api 服务是 go 写的,只要在其里面另 go 一个协程启动反向代理服务("net/http/httputil"下的 NewSingleHostReverseProxy)出来即可。
利用 http api 修改全局的转发配置,反向代理每次按照当前配置转发和设置请求头即可满足。

http-api 服务是主程(使用的 iris 框架只是图方便,后续加上权限控制也容易),不赘述。

反向代理服务简单说明

  1. 转发初始配置中,一个服务 target 对应多个 route,请求根据 route 中 path 正则匹配找到对应转发 target,然后覆写 path 和设置请求头
ProxyServ: ':82'
Confs:
  - Name: pay
    Target: http://127.0.0.1:80
    Routes:
      - Name: stripe-xxx
        Host: pay.xxx.com
        Path: '(/xxx-com/callback)/(stripexxx)'
        RePath: '{$1}/{$2}'
        ReqHeaders:
          - Name: Environment-Label
            Value: trunk
      - Name: payoneer
        Host: pay.xxx.com
        Path: '(/xxx-com/callback)/payoneer'
        RePath: '{$1}/payoneer'
        ReqHeaders:
          - Name: Environment-Label
            Value: trunk
  - Name: dd_social_pay
    Target: http://127.0.0.1:80
    Routes:
     - Name: stripe-xxx-dd
       Host: pay.xxx.com
       Path: '/callback/stripexxx/connect'
       RePath: '/callback/connect'
       ReqHeaders:
          - Name: Environment-Label
            Value: trunk

对应 go 中 struct:

// 请求头设置
type ReqHeader struct {
    Name  string `yaml:"Name"`
    Value string `yaml:"Value"`
}
// 路由设置
type Route struct {
    Name       string       `yaml:"Name"`
    Host       string       `yaml:"Host"`
    Path       string       `yaml:"Path"`
    RePath     string       `yaml:"RePath"`
    ReqHeaders []*ReqHeader `yaml:"ReqHeaders"`
}
// 单个反向设置
type SigleReverse struct {
    Name   string   `yaml:"Name"`
    Target string   `yaml:"Target"`
    Routes []*Route `yaml:"Routes"`
}
// 简单反向代理设置
type ReverseConf struct {
    ProxyServ string          `yaml:"ProxyServ"`
    Confs     []*SigleReverse `yaml:"Confs"`
}
  1. 因为要实时获取转发配置并进行对应转发处理,需要修改原版中的 proxy.Director,并将原版中私有方法 singleJoiningSlash、joinURLPath 拷贝一份出来使用:
proxy.Director = func(req *http.Request) {
//sr是全局配置中一个服务配置(SigleReverse),Target为其转发目标
                    target, err := url.Parse(strings.TrimSpace(sr.Target))
                    if err != nil {
                        log.Printf("转发的URL有误:%s", sr.Target)
                    }
                    targetQuery := target.RawQuery
                    req.URL.Scheme = target.Scheme
                    req.URL.Host = target.Host
                    req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
                    if targetQuery == "" || req.URL.RawQuery == "" {
                        req.URL.RawQuery = targetQuery + req.URL.RawQuery
                    } else {
                        req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
                    }
                    if _, ok := req.Header["User-Agent"]; !ok {
                        req.Header.Set("User-Agent", "")
                    }
                    xff := strings.TrimSpace(req.Header.Get("X-Forwarded-For"))
                    if xff != "" {
                        xff = xff + "," + strings.Split(req.RemoteAddr, ":")[0]
                    } else {
                        xff = strings.Split(req.RemoteAddr, ":")[0]
                    }
                    req.Header.Set("X-Forwarded-For", xff)
                    for _, srh := range rt.ReqHeaders {
                        req.Header.Set(srh.Name, srh.Value)
                    }
                    if strings.TrimSpace(rt.Host) != "" {
                        req.Host = rt.Host
                    }
                    req.URL.Path = newPath
                    log.Printf("url:%s, host:%s, xff:%s\n", req.URL.String(), req.Host, req.Header.Get("X-Forwarded-For"))
                }
                proxy.ServeHTTP(w, r)
  1. 每个请求,通过遍历转发配置,匹配 path 的正则来找到对应 target 和进行 route 中修改 path 和请求头即可完成功能。
  2. 这样简化后的回调切换结构如下
  3. 再利用 vue 画个前端页面就完工了

github:
https://github.com/mao303mao/go_dynamic_reverse_proxy


↙↙↙阅读原文可查看相关链接,并与作者交流