aboutsummaryrefslogtreecommitdiff
path: root/src/net/http/clientserver_test.go
diff options
context:
space:
mode:
authorKévin Dunglas <kevin@dunglas.fr>2022-05-17 16:05:20 +0000
committerDamien Neil <dneil@google.com>2022-05-17 16:09:37 +0000
commit770e0e584a98dfd5e8d0d00558085c339fda0ed7 (patch)
treef1285af51fef06879d720970b3e9090c13cd4921 /src/net/http/clientserver_test.go
parentcf7ec0fa098a46c3b75cc3d625f5d7528fe6e984 (diff)
downloadgo-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.go95
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)
+ }
+}