Commit bef4cb47 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: buffer before chunking

This introduces a buffer between writing from a handler and
writing chunks.  Further, it delays writing the header until
the first full chunk is ready.  In the case where the first
full chunk is also the final chunk (for small responses), that
means we can also compute a Content-Length, which is a nice
side effect for certain benchmarks.

Fixes #2357

R=golang-dev, dave, minux.ma, rsc, adg, balasanjay
CC=golang-dev
https://golang.org/cl/6964043
parent 98259b92
......@@ -54,6 +54,16 @@ func (h Header) Write(w io.Writer) error {
return h.WriteSubset(w, nil)
}
func (h Header) clone() Header {
h2 := make(Header, len(h))
for k, vv := range h {
vv2 := make([]string, len(vv))
copy(vv2, vv)
h2[k] = vv2
}
return h2
}
var timeFormats = []string{
TimeFormat,
time.RFC850,
......
......@@ -484,6 +484,7 @@ func TestChunkedResponseHeaders(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "intentional gibberish") // we check that this is deleted
w.(Flusher).Flush()
fmt.Fprintf(w, "I am a chunked response.")
}))
defer ts.Close()
......@@ -764,6 +765,7 @@ func TestServerUnreadRequestBodyLittle(t *testing.T) {
t.Errorf("on request, read buffer length is %d; expected about 100 KB", conn.readBuf.Len())
}
rw.WriteHeader(200)
rw.(Flusher).Flush()
if g, e := conn.readBuf.Len(), 0; g != e {
t.Errorf("after WriteHeader, read buffer length is %d; want %d", g, e)
}
......@@ -796,14 +798,16 @@ func TestServerUnreadRequestBodyLarge(t *testing.T) {
t.Errorf("on request, read buffer length is %d; expected about 1MB", conn.readBuf.Len())
}
rw.WriteHeader(200)
rw.(Flusher).Flush()
if conn.readBuf.Len() < len(body)/2 {
t.Errorf("post-WriteHeader, read buffer length is %d; expected about 1MB", conn.readBuf.Len())
}
if c := rw.Header().Get("Connection"); c != "close" {
t.Errorf(`Connection header = %q; want "close"`, c)
}
}))
<-done
if res := conn.writeBuf.String(); !strings.Contains(res, "Connection: close") {
t.Errorf("Expected a Connection: close header; got response: %s", res)
}
}
func TestTimeoutHandler(t *testing.T) {
......@@ -1144,17 +1148,13 @@ func TestClientWriteShutdown(t *testing.T) {
// Tests that chunked server responses that write 1 byte at a time are
// buffered before chunk headers are added, not after chunk headers.
func TestServerBufferedChunking(t *testing.T) {
if true {
t.Logf("Skipping known broken test; see Issue 2357")
return
}
conn := new(testConn)
conn.readBuf.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
done := make(chan bool)
ls := &oneConnListener{conn}
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
defer close(done)
rw.Header().Set("Content-Type", "text/plain") // prevent sniffing, which buffers
rw.(Flusher).Flush() // force the Header to be sent, in chunking mode, not counting the length
rw.Write([]byte{'x'})
rw.Write([]byte{'y'})
rw.Write([]byte{'z'})
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment