golang原生提供的httputil
库可以非常方便的实现反向代理。那么到底有多简单呢?如果只实现一个简单的定向代理,main
函数里只需要写3行代码!
package main
import (
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
u, _ := url.Parse("http://localhost:8888")
http.Handle("/", httputil.NewSingleHostReverseProxy(u))
http.ListenAndServe(":9999", nil)
}
上面的程序将本地:9999
端口代理到:8888
,当然你需要在:8888
端口起一个服务。
如果只是实现本地代理,上面的程序是完全没有问题的,但是如果你想代理到本机以外的域名,就会得到一个404
的响应,比如你想代理到www.baidu.com
,发现根本代理不过去。原因出在NewSingleHostReverseProxy
函数上。首先我们来看一下反向代理的真面目。
type ReverseProxy struct {
Director func(*http.Request)
Transport http.RoundTripper
FlushInterval time.Duration
ErrorLog *log.Logger
BufferPool BufferPool
ModifyResponse func(*http.Response) error
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
并无神奇之处,反向代理就是一个结构体!
从后往前说,ErrorHandler
是错误处理器,看到这个函数签名你应该感到熟悉。代理发生错误时,返回给你什么响应由它说了算,默认是一个404
的处理器。
ModifyResponse
用来修改响应,如果你想自定义代理的响应,不妨自己实现这个函数。
BufferPool
故名思意就是缓存池,代理需要拷贝响应内容,一般不需要自定义它。
Errorlog
日志,没啥好说的。
FlushInterval
拷贝响应时,将内容刷新到客户端的时间间隔,0代表不刷新,负值代表立即刷新;如果是流式响应,会忽略这个值,并总是立即刷新。
Transport
用来执行代理的请求,得到响应。
Director
正是问题的关键,它会重写请求,反向代理代理到哪里去就是由它说了算。
接下来就来看看我们是如何得到一个反向代理结构体的。
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
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", "")
}
}
return &ReverseProxy{Director: director}
}
NewSingleHostReverseProxy
用了一个闭包作为Director
新建了一个ReverseProxy
结构体,director
就是它为我们默认实现的代理函数。在这个函数中重写了请求URL的协议、Host
和Path
等。但是它没有重写req.Host
,这也就导致了我们的反向代理只能在本地进行代理。所以我们只要对这个函数进行修改就可以了,有两种方式可以修改。
一是我们可以将NewSingleHostReverseProxy
函数拷贝出来进行修改,反正这个函数也不复杂,然后在需要用到反向代理结构体的地方用我们自定义的函数新建代理器。
func NewProxy(target *url.URL) *httputil.ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.Host = target.Host // -- 加入这句 --
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
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 {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
}
return &httputil.ReverseProxy{Director: director}
}
然后将httputil.NewSingleHostReverseProxy(u)
替换成NewProxy(u)
就可以代理到其他域名了。
二是自定义Director
函数。我们还是用NewSingleHostReverseProxy
函数新建代理器,然后自定义一个Director
函数给它。
p := httputil.NewSingleHostReverseProxy(u)
d := p.Director
p.Director = func(r *http.Request) {
d(r)
r.Host = u.Host
}
最后附图一张。
![](https://i-blog.csdnimg.cn/blog_migrate/cbec74a54a4f46f7344c13c173af3b55.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ba3deddbd93c3490617073b8c694c842.png)