golang 反向代理 Gin框架反向代理详解
前言
想用gin框架做一个反向代理服务,搜索了一圈,全是只讲解些皮毛的帖子,今天我就总结一下gin做反向代理的详细操作和原理
正文
开始之前我们先了解一些前置知识
gin的通配符
gin的动态路由可以自行查阅,我这里指列出两个通配符:冒号( :)、星号( * )
- 冒号通配符
只能匹配到一层路由
// 路由请求
router.GET("shuiche/:name", handle)
// 请求结果匹配
/shuiche/query 匹配
/shuiche/invoke 匹配
/shuiche/v1/find 不匹配
/shuiche/ 不匹配
- 星号通配符
能匹配到多层路由
// 路由请求
router.GET("shuiche/*name", handle)
// 请求结果匹配
/shuiche/query 匹配
/shuiche/invoke 匹配
/shuiche/v1/find 匹配
/shuiche/ 匹配
反向代理
概念:
反向代理是什么?有个很棒的说法是流量转发。服务端获取到客户端来的请求,将它发往另一个服务器,从另外一个服务器那里获取到响应再回给原先的客户端。反向的意义简单来说在于这个代理自身决定了何时将流量发往何处。
最小案例
我们先看一下最简单的代理案例
// router路由
router.Any("/proxy/*name", proxyHandler)
// api函数
func proxyHandler(c *gin.Context) {
var target = "http://127.0.0.1:8081"
proxyUrl, _ := url.Parse(target)
proxy := httputil.NewSingleHostReverseProxy(proxyUrl)
proxy.ServeHTTP(c.Writer, c.Request)
}
案例解释:
我们假设请求链接为: http://127.0.0.1:8080/proxy/test
我们假设:当前gin服务使用的端口是 8080, 被代理的服务器端口是8081,也就是说:客户端访问8080,8080 将请求转发给 8081
代理函数这里只做了一个事情:就是把 http://127.0.0.1:8080/proxy/test 原样发送给了 8081。
对与客户端来说,相当于发送了这样样的请求:http://127.0.0.1:8081/proxy/test
截取路由转发
有时候,我们的项目接口比较多,有些本地api,也有代理api,这时候就会给代理专门加一个前缀,例如:/proxy, 服务端拿到 /proxy 的路由请求全部做转发,这时候上面的案例就不能满足需求了,因为「最小案例」在做转发时会将 /proxy 原样转发到被代理的服务器上,这样就会报 404 错误。这时我们就需要将 /proxy 前缀去掉,看案例
// router路由
router.Any("/proxy/*name", proxyHandler)
// api函数
func proxyHandler(c *gin.Context) {
var target = "http://127.0.0.1:8081"
proxyUrl, _ := url.Parse(target)
c.Request.URL.Path = c.Param("name") // 重点是这行代码
proxy := httputil.NewSingleHostReverseProxy(proxyUrl)
proxy.ServeHTTP(c.Writer, c.Request)
}
案例解释:
我们在「最小案例」的代码基础上增加了一行代码,c.Request.URL.Path = c.Param("name")
,我们将请求的url地址重新修改为了 *name
匹配到的值,这样我们的服务在转发时就会转发新的请求地址
修改 Request 信息
有时候,我们在做代理时,需要增加一些新的请求头,比如增加鉴权 token 等字段到http的header里面
我们先看一种变通方案:
// router路由
router.Any("/proxy/*name", proxyHandler)
// api函数
func proxyHandler(c *gin.Context) {
var target = "http://127.0.0.1:8081"
proxyUrl, _ := url.Parse(target)
c.Request.URL.Path = c.Param("name")
c.Request.Header.Set("token", "token_123") // 重点是这行代码
proxy := httputil.NewSingleHostReverseProxy(proxyUrl)
proxy.ServeHTTP(c.Writer, c.Request)
}
上面案例依然是直接修改原本的请求内容,修改后再用 NewSingleHostReverseProxy 做代理,这样代理时会把原本的请求内容全部转发。
我们再学看一种高级用法:
// router路由
router.Any("/proxy/*name", proxyHandler)
// api函数
func proxyHandler(c *gin.Context) {
var target = "http://127.0.0.1:8081"
proxyUrl, _ := url.Parse(target)
proxy := httputil.NewSingleHostReverseProxy(proxyUrl)
originalDirector := proxy.Director // 先将原本的处理函数缓存
proxy.Director = func(req *http.Request) { // 重新赋值新的处理函数
originalDirector(req) // 执行原本的处理函数
req.Header.Set("x-token", aiotToken) // 增加我们想要对 request 做的操作代码
}
proxy.ServeHTTP(c.Writer, c.Request)
}
这个用法就相对正规一些,不会破坏原本的请求头
修改 Header的Host 信息
有时候,我们访问的服务器对请求做了限制,比如Host必须和访问地址相同,而我们加代理后,会把我们页面的Host带过去,这就导致访问被拒绝。
其实修改Host非常简单:只需要 c.Request.Host = example.com
这样一行代码就可以了。但是网上有很多不负责任的人会告诉你在代理中 req.Header.Set("Host", "example.com")
,请明确一点,在gin框架中,这行代码并不能改变Host的值
明确一点:仅Host 参数需要 直接赋值修改。其他的 header 值依然可以使用
req.Header.Set
或者req.Header.Add
设定
完整案例:
// router路由
router.Any("/proxy/*name", proxyHandler)
// api函数
func proxyHandler(c *gin.Context) {
var target = "http://127.0.0.1:8081"
c.Request.Host = example.com // 这里修改host后,会将修改后的代理出去
proxyUrl, _ := url.Parse(target)
proxy := httputil.NewSingleHostReverseProxy(proxyUrl)
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
req.Header.Set("x-token", aiotToken) // 这里可以修改除了 Host 以外的 Header 参数。
}
proxy.ServeHTTP(c.Writer, c.Request)
}
修改 Response 信息
httputil 反向代理为我们提供了一种非常简单的机制来修改我们从服务器获得的响应,可以根据你的应用场景来缓存或更改此响应,案例:
// router路由
router.Any("/proxy/*name", proxyHandler)
// api函数
func proxyHandler(c *gin.Context) {
var target = "http://127.0.0.1:8081"
proxyUrl, _ := url.Parse(target)
proxy := httputil.NewSingleHostReverseProxy(proxyUrl)
proxy.ModifyResponse = func(resp *http.Response) { // 给 ModifyResponse 赋 回调函数即可
resp.Header.Del("Access-Control-Allow-Origin") // 删除响应的某个请求头
resp.Header.Set("x_token","token123") // 添加某个响应头
// 或者操作响应数据.....
}
proxy.ServeHTTP(c.Writer, c.Request)
}
当使用多级反向代理时,而且每一级代理都增加了CORS 跨域插件,前端就会报跨域错误,常规做法是保留一层跨域配置即可,但是有时候我们不方便去掉某一层的跨域配置,这时候就需要再当前代理服务器上删掉返回数据的请求头
主要是这两个请求头需要删掉:"Access-Control-Allow-Origin","Access-Control-Allow-Credentials"
另外一种写法
我们查看 httputil.NewSingleHostReverseProxy
源码时会发现,底层调用的其实还是 httputil.ReverseProxy
这个方法,我们也可以直接调用,
// router路由
router.Any("/proxy/*name", proxyHandler)
// api函数
func proxyHandler(c *gin.Context) {
var target = "http://127.0.0.1:8081"
director := func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = target
req.Host = target
req.URL.Path = c.Param("proxyPath")
}
proxy := &httputil.ReverseProxy{Director: director}
proxy.ServeHTTP(c.Writer, c.Request)
}
案例解释:
如果有对http请求做处理的需求,都可以在 director
函数中修改,最终,代理转发的是 director
函数请求
后记
转发代理其实很简单,就好比:我让你帮我把一个物品给他 这么简单,你就是代理服务器,物品在过你手时,你可以对物品做二次处理,当然,他在把物品还回来的时候,你也可以拦截做二次处理。
如果你还有什么疑问,可以留言一起讨论