Browse Source

fix panic if handle request by http.ReverseProxy (#14)

panic: http: wrote more than the declared Content-Length
same issue: https://github.com/mholt/caddy/issues/38
micanzhang 8 years ago
parent
commit
8550979331
2 changed files with 72 additions and 2 deletions
  1. 6 0
      gzip.go
  2. 66 2
      gzip_test.go

+ 6 - 0
gzip.go

@@ -61,6 +61,12 @@ func (g *gzipWriter) Write(data []byte) (int, error) {
 	return g.writer.Write(data)
 }
 
+// Fix: https://github.com/mholt/caddy/issues/38
+func (g *gzipWriter) WriteHeader(code int) {
+	g.Header().Del("Content-Length")
+	g.ResponseWriter.WriteHeader(code)
+}
+
 func shouldCompress(req *http.Request) bool {
 	if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") ||
 		strings.Contains(req.Header.Get("Connection"), "Upgrade") {

+ 66 - 2
gzip_test.go

@@ -6,6 +6,8 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"net/http/httputil"
+	"net/url"
 	"strconv"
 	"testing"
 
@@ -14,16 +16,55 @@ import (
 )
 
 const (
-	testResponse = "Gzip Test Response "
+	testResponse        = "Gzip Test Response "
+	testReverseResponse = "Gzip Test Reverse Response "
 )
 
+func init() {
+	gin.SetMode(gin.ReleaseMode)
+}
+
+type rServer struct{}
+
+func (s *rServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+	fmt.Fprint(rw, testReverseResponse)
+}
+
+type closeNotifyingRecorder struct {
+	*httptest.ResponseRecorder
+	closed chan bool
+}
+
+func newCloseNotifyingRecorder() *closeNotifyingRecorder {
+	return &closeNotifyingRecorder{
+		httptest.NewRecorder(),
+		make(chan bool, 1),
+	}
+}
+
+func (c *closeNotifyingRecorder) close() {
+	c.closed <- true
+}
+
+func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
+	return c.closed
+}
+
 func newServer() *gin.Engine {
-	router := gin.Default()
+	// init reverse proxy server
+	rServer := httptest.NewServer(new(rServer))
+	target, _ := url.Parse(rServer.URL)
+	rp := httputil.NewSingleHostReverseProxy(target)
+
+	router := gin.New()
 	router.Use(Gzip(DefaultCompression))
 	router.GET("/", func(c *gin.Context) {
 		c.Header("Content-Length", strconv.Itoa(len(testResponse)))
 		c.String(200, testResponse)
 	})
+	router.Any("/reverse", func(c *gin.Context) {
+		rp.ServeHTTP(c.Writer, c.Request)
+	})
 	return router
 }
 
@@ -81,3 +122,26 @@ func TestNoGzip(t *testing.T) {
 	assert.Equal(t, w.Header().Get("Content-Length"), "19")
 	assert.Equal(t, w.Body.String(), testResponse)
 }
+
+func TestGzipWithReverseProxy(t *testing.T) {
+	req, _ := http.NewRequest("GET", "/reverse", nil)
+	req.Header.Add("Accept-Encoding", "gzip")
+
+	w := newCloseNotifyingRecorder()
+	r := newServer()
+	r.ServeHTTP(w, req)
+
+	assert.Equal(t, w.Code, 200)
+	assert.Equal(t, w.Header().Get("Content-Encoding"), "gzip")
+	assert.Equal(t, w.Header().Get("Vary"), "Accept-Encoding")
+	assert.NotEqual(t, w.Header().Get("Content-Length"), "0")
+	assert.NotEqual(t, w.Body.Len(), 19)
+	assert.Equal(t, fmt.Sprint(w.Body.Len()), w.Header().Get("Content-Length"))
+
+	gr, err := gzip.NewReader(w.Body)
+	assert.NoError(t, err)
+	defer gr.Close()
+
+	body, _ := ioutil.ReadAll(gr)
+	assert.Equal(t, string(body), testReverseResponse)
+}