Go语言网络代理:深入了解net/httputil包

发表时间: 2024-05-16 21:41

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