iris icon indicating copy to clipboard operation
iris copied to clipboard

[BUG] proxy chunked enconding upstream erase body when record is active

Open zxfishhack opened this issue 4 years ago • 1 comments

Describe the bug I setup a chunked encoding response http server at localhost:9394, and try use host.ProxyHandle to proxy it when ctx.Record() is call in middleware, that lead to body be erased.

To Reproduce Steps to reproduce the behavior:

package main
import (
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/context"
	"github.com/kataras/iris/v12/core/host"
	"net/http"
	"net/http/httputil"
	"net/url"
)

func recorder(ctx *context.Context) {
	if _, ok := ctx.IsRecording(); !ok {
		ctx.Record()
	}

	ctx.Next()
}

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		chunkedWriter := httputil.NewChunkedWriter(writer)
		writer.Header().Set("Transfer-Encoding", "chunked")
		chunkedWriter.Write([]byte("hello"))
		chunkedWriter.Close()
	})

	go http.ListenAndServe(":9394", nil)

	app := iris.New()
	app.Use(recorder)
	u, _ := url.Parse("http://localhost:9394")
	app.Get("/", iris.FromStd(host.ProxyHandler(u, nil)))

	app.Run(iris.Addr(":9395"))
}

Expected behavior body response to client as normal

iris.Version

  • v12.1.8
  • go 1.16.4

Please make sure the bug is reproducible over the master branch:

$ cd PROJECT
$ go get -u github.com/kataras/iris/v12@master
$ go run .

Additional context a quick workaround is supply ModifyResponse to httputil.ReverseProxy, modify response.ContentLength to 0 when it's -1. (http.ReverseProxy will not call ResponseWriter.Flush in this situation)

zxfishhack avatar Aug 26 '21 07:08 zxfishhack

Hi

I think the bug come from this in go 1.16.4

var hopHeaders = []string{
       ...
	"Transfer-Encoding",
       ...
}

func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
        ...
	for _, h := range hopHeaders {
		res.Header.Del(h)
	}
       ...
}

And in recorder Flush, but i don't understand why we should check w.headers.Get("Transfer-Encoding") == "chunked".

// Flush sends any buffered data to the client.
func (w *ResponseRecorder) Flush() {
	// This fixes response recorder when chunked + Flush is used.
	if w.headers.Get("Transfer-Encoding") == "chunked" {
		if w.Written() == NoWritten {
			if len(w.headers) > 0 {
				h := w.ResponseWriter.Header()
				// note: we don't reset the current underline's headers.
				for k, v := range w.headers {
					h[k] = v
				}
			}
		}

		if len(w.chunks) > 0 {
			w.ResponseWriter.Write(w.chunks)
		}
	}

	w.ResponseWriter.Flush()
	w.ResetBody()
}

TcMits avatar Oct 21 '22 12:10 TcMits