如何读取反向代理的响应体

26
package main

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

func main() {
    target := &url.URL{Scheme: "http", Host: "www.google.com"}
    proxy := httputil.NewSingleHostReverseProxy(target)

    http.Handle("/google", proxy)
    http.ListenAndServe(":8099", nil)
}

反向代理正在运作。我该如何获取响应正文?

4个回答

33

现在的httputil/reverseproxy支持比以前更多的功能,查看源代码获取更多信息。

 type ReverseProxy struct {
        ...

        // ModifyResponse is an optional function that
        // modifies the Response from the backend
        // If it returns an error, the proxy returns a StatusBadGateway error.
        ModifyResponse func(*http.Response) error
    }



func rewriteBody(resp *http.Response) (err error) {
    b, err := ioutil.ReadAll(resp.Body) //Read html
    if err != nil {
        return  err
    }
    err = resp.Body.Close()
    if err != nil {
        return err
    }
    b = bytes.Replace(b, []byte("server"), []byte("schmerver"), -1) // replace html
    body := ioutil.NopCloser(bytes.NewReader(b))
    resp.Body = body
    resp.ContentLength = int64(len(b))
    resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
    return nil
}

// ...
target, _ := url.Parse("http://example.com")
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.ModifyResponse = rewriteBody

1
这里不应该使用 defer 来关闭 resp.Body 吗? - Brent Bradburn
不要这样做,否则你会遇到未处理的错误。即使Close()失败,此调用也会返回一个错误。 - craftizmv
不幸的是,当使用ModifyResponse时,在单个函数中访问req和res似乎没有办法,因此,如果修改逻辑基于动态的req,那么Transport mod是唯一的方法。为什么他们不在这个函数中添加req呢?我错过了什么吗? - SVUser
1
@SVUser - 可能有点晚了,但您可以像这样从响应中访问请求:resp.Request - Bravo Delta

31

httputil.ReverseProxy有一个Transport字段,您可以使用它来修改响应。例如:

type transport struct {
    http.RoundTripper
}

func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
    resp, err = t.RoundTripper.RoundTrip(req)
    if err != nil {
        return nil, err
    }
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    err = resp.Body.Close()
    if err != nil {
        return nil, err
    }
    b = bytes.Replace(b, []byte("server"), []byte("schmerver"), -1)
    body := ioutil.NopCloser(bytes.NewReader(b))
    resp.Body = body
    resp.ContentLength = int64(len(b))
    resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
    return resp, nil
}

// ...
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Transport = &transport{http.DefaultTransport}

整个事情的示例Playground: http://play.golang.org/p/b0S5CbCMrI


3
еҸҜд»Ҙз”Ёbytes.NewReaderжқҘеҢ…иЈ…bпјҢиҝҷжҳҜдёҖдёӘжӣҙе°Ҹзҡ„з»“жһ„дҪ“гҖӮ - JimB
如果HTTP升级到Websocket,我能获取响应的长度吗? - caimaoy

5
我不知道最好的解决方案。但是你可以做类似以下的事情:
package main

import (
    "fmt"
    "net/http"
    "net/http/httputil"
    "net/url"
)

func main() {
    target := &url.URL{Scheme: "http", Host: "www.google.com"}
    proxy := httputil.NewSingleHostReverseProxy(target)

    http.Handle("/google", CustomHandler(proxy))
    http.ListenAndServe(":8099", nil)
}

func CustomHandler(h http.Handler) http.HandlerFunc {
    return func(res http.ResponseWriter, req *http.Request) {
        h.ServeHTTP(NewCustomWriter(res), req)
    }
}

type customWriter struct {
    http.ResponseWriter
}

func NewCustomWriter(w http.ResponseWriter) *customWriter {
    return &customWriter{w}
}

func (c *customWriter) Header() http.Header {
    return c.ResponseWriter.Header()
}

func (c *customWriter) Write(data []byte) (int, error) {
    fmt.Println(string(data)) //get response here
    return c.ResponseWriter.Write(data)
}

func (c *customWriter) WriteHeader(i int) {
    c.ResponseWriter.WriteHeader(i)
}

这只能一次打印出主体的一个块。您仍需要将其复制到另一个 io.Writer 中以获取整个流; 幸运的是,已经有了一个工具:io.TeeReader。(或者如果您想在写出响应之前获得响应,则可以创建新的 RoundTripper) - JimB
当然可以。我只是提出了一个想法。我个人认为@Ainar-G的解决方案比我的更好。 - RoninDev
我认为楼主的问题不够清晰,不能排除这个答案,特别是它没有说明正文应该作为一个整体写入字符串;虽然这个例子有点臃肿,但它展示了如何捕获响应流。这是一个很好的解决方案,可以实时获取响应正文并执行例如编码/解码等操作。 - tomasz

-1

从源代码中,httptest.ResponseRecorder 用于获取处理程序的响应。

func TestModifyResponseClosesBody(t *testing.T) {
    req, _ := http.NewRequest("GET", "http://foo.tld/", nil)
    req.RemoteAddr = "1.2.3.4:56789"
    closeCheck := new(checkCloser)
    logBuf := new(bytes.Buffer)
    outErr := errors.New("ModifyResponse error")
    rp := &ReverseProxy{
        Director: func(req *http.Request) {},
        Transport: &staticTransport{&http.Response{
            StatusCode: 200,
            Body:       closeCheck,
        }},
        ErrorLog: log.New(logBuf, "", 0),
        ModifyResponse: func(*http.Response) error {
            return outErr
        },
    }
    rec := httptest.NewRecorder()
    rp.ServeHTTP(rec, req)
    res := rec.Result()
    if g, e := res.StatusCode, http.StatusBadGateway; g != e {
        t.Errorf("got res.StatusCode %d; expected %d", g, e)
    }
    if !closeCheck.closed {
        t.Errorf("body should have been closed")
    }
    if g, e := logBuf.String(), outErr.Error(); !strings.Contains(g, e) {
        t.Errorf("ErrorLog %q does not contain %q", g, e)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接