Gzipped reverse proxy produces bad responses

I’m trying to write a multiplexer in front of a variety of services using reverse proxies. However, when I try to gzip the response from the reverse proxy, the resulting response is bad. I get “unexpected EOF” when using ioutil.ReadAll, and curl complains about curl: (18) transfer closed with 3 bytes remaining to read.

I suspect this is a bug in ReverseProxy, and filed https://github.com/golang/go/issues/14975 to that end. However, I thought I’d also check here in case anybody else has done something like this and gotten it to work.

Sample code:

package main

import (
    "compress/gzip"
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strings"
    "time"
)

// Gzip from https://gist.github.com/the42/1956518
type gzipResponseWriter struct {
    io.Writer
    http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
    return w.Writer.Write(b)
}

func makeGzipHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            fn(w, r)
            return
        }
        w.Header().Set("Content-Encoding", "gzip")
        gz := gzip.NewWriter(w)
        defer gz.Close()
        gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
        fn(gzr, r)
    }
}

// Handler that does not set a content length, so, golang uses chunking.
func handler(w http.ResponseWriter, r *http.Request) {
    message := "Hello, world!"
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte(message))
}

// Constructs a reverse proxy to the given port.
func reverseProxy(port string) func(http.ResponseWriter, *http.Request) {
    url, err := url.Parse("http://127.0.0.1" + port)
    if err != nil {
        panic(err)
    }
    return httputil.NewSingleHostReverseProxy(url).ServeHTTP
}

// Gets the content from the given server, then returns the error from reading the body.
func get(server http.Server) error {
    resp, err := http.Get("http://127.0.0.1" + server.Addr)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    _, err = ioutil.ReadAll(resp.Body)
    return err
}

func main() {
    server := http.Server{
        Addr:    ":2000",
        Handler: http.HandlerFunc(handler),
    }
    go server.ListenAndServe()

    proxyServer := http.Server{
        Addr:    ":4000",
        Handler: makeGzipHandler(reverseProxy(server.Addr)),
    }
    go proxyServer.ListenAndServe()

    time.Sleep(10 * time.Millisecond)

    fmt.Printf("Server err: %v\n", get(server))
    fmt.Printf("Proxy server err: %v\n", get(proxyServer))
}

Anybody seen anything like this before, or gotten it to work?

Note that this was fixed in the linked issue - gw.Close() returns an error that isn’t being checked.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.