net/httputil 包
httputil包提供了HTTP公用函数, 是对net/http包的更常见函数的补充
反向代理的实现
方法一:
httputil.NewSingleHostReverseProxy()
NewSingleHostReverseProxy返回一个新的ReverseProxy。返回值会将请求的URL重写为target参数提供的协议、主机和基路径。
如果target参数的Path字段为"/base", 接收到的请求的URL.Path为"/dir", 修改后的请求的URL.Path将会是"/base/dir"
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxyfunc (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
ReverseProxy是一个HTTP处理器, 它接收一个请求, 发送给另一个服务端, 将回复转发给客户端
实例:
package mainimport ( "net/http" "net/http/httputil" "net/url")func main() { http.HandleFunc("/index.html", func(writer http.ResponseWriter, request *http.Request) { url, _ := url.ParseRequestURI("http://www.baidu.com") reverseProxy := httputil.NewSingleHostReverseProxy(url) reverseProxy.ServeHTTP(writer, request) }) http.ListenAndServe(":8080", nil)}
当我们请求
http://localhost:8080/index.html, 系统会自动代理到http://www.baidu.com主机上, 实际方位的地址为:
http://www.baidu.com/index.html
方法二: httputil.ReverseProxy{}
go 内置反向代理 ReverseProxy, 使用 ReverseProxy 实现反向代理服务器! 反向代理服务优点:
1 提高网络速度:因为数据会存在代理服务中, 然后对其进行转发。具有一定的存储功能! 也可以根据相关负载均衡对策进行资源的利用
2 其到防火墙作用:可以过滤请求, 过滤某些不安全信息。防止重要的服务器受到攻击出现问题!
3 通过代理服务器访问不能访问的目标站点, 互联网上有许多开发的代理服务器, 客户机可访问受限时, 可通过不受限的代理服务器 访问目标站点, 通俗说, 我们使用的翻墙浏览器就是利用了代理服务器, 可直接访问外网。
ReverseProxy 功能点
更改内容支持
错误信息回调
支持自定义负载均衡
url重写
支持协议升级(websocket, https)
实例:
package mainimport ( "bytes" "compress/gzip" "io/ioutil" "log" "math/rand" "net" "net/http" "net/http/httputil" "net/url" "regexp" "strconv" "strings" "time")var addr = "127.0.0.1:2002"func main() { rs1 := "http://www.baidu.com" //rs1 := "http://127.0.0.1:2003" url1, err1 := url.Parse(rs1) if err1 != nil { log.Println(err1) } // 注意: url地址 支持 http和https地址 rs2 := "https://www.hao123.com/" //rs2 := "http://127.0.0.1:2004" url2, err2 := url.Parse(rs2) if err2 != nil { log.Println(err2) } urls := []*url.URL{url1, url2} proxy := NewMultipleHostsReverseProxy(urls) log.Println("Starting httpserver at " + addr) log.Fatal(http.ListenAndServe(addr, proxy)) } var transport = &http.Transport{ DialContext: (&net.Dialer{ Timeout: 30 * time.Second, //连接超时 KeepAlive: 30 * time.Second, //长连接超时时间 }).DialContext, MaxIdleConns: 100, //最大空闲连接 IdleConnTimeout: 90 * time.Second, //空闲超时时间 TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间 ExpectContinueTimeout: 1 * time.Second, //100-continue 超时时间}func NewMultipleHostsReverseProxy(targets []*url.URL) *httputil.ReverseProxy { //请求协调者 director := func(req *http.Request) { //url_rewrite //127.0.0.1:2002/dir/abc ==> 127.0.0.1:2003/base/abc ?? //127.0.0.1:2002/dir/abc ==> 127.0.0.1:2002/abc //127.0.0.1:2002/abc ==> 127.0.0.1:2003/base/abc re, _ := regexp.Compile("^/dir(.*)") req.URL.Path = re.ReplaceAllString(req.URL.Path, "") //随机负载均衡 targetIndex := rand.Intn(len(targets)) target := targets[targetIndex] targetQuery := target.RawQuery req.URL.Scheme = target.Scheme req.URL.Host = target.Host //todo 部分章节补充1 //todo 当对域名(非内网)反向代理时需要设置此项。当作后端反向代理时不需要 req.Host = target.Host // url地址重写:重写前:/aa 重写后:/base/aa 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", "user-agent") } //只在第一代理中设置此header头 //req.Header.Set("X-Real-Ip", req.RemoteAddr) } //更改内容 modifyFunc := func(resp *http.Response) error { //请求以下命令:curl 'http://127.0.0.1:2002/error' //todo 部分章节功能补充2 //todo 兼容websocket if strings.Contains(resp.Header.Get("Connection"), "Upgrade") { return nil } var payload []byte var readErr error //todo 部分章节功能补充3 //todo 兼容gzip压缩 if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") { gr, err := gzip.NewReader(resp.Body) if err != nil { return err } payload, readErr = ioutil.ReadAll(gr) resp.Header.Del("Content-Encoding") } else { payload, readErr = ioutil.ReadAll(resp.Body) } if readErr != nil { return readErr } //异常请求时设置StatusCode if resp.StatusCode != 200 { payload = []byte("StatusCode error:" + string(payload)) } //todo 部分章节功能补充4 //todo 因为预读了数据所以内容重新回写 resp.Body = ioutil.NopCloser(bytes.NewBuffer(payload)) resp.ContentLength = int64(len(payload)) resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(payload)), 10)) return nil } //错误回调 :关闭real_server时测试,错误回调 errFunc := func(w http.ResponseWriter, r *http.Request, err error) { http.Error(w, "ErrorHandler error:"+err.Error(), 500) } return &httputil.ReverseProxy{ Director: director, Transport: transport, ModifyResponse: modifyFunc, ErrorHandler: errFunc}}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}
> # go run main.go
访问方式:
> # curl http://127.0.0.1:2002