]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: update bundled copy of http2, enable TestTrailersServerToClient tests
authorBrad Fitzpatrick <bradfitz@golang.org>
Thu, 17 Dec 2015 00:31:56 +0000 (00:31 +0000)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 17 Dec 2015 01:29:43 +0000 (01:29 +0000)
This CL updates the bundled copy of x/net/http2 to include
https://golang.org/cl/17930 and enables the previously-skipped tests
TestTrailersServerToClient_h2 and TestTrailersServerToClient_Flush_h2.

It also updates the docs on http.Response.Trailer to describe how to
use it. No change in rules. Just documenting the old unwritten rules.
(there were tests locking in the behavior, and misc docs and examples
scattered about, but not on http.Response.Trailer itself)

Updates #13557

Change-Id: I6261d439f6c0d17654a1a7928790e8ffed16df6c
Reviewed-on: https://go-review.googlesource.com/17931
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Blake Mizerany <blake.mizerany@gmail.com>
src/net/http/clientserver_test.go
src/net/http/h2_bundle.go
src/net/http/response.go

index 09dbceb99d4d677fc16c883092d3443d87a94f02..00d1c58cf0e24b390d6e29308ded8a7f4ae2d7c4 100644 (file)
@@ -526,16 +526,10 @@ func testTrailersClientToServer(t *testing.T, h2 bool) {
 }
 
 // Tests that servers send trailers to a client and that the client can read them.
-func TestTrailersServerToClient_h1(t *testing.T) { testTrailersServerToClient(t, h1Mode, false) }
-func TestTrailersServerToClient_h2(t *testing.T) {
-       t.Skip("skipping in http2 mode; golang.org/issue/13557")
-       testTrailersServerToClient(t, h2Mode, false)
-}
+func TestTrailersServerToClient_h1(t *testing.T)       { testTrailersServerToClient(t, h1Mode, false) }
+func TestTrailersServerToClient_h2(t *testing.T)       { testTrailersServerToClient(t, h2Mode, false) }
 func TestTrailersServerToClient_Flush_h1(t *testing.T) { testTrailersServerToClient(t, h1Mode, true) }
