diff options
| author | Kévin Dunglas <kevin@dunglas.fr> | 2022-05-17 16:05:20 +0000 |
|---|---|---|
| committer | Damien Neil <dneil@google.com> | 2022-05-17 16:09:37 +0000 |
| commit | 770e0e584a98dfd5e8d0d00558085c339fda0ed7 (patch) | |
| tree | f1285af51fef06879d720970b3e9090c13cd4921 /src/net/http/clientserver_test.go | |
| parent | cf7ec0fa098a46c3b75cc3d625f5d7528fe6e984 (diff) | |
| download | go-770e0e584a98dfd5e8d0d00558085c339fda0ed7.tar.xz | |
net/http: allow sending 1xx responses
Currently, it's not possible to send informational responses such as
103 Early Hints or 102 Processing.
This patch allows calling WriteHeader() multiple times in order
to send informational responses before the final one.
If the status code is in the 1xx range, the current content of the header map
is also sent. Its content is not removed after the call to WriteHeader()
because the headers must also be included in the final response.
The Chrome and Fastly teams are starting a large-scale experiment to measure
the real-life impact of the 103 status code.
Using Early Hints is proposed as a (partial) alternative to Server Push,
which are going to be removed from Chrome:
https://groups.google.com/a/chromium.org/g/blink-dev/c/K3rYLvmQUBY/m/21anpFhxAQAJ
Being able to send this status code from servers implemented using Go would
help to see if implementing it in browsers is worth it.
Fixes #26089
Fixes #36734
Updates #26088
Change-Id: Ib7023c1892c35e8915d4305dd7f6373dbd00a19d
GitHub-Last-Rev: 06d749d3454aa35c177a50ce4a25715df21fd742
GitHub-Pull-Request: golang/go#42597
Reviewed-on: https://go-review.googlesource.com/c/go/+/269997
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Diffstat (limited to 'src/net/http/clientserver_test.go')
| -rw-r--r-- | src/net/http/clientserver_test.go | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/src/net/http/clientserver_test.go b/src/net/http/clientserver_test.go index 71b2a32cb4..3fc9fcf19d 100644 --- a/src/net/http/clientserver_test.go +++ b/src/net/http/clientserver_test.go @@ -9,6 +9,7 @@ package http_test import ( "bytes" "compress/gzip" + "context" "crypto/rand" "crypto/sha1" "crypto/tls" @@ -19,7 +20,9 @@ import ( "net" . "net/http" "net/http/httptest" + "net/http/httptrace" "net/http/httputil" + "net/textproto" "net/url" "os" "reflect" @@ -1616,3 +1619,95 @@ func testIdentityTransferEncoding(t *testing.T, h2 bool) { t.Errorf("got response body = %q; want %q", got, want) } } + +func TestEarlyHintsRequest_h1(t *testing.T) { testEarlyHintsRequest(t, h1Mode) } +func TestEarlyHintsRequest_h2(t *testing.T) { testEarlyHintsRequest(t, h2Mode) } +func testEarlyHintsRequest(t *testing.T, h2 bool) { + defer afterTest(t) + if h2 { + t.Skip("Waiting for H2 support to be merged: https://go-review.googlesource.com/c/net/+/406494") + } + + var wg sync.WaitGroup + wg.Add(1) + cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { + h := w.Header() + + h.Add("Content-Length", "123") // must be ignored + h.Add("Link", "</style.css>; rel=preload; as=style") + h.Add("Link", "</script.js>; rel=preload; as=script") + w.WriteHeader(StatusEarlyHints) + + wg.Wait() + + h.Add("Link", "</foo.js>; rel=preload; as=script") + w.WriteHeader(StatusEarlyHints) + + w.Write([]byte("Hello")) + })) + defer cst.close() + + checkLinkHeaders := func(t *testing.T, expected, got []string) { + t.Helper() + + if len(expected) != len(got) { + t.Errorf("got %d expected %d", len(got), len(expected)) + } + + for i := range expected { + if expected[i] != got[i] { + t.Errorf("got %q expected %q", got[i], expected[i]) + } + } + } + + checkExcludedHeaders := func(t *testing.T, header textproto.MIMEHeader) { + t.Helper() + + for _, h := range []string{"Content-Length", "Transfer-Encoding"} { + if v, ok := header[h]; ok { + t.Errorf("%s is %q; must not be sent", h, v) + } + } + } + + var respCounter uint8 + trace := &httptrace.ClientTrace{ + Got1xxResponse: func(code int, header textproto.MIMEHeader) error { + switch respCounter { + case 0: + checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"}, header["Link"]) + checkExcludedHeaders(t, header) + + wg.Done() + case 1: + checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script", "</foo.js>; rel=preload; as=script"}, header["Link"]) + checkExcludedHeaders(t, header) + + default: + t.Error("Unexpected 1xx response") + } + + respCounter++ + + return nil + }, + } + req, _ := NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), "GET", cst.ts.URL, nil) + + res, err := cst.c.Do(req) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + + checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script", "</foo.js>; rel=preload; as=script"}, res.Header["Link"]) + if cl := res.Header.Get("Content-Length"); cl != "123" { + t.Errorf("Content-Length is %q; want 123", cl) + } + + body, _ := io.ReadAll(res.Body) + if string(body) != "Hello" { + t.Errorf("Read body %q; want Hello", body) + } +} |
