How to read response body of ReverseProxy

17,209

Solution 1

httputil.ReverseProxy has a Transport field. You can use it to modify the response. For example:

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 example of the whole thing: http://play.golang.org/p/b0S5CbCMrI.

Solution 2

now httputil/reverseproxy, support than, see source

 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

Solution 3

I don't know best solution. But you can do something like this:

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)
}
Share:
17,209

Related videos on Youtube

fannheyward
Author by

fannheyward

aka fannheyward.

Updated on September 14, 2022

Comments

  • fannheyward
    fannheyward over 1 year
    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)
    }
    

    Reverse Proxy is works. How can I get the response body?

  • JimB
    JimB almost 9 years
    small nit, b can be wrapped with bytes.NewReader, which is a bit smaller structure.
  • JimB
    JimB almost 9 years
    This only manages to print out the body one chunk at a time. You would still need to copy it into a another io.Writer to get the entire stream; luckily there's already something for that: io.TeeReader. (or make a new RoundTripper if you want the response before it's being written out)
  • RoninDev
    RoninDev almost 9 years
    Yes, of course. I just show the idea. I personally think that @Ainar-G solution is better than mine.
  • tomasz
    tomasz almost 9 years
    I don't think OP's question is clear enough to discard this answer, in particular it doesn't say the body should be written to the string as a whole; this example, although a little bit too bloated, shows how to capture the response stream. Good solution for getting hold of response body on the fly and performing, for example, some encoding/decoding.
  • Brent Bradburn
    Brent Bradburn over 4 years
    Shouldn't this use defer for resp.Body.Close()?
  • craftizmv
    craftizmv almost 3 years
    No as, you will end up in an unhandled err. This call returns an error also if Close() fails.
  • SVUser
    SVUser almost 3 years
    Unfortunately, it seems that there is no way to access both req and res in a single function when using ModifyResponse, so if the modification logic is based on req dynamically, the Transport mod is the only approach. Why wouldn't they add req to this function? What am I missing?
  • Bravo Delta
    Bravo Delta over 2 years
    @SVUser - Probably a bit late, but you can access the request off the response like such: resp.Request