-func TestTrailersServerToClient_Flush_h2(t *testing.T) {
-       t.Skip("skipping in http2 mode; golang.org/issue/13557")
-       testTrailersServerToClient(t, h2Mode, true)
-}
+func TestTrailersServerToClient_Flush_h2(t *testing.T) { testTrailersServerToClient(t, h2Mode, true) }
 
 func testTrailersServerToClient(t *testing.T, h2, flush bool) {
        defer afterTest(t)
@@ -564,11 +558,26 @@ func testTrailersServerToClient(t *testing.T, h2, flush bool) {
                t.Fatal(err)
        }
 
-       delete(res.Header, "Date") // irrelevant for test
-       if got, want := res.Header, (Header{
+       wantHeader := Header{
                "Content-Type": {"text/plain; charset=utf-8"},
-       }); !reflect.DeepEqual(got, want) {
-               t.Errorf("Header = %v; want %v", got, want)
+       }
+       wantLen := -1
+       if h2 && !flush {
+               // In HTTP/1.1, any use of trailers forces HTTP/1.1
+               // chunking and a flush at the first write. That's
+               // unnecessary with HTTP/2's framing, so the server
+               // is able to calculate the length while still sending
+               // trailers afterwards.
+               wantLen = len(body)
+               wantHeader["Content-Length"] = []string{fmt.Sprint(wantLen)}
+       }
+       if res.ContentLength != int64(wantLen) {
+               t.Errorf("ContentLength = %v; want %v", res.ContentLength, wantLen)
+       }
+
+       delete(res.Header, "Date") // irrelevant for test
+       if !reflect.DeepEqual(res.Header, wantHeader) {
+               t.Errorf("Header = %v; want %v", res.Header, wantHeader)
        }
 
        if got, want := res.Trailer, (Header{
index 89a8fd094a3478f16d58e1c8896f051fed2a8dce..b793f18416c7f7a421dd27f7e3666e65ae94d461 100644 (file)
@@ -28,6 +28,7 @@ import (
        "io/ioutil"
        "log"
        "net"
+       "net/textproto"
        "net/url"
        "os"
        "runtime"
@@ -3706,12 +3707,13 @@ type http2responseWriterState struct {
        bw *bufio.Writer // writing to a chunkWriter{this *responseWriterState}
 
        // mutated by http.Handler goroutine:
-       handlerHeader Header // nil until called
-       snapHeader    Header // snapshot of handlerHeader at WriteHeader time
-       status        int    // status code passed to WriteHeader
-       wroteHeader   bool   // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet.
-       sentHeader    bool   // have we sent the header frame?
-       handlerDone   bool   // handler has finished
+       handlerHeader Header   // nil until called
+       snapHeader    Header   // snapshot of handlerHeader at WriteHeader time
+       trailers      []string // set in writeChunk
+       status        int      // status code passed to WriteHeader
+       wroteHeader   bool     // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet.
+       sentHeader    bool     // have we sent the header frame?
+       handlerDone   bool     // handler has finished
 
        sentContentLen int64 // non-zero if handler set a Content-Length header
        wroteBytes     int64
@@ -3724,6 +3726,21 @@ type http2chunkWriter struct{ rws *http2responseWriterState }
 
 func (cw http2chunkWriter) Write(p []byte) (n int, err error) { return cw.rws.writeChunk(p) }
 
+func (rws *http2responseWriterState) hasTrailers() bool { return len(rws.trailers) != 0 }
+
+// declareTrailer is called for each Trailer header when the
+// response header is written. It notes that a header will need to be
+// written in the trailers at the end of the response.
+func (rws *http2responseWriterState) declareTrailer(k string) {
+       k = CanonicalHeaderKey(k)
+       switch k {
+       case "Transfer-Encoding", "Content-Length", "Trailer":
+
+               return
+       }
+       rws.trailers = append(rws.trailers, k)
+}
+
 // writeChunk writes chunks from the bufio.Writer. But because
 // bufio.Writer may bypass its chunking, sometimes p may be
 // arbitrarily large.
@@ -3734,6 +3751,7 @@ func (rws *http2responseWriterState) writeChunk(p []byte) (n int, err error) {
        if !rws.wroteHeader {
                rws.writeHeader(200)
        }
+
        isHeadResp := rws.req.Method == "HEAD"
        if !rws.sentHeader {
                rws.sentHeader = true
@@ -3759,7 +3777,12 @@ func (rws *http2responseWriterState) writeChunk(p []byte) (n int, err error) {
 
                        date = time.Now().UTC().Format(TimeFormat)
                }
-               endStream := (rws.handlerDone && len(p) == 0) || isHeadResp
+
+               for _, v := range rws.snapHeader["Trailer"] {
+                       http2foreachHeaderElement(v, rws.declareTrailer)
+               }
+
+               endStream := (rws.handlerDone && !rws.hasTrailers() && len(p) == 0) || isHeadResp
                err = rws.conn.writeHeaders(rws.stream, &http2writeResHeaders{
                        streamID:      rws.stream.id,
                        httpResCode:   rws.status,
@@ -3783,8 +3806,22 @@ func (rws *http2responseWriterState) writeChunk(p []byte) (n int, err error) {
                return 0, nil
        }
 
-       if err := rws.conn.writeDataFromHandler(rws.stream, p, rws.handlerDone); err != nil {
-               return 0, err
+       endStream := rws.handlerDone && !rws.hasTrailers()
+       if len(p) > 0 || endStream {
+
+               if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil {
+                       return 0, err
+               }
+       }
+
+       if rws.handlerDone && rws.hasTrailers() {
+               err = rws.conn.writeHeaders(rws.stream, &http2writeResHeaders{
+                       streamID:  rws.stream.id,
+                       h:         rws.handlerHeader,
+                       trailers:  rws.trailers,
+                       endStream: true,
+               })
+               return len(p), err
        }
        return len(p), nil
 }
@@ -3912,6 +3949,24 @@ func (w *http2responseWriter) handlerDone() {
        http2responseWriterStatePool.Put(rws)
 }
 
+// foreachHeaderElement splits v according to the "#rule" construction
+// in RFC 2616 section 2.1 and calls fn for each non-empty element.
+func http2foreachHeaderElement(v string, fn func(string)) {
+       v = textproto.TrimString(v)
+       if v == "" {
+               return
+       }
+       if !strings.Contains(v, ",") {
+               fn(v)
+               return
+       }
+       for _, f := range strings.Split(v, ",") {
+               if f = textproto.TrimString(f); f != "" {
+                       fn(f)
+               }
+       }
+}
+
 const (
        // transportDefaultConnFlow is how many connection-level flow control
        // tokens we give the server at start-up, past the default 64k.
@@ -4045,6 +4100,13 @@ type http2clientStream struct {
 
        peerReset chan struct{} // closed on peer reset
        resetErr  error         // populated before peerReset is closed
+
+       // owned by clientConnReadLoop:
+       headersDone  bool // got HEADERS w/ END_HEADERS
+       trailersDone bool // got second HEADERS frame w/ END_HEADERS
+
+       trailer    Header // accumulated trailers
+       resTrailer Header // client's Response.Trailer
 }
 
 // awaitRequestCancel runs in its own goroutine and waits for the user's
@@ -4753,9 +4815,14 @@ func (rl *http2clientConnReadLoop) processHeaderBlockFragment(frag []byte, strea
 
                return nil
        }
+       if cs.headersDone {
+               rl.hdec.SetEmitFunc(cs.onNewTrailerField)
+       } else {
+               rl.hdec.SetEmitFunc(rl.onNewHeaderField)
+       }
        _, err := rl.hdec.Write(frag)
        if err != nil {
-               return err
+               return http2ConnectionError(http2ErrCodeCompression)
        }
        if !headersEnded {
                rl.continueStreamID = cs.ID
@@ -4764,6 +4831,23 @@ func (rl *http2clientConnReadLoop) processHeaderBlockFragment(frag []byte, strea
 
        rl.continueStreamID = 0
 
+       if !cs.headersDone {
+               cs.headersDone = true
+       } else {
+
+               if cs.trailersDone {
+
+                       return http2ConnectionError(http2ErrCodeProtocol)
+               }
+               cs.trailersDone = true
+               if !streamEnded {
+
+                       return http2ConnectionError(http2ErrCodeProtocol)
+               }
+               rl.endStream(cs)
+               return nil
+       }
+
        if rl.reqMalformed != nil {
                cs.resc <- http2resAndError{err: rl.reqMalformed}
                rl.cc.writeStreamReset(cs.ID, http2ErrCodeProtocol, rl.reqMalformed)
@@ -4802,6 +4886,7 @@ func (rl *http2clientConnReadLoop) processHeaderBlockFragment(frag []byte, strea
                }
        }
 
+       cs.resTrailer = res.Trailer
        rl.activeRes[cs.ID] = cs
        cs.resc <- http2resAndError{res: res}
        rl.nextRes = nil
@@ -4907,12 +4992,23 @@ func (rl *http2clientConnReadLoop) processData(f *http2DataFrame) error {
        }
 
        if f.StreamEnded() {
-               cs.bufPipe.CloseWithError(io.EOF)
-               delete(rl.activeRes, cs.ID)
+               rl.endStream(cs)
        }
        return nil
 }
 
+func (rl *http2clientConnReadLoop) endStream(cs *http2clientStream) {
+
+       cs.bufPipe.closeWithErrorAndCode(io.EOF, cs.copyTrailers)
+       delete(rl.activeRes, cs.ID)
+}
+
+func (cs *http2clientStream) copyTrailers() {
+       for k, vv := range cs.trailer {
+               cs.resTrailer[k] = vv
+       }
+}
+
 func (rl *http2clientConnReadLoop) processGoAway(f *http2GoAwayFrame) error {
        cc := rl.cc
        cc.t.connPool().MarkDead(cc)
@@ -5019,6 +5115,7 @@ func (rl *http2clientConnReadLoop) onNewHeaderField(f hpack.HeaderField) {
        if http2VerboseLogs {
                cc.logf("Header field: %+v", f)
        }
+
        isPseudo := strings.HasPrefix(f.Name, ":")
        if isPseudo {
                if rl.sawRegHeader {
@@ -5040,7 +5137,37 @@ func (rl *http2clientConnReadLoop) onNewHeaderField(f hpack.HeaderField) {
                }
        } else {
                rl.sawRegHeader = true
-               rl.nextRes.Header.Add(CanonicalHeaderKey(f.Name), f.Value)
+               key := CanonicalHeaderKey(f.Name)
+               if key == "Trailer" {
+                       t := rl.nextRes.Trailer
+                       if t == nil {
+                               t = make(Header)
+                               rl.nextRes.Trailer = t
+                       }
+                       http2foreachHeaderElement(f.Value, func(v string) {
+                               t[CanonicalHeaderKey(v)] = nil
+                       })
+               } else {
+                       rl.nextRes.Header.Add(key, f.Value)
+               }
+       }
+}
+
+func (cs *http2clientStream) onNewTrailerField(f hpack.HeaderField) {
+       isPseudo := strings.HasPrefix(f.Name, ":")
+       if isPseudo {
+
+               return
+       }
+       key := CanonicalHeaderKey(f.Name)
+       if _, ok := cs.resTrailer[key]; ok {
+               if cs.trailer == nil {
+                       cs.trailer = make(Header)
+               }
+               const tooBig = 1000 // TODO: arbitrary; use max header list size limits
+               if cur := cs.trailer[key]; len(cur) < tooBig {
+                       cs.trailer[key] = append(cur, f.Value)
+               }
        }
 }
 
@@ -5205,11 +5332,12 @@ func (http2writeSettingsAck) writeFrame(ctx http2writeContext) error {
 }
 
 // writeResHeaders is a request to write a HEADERS and 0+ CONTINUATION frames
-// for HTTP response headers from a server handler.
+// for HTTP response headers or trailers from a server handler.
 type http2writeResHeaders struct {
        streamID    uint32
-       httpResCode int
-       h           Header // may be nil
+       httpResCode int      // 0 means no ":status" line
+       h           Header   // may be nil
+       trailers    []string // if non-nil, which keys of h to write. nil means all.
        endStream   bool
 
        date          string
@@ -5220,25 +5348,16 @@ type http2writeResHeaders struct {
 func (w *http2writeResHeaders) writeFrame(ctx http2writeContext) error {
        enc, buf := ctx.HeaderEncoder()
        buf.Reset()
-       enc.WriteField(hpack.HeaderField{Name: ":status", Value: http2httpCodeString(w.httpResCode)})
 
-       keys := make([]string, 0, len(w.h))
-       for k := range w.h {
-               keys = append(keys, k)
+       if w.httpResCode != 0 {
+               enc.WriteField(hpack.HeaderField{
+                       Name:  ":status",
+                       Value: http2httpCodeString(w.httpResCode),
+               })
        }
-       sort.Strings(keys)
-       for _, k := range keys {
-               vv := w.h[k]
-               k = http2lowerHeader(k)
-               isTE := k == "transfer-encoding"
-               for _, v := range vv {
 
-                       if isTE && v != "trailers" {
-                               continue
-                       }
-                       enc.WriteField(hpack.HeaderField{Name: k, Value: v})
-               }
-       }
+       http2encodeHeaders(enc, w.h, w.trailers)
+
        if w.contentType != "" {
                enc.WriteField(hpack.HeaderField{Name: "content-type", Value: w.contentType})
        }
@@ -5250,7 +5369,7 @@ func (w *http2writeResHeaders) writeFrame(ctx http2writeContext) error {
        }
 
        headerBlock := buf.Bytes()
-       if len(headerBlock) == 0 {
+       if len(headerBlock) == 0 && w.trailers == nil {
                panic("unexpected empty hpack")
        }
 
@@ -5314,6 +5433,29 @@ func (wu http2writeWindowUpdate) writeFrame(ctx http2writeContext) error {
        return ctx.Framer().WriteWindowUpdate(wu.streamID, wu.n)
 }
 
+func http2encodeHeaders(enc *hpack.Encoder, h Header, keys []string) {
+
+       if keys == nil {
+               keys = make([]string, 0, len(h))
+               for k := range h {
+                       keys = append(keys, k)
+               }
+               sort.Strings(keys)
+       }
+       for _, k := range keys {
+               vv := h[k]
+               k = http2lowerHeader(k)
+               isTE := k == "transfer-encoding"
+               for _, v := range vv {
+
+                       if isTE && v != "trailers" {
+                               continue
+                       }
+                       enc.WriteField(hpack.HeaderField{Name: k, Value: v})
+               }
+       }
+}
+
 // frameWriteMsg is a request to write a frame.
 type http2frameWriteMsg struct {
        // write is the interface value that does the writing, once the
index 76b8538524476560e282a172d87ffb7ebfd61f5b..0e39ed3a3a299c1930d83345825efe0040e93b74 100644 (file)
@@ -74,6 +74,12 @@ type Response struct {
 
        // Trailer maps trailer keys to values, in the same
        // format as the header.
+       //
+       // The Trailer initially contains only the server's
+       // pre-declared trailer keys, but with nil values. Trailer
+       // must not be access concurrently with Read calls on the
+       // Body. After Body.Read has returned io.EOF, Trailer can be read
+       // again and will contain any values sent by the server.
        Trailer Header
 
        // The Request that was sent to obtain this Response.