Skip to content

Commit

Permalink
allow sending 1xx responses
Browse files Browse the repository at this point in the history
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.
It follows the patch for HTTP/1 (golang/go#42597) and HTTP/2 (golang/net#96).

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.
  • Loading branch information
dunglas committed Feb 18, 2021
1 parent bfad411 commit 11220d7
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 1 deletion.
5 changes: 4 additions & 1 deletion http3/response_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ func (w *responseWriter) WriteHeader(status int) {
if w.headerWritten {
return
}
w.headerWritten = true

if status < 100 || status >= 200 {
w.headerWritten = true
}
w.status = status

var headers bytes.Buffer
Expand Down
24 changes: 24 additions & 0 deletions http3/response_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,30 @@ var _ = Describe("Response Writer", func() {
Expect(fields).To(HaveKeyWithValue(":status", []string{"200"}))
})

It("allows calling WriteHeader() several times when using the 103 status code", func() {
rw.Header().Add("Link", "</style.css>; rel=preload; as=style")
rw.Header().Add("Link", "</script.js>; rel=preload; as=script")
rw.WriteHeader(http.StatusEarlyHints)

n, err := rw.Write([]byte("foobar"))
Expect(n).To(Equal(6))
Expect(err).ToNot(HaveOccurred())

// Early Hints must have been received
fields := decodeHeader(strBuf)
Expect(fields).To(HaveLen(2))
Expect(fields).To(HaveKeyWithValue(":status", []string{"103"}))
Expect(fields).To(HaveKeyWithValue("link", []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"}))

// According to the spec, headers sent in the informational response must also be included in the final response
fields = decodeHeader(strBuf)
Expect(fields).To(HaveLen(2))
Expect(fields).To(HaveKeyWithValue(":status", []string{"200"}))
Expect(fields).To(HaveKeyWithValue("link", []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"}))

Expect(getData(strBuf)).To(Equal([]byte("foobar")))
})

It("doesn't allow writes if the status code doesn't allow a body", func() {
rw.WriteHeader(304)
n, err := rw.Write([]byte("foobar"))
Expand Down

0 comments on commit 11220d7

Please sign in to comment.