Skip to content

Commit 33505cb

Browse files
committed
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. In conformance with RFC 8297, if the status code is 103 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.
1 parent 30ba798 commit 33505cb

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

src/net/http/serve_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6448,3 +6448,36 @@ func BenchmarkResponseStatusLine(b *testing.B) {
64486448
}
64496449
})
64506450
}
6451+
6452+
func TestEarlyHints(t *testing.T) {
6453+
ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) {
6454+
h := w.Header()
6455+
h.Add("Link", "</style.css>; rel=preload; as=style")
6456+
h.Add("Link", "</script.js>; rel=preload; as=script")
6457+
w.WriteHeader(StatusEarlyHints)
6458+
6459+
h.Add("Link", "</foo.js>; rel=preload; as=script")
6460+
w.WriteHeader(StatusEarlyHints)
6461+
6462+
w.Write([]byte("stuff"))
6463+
}))
6464+
6465+
got := ht.rawResponse("GET / HTTP/1.1\nHost: golang.org")
6466+
expected := "HTTP/1.1 103 Early Hints\r\nLink: </style.css>; rel=preload; as=style\r\nLink: </script.js>; rel=preload; as=script\r\n\r\nHTTP/1.1 103 Early Hints\r\nLink: </style.css>; rel=preload; as=style\r\nLink: </script.js>; rel=preload; as=script\r\nLink: </foo.js>; rel=preload; as=script\r\n\r\nHTTP/1.1 200 OK\r\nLink: </style.css>; rel=preload; as=style\r\nLink: </script.js>; rel=preload; as=script\r\nLink: </foo.js>; rel=preload; as=script\r\nDate: " // dynamic content expected
6467+
if !strings.Contains(got, expected) {
6468+
t.Errorf("unexpected response; got %q; should start by %q", got, expected)
6469+
}
6470+
}
6471+
6472+
func TestProcessing(t *testing.T) {
6473+
ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) {
6474+
w.WriteHeader(StatusProcessing)
6475+
w.Write([]byte("stuff"))
6476+
}))
6477+
6478+
got := ht.rawResponse("GET / HTTP/1.1\nHost: golang.org")
6479+
expected := "HTTP/1.1 102 Processing\r\n\r\nHTTP/1.1 200 OK\r\nDate: " // dynamic content expected
6480+
if !strings.Contains(got, expected) {
6481+
t.Errorf("unexpected response; got %q; should start by %q", got, expected)
6482+
}
6483+
}

src/net/http/server.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,20 @@ type ResponseWriter interface {
144144
// If WriteHeader is not called explicitly, the first call to Write
145145
// will trigger an implicit WriteHeader(http.StatusOK).
146146
// Thus explicit calls to WriteHeader are mainly used to
147-
// send error codes.
147+
// send error codes or informational responses.
148148
//
149149
// The provided code must be a valid HTTP 1xx-5xx status code.
150-
// Only one header may be written. Go does not currently
151-
// support sending user-defined 1xx informational headers,
152-
// with the exception of 100-continue response header that the
153-
// Server sends automatically when the Request.Body is read.
150+
// Only one header with a status code not in the 1xx range may
151+
// be written.
152+
//
153+
// Informational headers (status in the 1xx range) can be sent multiple
154+
// times before sending the final header.
155+
//
156+
// If the passed status code is 103, the current content of the header
157+
// map will be sent as early hints for the client.
158+
//
159+
// The server automatically sends the 100-continue response header
160+
// when the Request.Body is read.
154161
WriteHeader(statusCode int)
155162
}
156163

@@ -1144,6 +1151,19 @@ func (w *response) WriteHeader(code int) {
11441151
return
11451152
}
11461153
checkWriteHeaderCode(code)
1154+
1155+
// Handle provisional headers, except 100 (continue) which is handled automatically
1156+
if code > 100 && code < 200 {
1157+
writeStatusLine(w.conn.bufw, w.req.ProtoAtLeast(1, 1), code, w.statusBuf[:])
1158+
if code == 103 {
1159+
// Per RFC 8297 we must not clear the current header map
1160+
w.handlerHeader.Write(w.conn.bufw)
1161+
}
1162+
w.conn.bufw.Write(crlf)
1163+
1164+
return
1165+
}
1166+
11471167
w.wroteHeader = true
11481168
w.status = code
11491169

0 commit comments

Comments
 (0)