diff --git a/internal/proxy/passthrough_test.go b/internal/proxy/passthrough_test.go index 46cb72d..80dfdb3 100644 --- a/internal/proxy/passthrough_test.go +++ b/internal/proxy/passthrough_test.go @@ -151,6 +151,41 @@ func wantRequestFields(t *testing.T, line logLine, host string, sent, received i } } +func TestAddsNoContentTypeTheAppDidNotSend(t *testing.T) { + t.Parallel() + + const typed = "text/plain" + + app := startApp(t, func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/typed" { + w.Header().Set("Content-Type", typed) + } else { + // The key with no value keeps Go's server from adding one. + w.Header()["Content-Type"] = nil + } + + _, _ = io.WriteString(w, "page") + }) + addr, _ := startProxy(t, app.URL, nil) + + // Go's server guesses a Content-Type for an answer without one only + // when the first part of the body goes out with the headers, which + // happens to some answers and not others: the test asks many times. + for range 50 { + got := get(t, addr, "/") + if got.header.Values("Content-Type") != nil { + t.Fatalf("client got Content-Type %q, want none", + got.header.Get("Content-Type")) + } + } + + got := get(t, addr, "/typed") + if got.header.Get("Content-Type") != typed { + t.Errorf("client got Content-Type %q, want %q", + got.header.Get("Content-Type"), typed) + } +} + func TestStreamsTheRequestBody(t *testing.T) { t.Parallel() diff --git a/internal/proxy/request.go b/internal/proxy/request.go index e67599c..922afa5 100644 --- a/internal/proxy/request.go +++ b/internal/proxy/request.go @@ -421,6 +421,13 @@ func (rq *request) modifyResponse(res *http.Response) error { return errResponseTooLarge } + // Go's server adds a Content-Type it guesses from the body to a + // response whose headers have no Content-Type key. The key with no + // value sends none, as the app sent none. + if res.Header.Get("Content-Type") == "" { + rq.out.Header()["Content-Type"] = nil + } + res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq} rq.startClientResponseTimeout()