mirror of
https://github.com/caddyserver/caddy.git
synced 2026-10-31 08:21:39 -04:00
reverseproxy: Keep own headers after 1xx responses (#8169)
* reverseproxy: Keep own headers after 1xx responses Since #6656 the 1xx handler clears the whole header map after writing the informational response, which also drops headers set before proxying (for example by the header handler). Restore those after clearing, as #5091 did. * reverseproxy: Use maps.Copy to restore own headers * reverseproxy: Check the 103 reaches the client in the 1xx headers test
This commit is contained in:
1 parent
1b3838c1fd
commit
519be12306
2 files changed
+64
-1
No files matched your search
@@ -2,7 +2,10 @@ package reverseproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
@@ -125,3 +128,60 @@ func TestAddForwardedHeaders_UnixSocketTrustedNoExistingHeaders(t *testing.T) {
|
||||
t.Errorf("X-Forwarded-Host = %q, want %q", got, "example.com")
|
||||
}
|
||||
}
|
||||
|
||||
// TestInformationalResponseKeepsHandlerHeaders verifies that a 1xx response
|
||||
// from the upstream does not remove response headers set before proxying.
|
||||
func TestInformationalResponseKeepsHandlerHeaders(t *testing.T) {
|
||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Link", "</style.css>; rel=preload")
|
||||
w.WriteHeader(http.StatusEarlyHints)
|
||||
w.Header().Del("Link")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
t.Cleanup(backend.Close)
|
||||
|
||||
h := minimalHandler(0, &Upstream{Host: new(Host), Dial: backend.Listener.Addr().String()})
|
||||
|
||||
front := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
|
||||
_ = h.ServeHTTP(w, prepareTestRequest(r), caddyhttp.HandlerFunc(func(http.ResponseWriter, *http.Request) error {
|
||||
return nil
|
||||
}))
|
||||
}))
|
||||
t.Cleanup(front.Close)
|
||||
|
||||
var earlyHints []textproto.MIMEHeader
|
||||
trace := &httptrace.ClientTrace{
|
||||
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
||||
if code == http.StatusEarlyHints {
|
||||
earlyHints = append(earlyHints, header)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, front.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if len(earlyHints) != 1 {
|
||||
t.Fatalf("103 responses: got %d, want 1", len(earlyHints))
|
||||
}
|
||||
if got := earlyHints[0].Get("Link"); got != "</style.css>; rel=preload" {
|
||||
t.Errorf("Link on the 103: got %q, want %q", got, "</style.css>; rel=preload")
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("status: got %d, want %d", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
if got := resp.Header.Get("Strict-Transport-Security"); got != "max-age=31536000" {
|
||||
t.Errorf("Strict-Transport-Security: got %q, want %q", got, "max-age=31536000")
|
||||
}
|
||||
if got := resp.Header.Get("Link"); got != "" {
|
||||
t.Errorf("Link from the 103 leaked into the final response: %q", got)
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
@@ -1174,12 +1175,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
|
||||
return nil
|
||||
}
|
||||
h := rw.Header()
|
||||
ownHeaders := h.Clone()
|
||||
copyHeader(h, http.Header(header))
|
||||
rw.WriteHeader(code)
|
||||
|
||||
// Clear headers coming from the backend
|
||||
// Clear headers coming from the backend, keeping our own
|
||||
// (it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses)
|
||||
clear(h)
|
||||
maps.Copy(h, ownHeaders)
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user