diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index fb18cb2c6f5b2e..7ceac31e8fca74 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -6730,3 +6730,36 @@ func testMaxBytesHandler(t *testing.T, maxSize, requestSize int64) { t.Errorf("expected echo of size %d; got %d", handlerN, buf.Len()) } } + +func TestEarlyHints(t *testing.T) { + ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { + h := w.Header() + h.Add("Link", "; rel=preload; as=style") + h.Add("Link", "; rel=preload; as=script") + w.WriteHeader(StatusEarlyHints) + + h.Add("Link", "; rel=preload; as=script") + w.WriteHeader(StatusEarlyHints) + + w.Write([]byte("stuff")) + })) + + got := ht.rawResponse("GET / HTTP/1.1\nHost: golang.org") + expected := "HTTP/1.1 103 Early Hints\r\nLink: ; rel=preload; as=style\r\nLink: ; rel=preload; as=script\r\n\r\nHTTP/1.1 103 Early Hints\r\nLink: ; rel=preload; as=style\r\nLink: ; rel=preload; as=script\r\nLink: ; rel=preload; as=script\r\n\r\nHTTP/1.1 200 OK\r\nLink: ; rel=preload; as=style\r\nLink: ; rel=preload; as=script\r\nLink: ; rel=preload; as=script\r\nDate: " // dynamic content expected + if !strings.Contains(got, expected) { + t.Errorf("unexpected response; got %q; should start by %q", got, expected) + } +} + +func TestProcessing(t *testing.T) { + ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { + w.WriteHeader(StatusProcessing) + w.Write([]byte("stuff")) + })) + + got := ht.rawResponse("GET / HTTP/1.1\nHost: golang.org") + expected := "HTTP/1.1 102 Processing\r\n\r\nHTTP/1.1 200 OK\r\nDate: " // dynamic content expected + if !strings.Contains(got, expected) { + t.Errorf("unexpected response; got %q; should start by %q", got, expected) + } +} diff --git a/src/net/http/server.go b/src/net/http/server.go index ffb742ba4ad2ea..03f30367446628 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -144,13 +144,20 @@ type ResponseWriter interface { // If WriteHeader is not called explicitly, the first call to Write // will trigger an implicit WriteHeader(http.StatusOK). // Thus explicit calls to WriteHeader are mainly used to - // send error codes. + // send error codes or informational responses. // // The provided code must be a valid HTTP 1xx-5xx status code. - // Only one header may be written. Go does not currently - // support sending user-defined 1xx informational headers, - // with the exception of 100-continue response header that the - // Server sends automatically when the Request.Body is read. + // Only one header with a status code not in the 1xx range may + // be written. + // + // Informational headers (status in the 1xx range) can be sent multiple + // times before sending the final header. + // + // If the passed status code is 103, the current content of the header + // map will be sent as early hints for the client. + // + // The server automatically sends the 100-continue response header + // when the Request.Body is read. WriteHeader(statusCode int) } @@ -1143,6 +1150,19 @@ func (w *response) WriteHeader(code int) { return } checkWriteHeaderCode(code) + + // Handle provisional headers, except 100 (continue) which is handled automatically + if code > 100 && code < 200 { + writeStatusLine(w.conn.bufw, w.req.ProtoAtLeast(1, 1), code, w.statusBuf[:]) + if code == 103 { + // Per RFC 8297 we must not clear the current header map + w.handlerHeader.Write(w.conn.bufw) + } + w.conn.bufw.Write(crlf) + + return + } + w.wroteHeader = true w.status = code