Go实现反向代理,并自动处理重定向
反向代理
说起反向代理,第一印象估计都是超高人气的Nginx,其凭借强大的性能,吸引了不少使用者。
为什么需要反向代理?
- 隐藏服务器细节:在你的反向代理机器背后,就是你真正的业务服务器。它可能是由上千台机器组成的庞大集群,也可能是一个便宜好用的云服务器。外部请求想要访问你的服务器,必须通过反向代理。并且假如你的服务器内部有重定向细节,反向代理服务器可以很好的屏蔽,让客户端完全无感知
- 负载均衡:nginx就是一个很好的负载均衡器,能很好的平衡服务器资源
- 统一SSL:不必让内部服务器执行这毫无必要的SSL过程,将资源解放出来专注业务服务
- 静态资源缓存:提供网页、文件的静态服务能力,并能提供压缩等节省服务器资源的手段
Go实现反向代理,并自动重定向
Go语言作为一个工具语言,其标准库就为我们提供了大量好用的工具。
今天的主角就是httputil.ReverseProxy
通过它,我们就可以立即获得一个基本的反向代理服务器,实现对请求的转发。
同时,它通过拦截器的设计,为我们提供了可扩展的能力,我们将基于它实现: “当目标服务器返回302响应时,自动转发请求到新的目标服务器上,完全屏蔽客户端的感知”。
首先,我们通过httputil.NewSingleHostReverseProxy
创建一个反向代理,它接受一个url参数,这个参数就是原始目标服务器的地址。创建完毕后,所有被这个proxy serve的请求,都会转发到原始目标服务器了
// create a ReverseProxy, it forward all request to url
proxy = httputil.NewSingleHostReverseProxy(url)
接下来,我们设置ModifyResponse
,它的作用是:对目标服务器的响应判断,如果需要302,那么返回一个我们自定义的重定向错误
type RedirectErr struct {
location *urlpkg.URL
}
func (e *RedirectErr) Error() string {
return "the request should redirect"
}
proxy.ModifyResponse = func(response *http.Response) error {
if response.StatusCode > 300 && response.StatusCode < 400 {
location, err := response.Location()
if err != nil {
return err
}
return &RedirectErr{location: location}
}
return nil
}
接着我们需要对上一步返回的错误进行处理:如果发现它是重定向错误,那么我们修改请求头,添加一个字段,包含重定向目标服务器的地址,然后重新serve
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
var redirect *RedirectErr
if redirect, ok = err.(*RedirectErr); !ok {
return
}
request.Header.Set("redirect", redirect.location.String())
proxy.ServeHTTP(writer, request)
}
我们通过Rewrite函数来对转发处理,这个函数如果检测到请求存在redirect字段,就会修改目标地址为redirect的值。否则还是转发到httputil.NewSingleHostReverseProxy(url)
的参数的URL。这里的rewriteRequestURL
函数就是从标准库复制出来的,作用就是修改request的url,以遍能将请求转发到target上。注意,这里我们通过存放在context里的body,进行了rewind,这是因为 request.body只能读取一次,第二次转发的时候,它将无法读取,所以需要还原。
porxy.Director = nil
proxy.Rewrite = func(request *httputil.ProxyRequest) {
if redirect := request.In.Header.Get("redirect"); redirect != "" {
request.In.Header.Del("redirect")
u, _ := urlpkg.Parse(redirect)
// rewind body
if body := request.In.Context().Value(bodyKey); body != nil {
if bodyBytes, ok := body.([]byte); ok {
request.Out.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
}
}
request.SetURL(u)
request.Out.Host = u.Host
return
}
request.SetURL(url)
request.Out.Host = url.Host
}
我们在 serve 之前,先把 body存起来,
// The body can only be read once, so we store it to make the body available on the second forwarding
if req.Body != nil {
bodyBytes, _ := io.ReadAll(req.Body)
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
req = req.WithContext(context.WithValue(req.Context(), bodyKey, bodyBytes))
}
p.ReverseProxy.ServeHTTP(rw, req)
- 0
- 0
-
分享