Support informational headers in middlewares redefining the response writer.
Co-authored-by: LandryBe <lbenguigui@gmail.com>
This commit is contained in:
parent
68ed875966
commit
6885e410f0
10 changed files with 471 additions and 7 deletions
|
@ -193,11 +193,25 @@ func (cc *codeCatcher) Write(buf []byte) (int, error) {
|
|||
return cc.responseWriter.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader is, in the specific case of 1xx status codes, a direct call to the wrapped ResponseWriter, without marking headers as sent,
|
||||
// allowing so further calls.
|
||||
func (cc *codeCatcher) WriteHeader(code int) {
|
||||
if cc.headersSent || cc.caughtFilteredCode {
|
||||
return
|
||||
}
|
||||
|
||||
// Handling informational headers.
|
||||
if code >= 100 && code <= 199 {
|
||||
// Multiple informational status codes can be used,
|
||||
// so here the copy is not appending the values to not repeat them.
|
||||
for k, v := range cc.Header() {
|
||||
cc.responseWriter.Header()[k] = v
|
||||
}
|
||||
|
||||
cc.responseWriter.WriteHeader(code)
|
||||
return
|
||||
}
|
||||
|
||||
cc.code = code
|
||||
for _, block := range cc.httpCodeRanges {
|
||||
if cc.code >= block[0] && cc.code <= block[1] {
|
||||
|
@ -208,7 +222,11 @@ func (cc *codeCatcher) WriteHeader(code int) {
|
|||
}
|
||||
}
|
||||
|
||||
utils.CopyHeaders(cc.responseWriter.Header(), cc.Header())
|
||||
// The copy is not appending the values,
|
||||
// to not repeat them in case any informational status code has been written.
|
||||
for k, v := range cc.Header() {
|
||||
cc.responseWriter.Header()[k] = v
|
||||
}
|
||||
cc.responseWriter.WriteHeader(cc.code)
|
||||
cc.headersSent = true
|
||||
}
|
||||
|
@ -303,12 +321,28 @@ func (r *codeModifierWithoutCloseNotify) Write(buf []byte) (int, error) {
|
|||
|
||||
// WriteHeader sends the headers, with the enforced code (the code in argument is always ignored),
|
||||
// if it hasn't already been done.
|
||||
func (r *codeModifierWithoutCloseNotify) WriteHeader(_ int) {
|
||||
// WriteHeader is, in the specific case of 1xx status codes, a direct call to the wrapped ResponseWriter, without marking headers as sent,
|
||||
// allowing so further calls.
|
||||
func (r *codeModifierWithoutCloseNotify) WriteHeader(code int) {
|
||||
if r.headerSent {
|
||||
return
|
||||
}
|
||||
|
||||
utils.CopyHeaders(r.responseWriter.Header(), r.Header())
|
||||
// Handling informational headers.
|
||||
if code >= 100 && code <= 199 {
|
||||
// Multiple informational status codes can be used,
|
||||
// so here the copy is not appending the values to not repeat them.
|
||||
for k, v := range r.headerMap {
|
||||
r.responseWriter.Header()[k] = v
|
||||
}
|
||||
|
||||
r.responseWriter.WriteHeader(code)
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range r.headerMap {
|
||||
r.responseWriter.Header()[k] = v
|
||||
}
|
||||
r.responseWriter.WriteHeader(r.code)
|
||||
r.headerSent = true
|
||||
}
|
||||
|
|
|
@ -3,8 +3,11 @@ package customerrors
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -181,6 +184,88 @@ func TestHandler(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// This test is an adapted version of net/http/httputil.Test1xxResponses test.
|
||||
func Test1xxResponses(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Add("Link", "</style.css>; rel=preload; as=style")
|
||||
h.Add("Link", "</script.js>; rel=preload; as=script")
|
||||
w.WriteHeader(http.StatusEarlyHints)
|
||||
|
||||
h.Add("Link", "</foo.js>; rel=preload; as=script")
|
||||
w.WriteHeader(http.StatusProcessing)
|
||||
|
||||
h.Add("User-Agent", "foobar")
|
||||
_, _ = w.Write([]byte("Hello"))
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
})
|
||||
|
||||
serviceBuilderMock := &mockServiceBuilder{handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprintln(w, "My error page.")
|
||||
})}
|
||||
|
||||
config := dynamic.ErrorPage{Service: "error", Query: "/", Status: []string{"200"}}
|
||||
|
||||
errorPageHandler, err := New(context.Background(), next, config, serviceBuilderMock, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
server := httptest.NewServer(errorPageHandler)
|
||||
t.Cleanup(server.Close)
|
||||
frontendClient := server.Client()
|
||||
|
||||
checkLinkHeaders := func(t *testing.T, expected, got []string) {
|
||||
t.Helper()
|
||||
|
||||
if len(expected) != len(got) {
|
||||
t.Errorf("Expected %d link headers; got %d", len(expected), len(got))
|
||||
}
|
||||
|
||||
for i := range expected {
|
||||
if i >= len(got) {
|
||||
t.Errorf("Expected %q link header; got nothing", expected[i])
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if expected[i] != got[i] {
|
||||
t.Errorf("Expected %q link header; got %q", expected[i], got[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var respCounter uint8
|
||||
trace := &httptrace.ClientTrace{
|
||||
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
||||
switch code {
|
||||
case http.StatusEarlyHints:
|
||||
checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"}, header["Link"])
|
||||
case http.StatusProcessing:
|
||||
checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script", "</foo.js>; rel=preload; as=script"}, header["Link"])
|
||||
default:
|
||||
t.Error("Unexpected 1xx response")
|
||||
}
|
||||
|
||||
respCounter++
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil)
|
||||
|
||||
res, err := frontendClient.Do(req)
|
||||
assert.Nil(t, err)
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if respCounter != 2 {
|
||||
t.Errorf("Expected 2 1xx responses; got %d", respCounter)
|
||||
}
|
||||
checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script", "</foo.js>; rel=preload; as=script"}, res.Header["Link"])
|
||||
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
assert.Equal(t, "My error page.\n", string(body))
|
||||
}
|
||||
|
||||
type mockServiceBuilder struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue