跨域案例go gf ,请求代理,前端请求后端A转后端B
案例:从前端请求后端A(路径携带argusx),后端A转发请求到多个不同地区(可一个)后端B(切掉argusx,其他不变进行请求),由请求头x-proxy指定请求哪个服务端
方案一:handler形式处理:
func InitRouter() {
s := g.Server()
// 分组路由注册方式
app.Api = s.Group("/argusx", func(group *ghttp.RouterGroup) {
// 跨域中间件
service.Middleware.InitGroup(group)
ReverseProxy(group, "/xxx/")
})
}
// InitGroup 注册中间件
func (s *middlewareService) InitGroup(group *ghttp.RouterGroup) {
group.Middleware(
// 跨域处理
s.CORS,
//s.Auth,
)
}
// 使用默认跨域处理
func (s *middlewareService) CORS(r *ghttp.Request) {
//r.Response.CORSDefault() //若是请求头没有新的,则直接用default,否则用下面三行代码
options := r.Response.DefaultCORSOptions()
options.AllowHeaders = options.AllowHeaders + ",X-Proxy"
r.Response.CORS(options)
r.Middleware.Next()
}
func ReverseProxy(pg *ghttp.RouterGroup, path string) {
op := func(r *ghttp.Request) {
// 根据code拿到地址,可用自己的方式获取参数
argusvoiceCode := r.GetHeader("x-proxy")
g.Log().Infof("[argusvoice] argusvoiceCode=%s", argusvoiceCode)
if argusvoiceCode != "" {
argusvoiceApi := service.GetArgusVoiceUrlByCode(argusvoiceCode)
g.Log().Infof("[argusvoice] argusvoiceApi=%s", argusvoiceApi)
remote, err := url.Parse(argusvoiceApi)
if err != nil {
fmt.Println(err.Error())
g.Log().Errorf("[argusvoice] url parse error, argusvoiceApi=%s, error=%v", argusvoiceApi, err)
}
reverseProxy := proxy.GoReverseProxy(&proxy.RProxy{
Remote: remote,
})
if err != nil {
fmt.Println(err.Error())
r.ExitAll()
return
}
reverseProxy.ServeHTTP(r.Response.Writer, r.Request)
r.ExitAll()
} else {
r.Middleware.Next()
}
}
pg.Bind([]ghttp.GroupItem{{"ALL", path + "*", op}})
}
方案二:中间件的形式代理:
对所有请求都拦截,包括options,这样需要自己处理options请求,options请求是为了协商请求头,所以需要返回成功以及必要信息方便后期请求携带。
请求允许的加上x-proxy,注意option请求是拿不到具体的值
坏处:无法使用框架自带的options处理
s.BindMiddlewareDefault(func(r *ghttp.Request) {
// 根据code拿到地址
accessControlHeaders := r.Header.Get("Access-Control-Request-Headers")
isProxy := strings.Contains(accessControlHeaders, "x-proxy")
argusvoiceCode := r.GetHeader("x-proxy")
r.Header.Set("Access-Control-Allow-Origin", "*")
g.Log().Infof("[argusvoice] argusvoiceCode=%s", argusvoiceCode)
if isProxy || (argusvoiceCode != "") {
argusvoiceApi := service.GetArgusVoiceUrlByCode(argusvoiceCode)
if r.Request.Method == "OPTIONS" {
r.Response.Status = 200
// 以下头部请参照自己正常请求的头部
r.Response.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) //此处不能写作*
r.Response.Header().Set("Access-Control-Allow-Credentials", "true")
r.Response.Header().Set("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token, x-requested-with,Accept,Origin,Referer,User-Agent,x-proxy")
r.Response.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE")
r.Response.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
return
}
g.Log().Infof("[argusvoice] argusvoiceApi=%s", argusvoiceApi)
remote, err := url.Parse(argusvoiceApi)
if err != nil {
fmt.Println(err.Error())
g.Log().Errorf("[argusvoice] url parse error, argusvoiceApi=%s, error=%v", argusvoiceApi, err)
}
reverseProxy := proxy.GoReverseProxy(&proxy.RProxy{
Remote: remote,
})
if err != nil {
fmt.Println(err.Error())
r.ExitAll()
return
}
reverseProxy.ServeHTTP(r.Response.Writer, r.Request)
r.ExitAll()
} else {
r.Middleware.Next()
}
})
proxy文件:(此处代码大部分抄自https://github.com/hezhizheng/go-reverse-proxy/blob/master/handle.go)
该项目使用案例:https://hzz.cool/blog/implementation-of-simple-http-and-https-reverse-proxy-by-golang
package proxy
import (
"github.com/gogf/gf/frame/g"
"net/http"
"net/http/httputil"
"net/url"
"strings"
)
type RProxy struct {
Remote *url.URL
}
func GoReverseProxy(this *RProxy) *httputil.ReverseProxy {
remote := this.Remote
proxy := httputil.NewSingleHostReverseProxy(remote)
proxy.Director = func(request *http.Request) {
Path := ""
targetQuery := remote.RawQuery
request.URL.Scheme = remote.Scheme
request.URL.Host = remote.Host
request.Host = remote.Host
Path, request.URL.RawPath = joinURLPath(remote, request.URL)
Paths := strings.Split(Path, "/argusx")
request.URL.Path = Paths[1]
g.Log().Infof("[argusvoice] request.Body=%v", request.Body)
if targetQuery == "" || request.URL.RawQuery == "" {
request.URL.RawQuery = targetQuery + request.URL.RawQuery
} else {
request.URL.RawQuery = targetQuery + "&" + request.URL.RawQuery
}
g.Log().Infof("[argusvoice] request.URL.Path=%s, request.URL.RawQuery=%s", request.URL.Path, request.URL.RawQuery)
}
proxy.ModifyResponse = func(response *http.Response) error {
response.Header.Del("Access-Control-Allow-Origin")
response.Header.Del("Access-Control-Allow-Credentials")
response.Header.Del("Access-Control-Allow-Headers")
response.Header.Del("Access-Control-Allow-Methods")
return nil
}
return proxy
}
// go sdk 源码
func joinURLPath(a, b *url.URL) (path, rawpath string) {
if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), ""
}
// Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added
apath := a.EscapedPath()
bpath := b.EscapedPath()
aslash := strings.HasSuffix(apath, "/")
bslash := strings.HasPrefix(bpath, "/")
switch {
case aslash && bslash:
return a.Path + b.Path[1:], apath + bpath[1:]
case !aslash && !bslash:
return a.Path + "/" + b.Path, apath + "/" + bpath
}
return a.Path + b.Path, apath + bpath
}
// go sdk 源码
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}