Skip to content

Commit 27f1598

Browse files
committed
x/net/http2: 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. Updates #42597.
1 parent fe4d628 commit 27f1598

File tree

2 files changed

+119
-8
lines changed

2 files changed

+119
-8
lines changed

http2/server.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,8 +2643,7 @@ func checkWriteHeaderCode(code int) {
26432643
// Issue 22880: require valid WriteHeader status codes.
26442644
// For now we only enforce that it's three digits.
26452645
// In the future we might block things over 599 (600 and above aren't defined
2646-
// at http://httpwg.org/specs/rfc7231.html#status.codes)
2647-
// and we might block under 200 (once we have more mature 1xx support).
2646+
// at http://httpwg.org/specs/rfc7231.html#status.codes).
26482647
// But for now any three digits.
26492648
//
26502649
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's
@@ -2665,13 +2664,36 @@ func (w *responseWriter) WriteHeader(code int) {
26652664
}
26662665

26672666
func (rws *responseWriterState) writeHeader(code int) {
2668-
if !rws.wroteHeader {
2669-
checkWriteHeaderCode(code)
2670-
rws.wroteHeader = true
2671-
rws.status = code
2672-
if len(rws.handlerHeader) > 0 {
2673-
rws.snapHeader = cloneHeader(rws.handlerHeader)
2667+
if rws.wroteHeader {
2668+
return
2669+
}
2670+
2671+
checkWriteHeaderCode(code)
2672+
2673+
// Handle informational headers, except 100 (Continue) which is handled automatically
2674+
if code > 100 && code < 200 {
2675+
var h http.Header
2676+
if code == 103 {
2677+
// Per RFC 8297 we must not clear the current header map
2678+
h = rws.handlerHeader
2679+
}
2680+
2681+
if rws.conn.writeHeaders(rws.stream, &writeResHeaders{
2682+
streamID: rws.stream.id,
2683+
httpResCode: code,
2684+
h: h,
2685+
endStream: rws.handlerDone && !rws.hasTrailers(),
2686+
}) != nil {
2687+
rws.dirty = true
26742688
}
2689+
2690+
return
2691+
}
2692+
2693+
rws.wroteHeader = true
2694+
rws.status = code
2695+
if len(rws.handlerHeader) > 0 {
2696+
rws.snapHeader = cloneHeader(rws.handlerHeader)
26752697
}
26762698
}
26772699

http2/server_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4367,3 +4367,92 @@ func TestNoErrorLoggedOnPostAfterGOAWAY(t *testing.T) {
43674367
t.Error("got protocol error")
43684368
}
43694369
}
4370+
4371+
func TestServerSendsProcessing(t *testing.T) {
4372+
testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
4373+
w.WriteHeader(http.StatusProcessing)
4374+
w.Write([]byte("stuff"))
4375+
4376+
return nil
4377+
}, func(st *serverTester) {
4378+
getSlash(st)
4379+
hf := st.wantHeaders()
4380+
goth := st.decodeHeader(hf.HeaderBlockFragment())
4381+
wanth := [][2]string{
4382+
{":status", "102"},
4383+
}
4384+
4385+
if !reflect.DeepEqual(goth, wanth) {
4386+
t.Errorf("Got = %q; want %q", goth, wanth)
4387+
}
4388+
4389+
hf = st.wantHeaders()
4390+
goth = st.decodeHeader(hf.HeaderBlockFragment())
4391+
wanth = [][2]string{
4392+
{":status", "200"},
4393+
{"content-type", "text/plain; charset=utf-8"},
4394+
{"content-length", "5"},
4395+
}
4396+
4397+
if !reflect.DeepEqual(goth, wanth) {
4398+
t.Errorf("Got = %q; want %q", goth, wanth)
4399+
}
4400+
})
4401+
}
4402+
4403+
func TestServerSendsEarlyHints(t *testing.T) {
4404+
testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
4405+
h := w.Header()
4406+
h.Add("Link", "</style.css>; rel=preload; as=style")
4407+
h.Add("Link", "</script.js>; rel=preload; as=script")
4408+
w.WriteHeader(http.StatusEarlyHints)
4409+
4410+
h.Add("Link", "</foo.js>; rel=preload; as=script")
4411+
w.WriteHeader(http.StatusEarlyHints)
4412+
4413+
w.Write([]byte("stuff"))
4414+
4415+
return nil
4416+
}, func(st *serverTester) {
4417+
getSlash(st)
4418+
hf := st.wantHeaders()
4419+
goth := st.decodeHeader(hf.HeaderBlockFragment())
4420+
wanth := [][2]string{
4421+
{":status", "103"},
4422+
{"link", "</style.css>; rel=preload; as=style"},
4423+
{"link", "</script.js>; rel=preload; as=script"},
4424+
}
4425+
4426+
if !reflect.DeepEqual(goth, wanth) {
4427+
t.Errorf("Got = %q; want %q", goth, wanth)
4428+
}
4429+
4430+
hf = st.wantHeaders()
4431+
goth = st.decodeHeader(hf.HeaderBlockFragment())
4432+
wanth = [][2]string{
4433+
{":status", "103"},
4434+
{"link", "</style.css>; rel=preload; as=style"},
4435+
{"link", "</script.js>; rel=preload; as=script"},
4436+
{"link", "</foo.js>; rel=preload; as=script"},
4437+
}
4438+
4439+
if !reflect.DeepEqual(goth, wanth) {
4440+
t.Errorf("Got = %q; want %q", goth, wanth)
4441+
}
4442+
4443+
hf = st.wantHeaders()
4444+
goth = st.decodeHeader(hf.HeaderBlockFragment())
4445+
wanth = [][2]string{
4446+
{":status", "200"},
4447+
{"link", "</style.css>; rel=preload; as=style"},
4448+
{"link", "</script.js>; rel=preload; as=script"},
4449+
{"link", "</foo.js>; rel=preload; as=script"},
4450+
{"content-type", "text/plain; charset=utf-8"},
4451+
{"content-length", "5"},
4452+
}
4453+
4454+
if !reflect.DeepEqual(goth, wanth) {
4455+
t.Errorf("Got = %q; want %q", goth, wanth)
4456+
}
4457+
})
4458+
}

0 commit comments

Comments
 (0)