构建反向代理
发布者:admin 发表于:439天前 阅读数:568 评论:0

本节我们将建立一个反向代理应用。我们的想法是,通过请求http:// localhost:3333,所有流量都将转发到可配置的主机,响应将转发到你的浏览器。

这可以与端口转发和ssh隧道结合使用,以便通过中间服务器安全地访问网站。本节将从头开始构建反向代理,但标准库的net/http/httputil包也提供此功能。使用此包,可以通过Director

func(*http.Request)修改传入请求,并且可以通过 ModifyResponse

func(*http.Response) error错误修改传出响应。 此外,还支持缓冲响应。

实践

建立 proxy.go:

package proxy

import (
    "log"
    "net/http"
)

// Proxy 保存了客户端配置和需要代理的BaseURL地址
type Proxy struct {
    Client  *http.Client
    BaseURL string
}

// ServeHTTP 表示代理部署了Handler接口它操纵请求,将其转发给BaseURL,然后返回响应
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if err := p.ProcessRequest(r); err != nil {
        log.Printf("error occurred during process request: %s", err.Error())
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    resp, err := p.Client.Do(r)
    if err != nil {
        log.Printf("error occurred during client operation: %s", err.Error())
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()
    CopyResponse(w, resp)
}

建立 process.go:

package proxy

import (
    "bytes"
    "net/http"
    "net/url"
)

// ProcessRequest 根据Proxy设置修改请求
func (p *Proxy) ProcessRequest(r *http.Request) error {
    proxyURLRaw := p.BaseURL + r.URL.String()

    proxyURL, err := url.Parse(proxyURLRaw)
    if err != nil {
        return err
    }
    r.URL = proxyURL
    r.Host = proxyURL.Host
    r.RequestURI = ""
    return nil
}

// CopyResponse获取客户端响应并将所有内容写入原始处理程序中的ResponseWriter
func CopyResponse(w http.ResponseWriter, resp *http.Response) {
    var out bytes.Buffer
    out.ReadFrom(resp.Body)

    for key, values := range resp.Header {
        for _, value := range values {
            w.Header().Add(key, value)
        }
    }

    w.WriteHeader(resp.StatusCode)
    w.Write(out.Bytes())
}

建立 main.go:

package main

import (
    "fmt"
    "net/http"

    "github.com/agtorre/go-cookbook/chapter7/proxy"
)

func main() {
    p := &proxy.Proxy{
        Client:  http.DefaultClient,
        BaseURL: "https://www.golang.org",
    }
    http.Handle("/", p)
    fmt.Println("Listening on port :3333")
    err := http.ListenAndServe(":3333", nil)
    panic(err)
}

这会输出:

$ go run main.go
Listening on port :3333

在浏览器地址栏输入localhost:3333/, 你会看到跳转到了https://golang.org/。

说明

Go请求和响应对象在很大程度上可以在客户端和处理程序之间共享。示例代码接受由满足Handler接口的Proxy结构获取的请求。一旦请求可用,它就被修改为在请求之前添加Proxy.BaseURL。最后,响应被复制回ResponseWriter接口。

我们还可以添加一些其他功能,例如请求的基本身份验证,令牌管理等。这对于代理管理JavaScript或其他客户端应用程序的会话令牌管理非常有用。