diff --git a/bpf/instructions_test.go b/bpf/instructions_test.go index 69b25c5415..f5111c66fe 100644 --- a/bpf/instructions_test.go +++ b/bpf/instructions_test.go @@ -6,7 +6,7 @@ package bpf import ( "fmt" - "io/ioutil" + "os" "reflect" "strconv" "strings" @@ -98,7 +98,7 @@ func TestInterop(t *testing.T) { } t.Logf("Assembled program is %d instructions long", len(out)) - bs, err := ioutil.ReadFile(allInstructionsExpected) + bs, err := os.ReadFile(allInstructionsExpected) if err != nil { t.Fatalf("reading %s: %s", allInstructionsExpected, err) } diff --git a/context/ctxhttp/ctxhttp_test.go b/context/ctxhttp/ctxhttp_test.go index d585f117f0..ace33f2002 100644 --- a/context/ctxhttp/ctxhttp_test.go +++ b/context/ctxhttp/ctxhttp_test.go @@ -9,7 +9,6 @@ package ctxhttp import ( "context" "io" - "io/ioutil" "net/http" "net/http/httptest" "testing" @@ -49,7 +48,7 @@ func TestNoTimeout(t *testing.T) { t.Fatal(err) } defer res.Body.Close() - slurp, err := ioutil.ReadAll(res.Body) + slurp, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } @@ -102,7 +101,7 @@ func TestCancelAfterHangingRequest(t *testing.T) { done := make(chan struct{}) go func() { - b, err := ioutil.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) if len(b) != 0 || err == nil { t.Errorf(`Read got (%q, %v); want ("", error)`, b, err) } diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go index 2555305980..1fa93e63ad 100644 --- a/dns/dnsmessage/message_test.go +++ b/dns/dnsmessage/message_test.go @@ -7,7 +7,7 @@ package dnsmessage import ( "bytes" "fmt" - "io/ioutil" + "os" "path/filepath" "reflect" "strings" @@ -1611,7 +1611,7 @@ func TestNoFmt(t *testing.T) { // Could use something complex like go/build or x/tools/go/packages, // but there's no reason for "fmt" to appear (in quotes) in the source // otherwise, so just use a simple substring search. - data, err := ioutil.ReadFile(file) + data, err := os.ReadFile(file) if err != nil { t.Fatal(err) } diff --git a/go.mod b/go.mod index 57e8a02529..3556731605 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module golang.org/x/net go 1.18 require ( - golang.org/x/crypto v0.23.0 - golang.org/x/sys v0.20.0 - golang.org/x/term v0.20.0 - golang.org/x/text v0.15.0 + golang.org/x/crypto v0.24.0 + golang.org/x/sys v0.21.0 + golang.org/x/term v0.21.0 + golang.org/x/text v0.16.0 ) diff --git a/go.sum b/go.sum index 50575cf8ca..f7e8785a81 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= diff --git a/html/atom/gen.go b/html/atom/gen.go index 5d85c604d1..1e249d163c 100644 --- a/html/atom/gen.go +++ b/html/atom/gen.go @@ -14,7 +14,6 @@ import ( "flag" "fmt" "go/format" - "io/ioutil" "math/rand" "os" "sort" @@ -48,7 +47,7 @@ func genFile(name string, buf *bytes.Buffer) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - if err := ioutil.WriteFile(name, b, 0644); err != nil { + if err := os.WriteFile(name, b, 0644); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } diff --git a/html/charset/charset_test.go b/html/charset/charset_test.go index b71eb43f70..c2f62445c7 100644 --- a/html/charset/charset_test.go +++ b/html/charset/charset_test.go @@ -7,7 +7,8 @@ package charset import ( "bytes" "encoding/xml" - "io/ioutil" + "io" + "os" "runtime" "strings" "testing" @@ -17,7 +18,7 @@ import ( func transformString(t transform.Transformer, s string) (string, error) { r := transform.NewReader(strings.NewReader(s), t) - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) return string(b), err } @@ -142,7 +143,7 @@ func TestSniff(t *testing.T) { } for _, tc := range sniffTestCases { - content, err := ioutil.ReadFile("testdata/" + tc.filename) + content, err := os.ReadFile("testdata/" + tc.filename) if err != nil { t.Errorf("%s: error reading file: %v", tc.filename, err) continue @@ -163,7 +164,7 @@ func TestReader(t *testing.T) { } for _, tc := range sniffTestCases { - content, err := ioutil.ReadFile("testdata/" + tc.filename) + content, err := os.ReadFile("testdata/" + tc.filename) if err != nil { t.Errorf("%s: error reading file: %v", tc.filename, err) continue @@ -175,14 +176,14 @@ func TestReader(t *testing.T) { continue } - got, err := ioutil.ReadAll(r) + got, err := io.ReadAll(r) if err != nil { t.Errorf("%s: error reading from charset.NewReader: %v", tc.filename, err) continue } e, _ := Lookup(tc.want) - want, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(content), e.NewDecoder())) + want, err := io.ReadAll(transform.NewReader(bytes.NewReader(content), e.NewDecoder())) if err != nil { t.Errorf("%s: error decoding with hard-coded charset name: %v", tc.filename, err) continue diff --git a/html/parse_test.go b/html/parse_test.go index 019333d519..aa91f4c2b3 100644 --- a/html/parse_test.go +++ b/html/parse_test.go @@ -10,7 +10,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "path/filepath" "runtime" @@ -477,7 +476,7 @@ func TestParseFragmentForeignContentTemplates(t *testing.T) { } func BenchmarkParser(b *testing.B) { - buf, err := ioutil.ReadFile("testdata/go1.html") + buf, err := os.ReadFile("testdata/go1.html") if err != nil { b.Fatalf("could not read testdata/go1.html: %v", err) } diff --git a/html/token_test.go b/html/token_test.go index 8b0d5aab63..a36d112d74 100644 --- a/html/token_test.go +++ b/html/token_test.go @@ -7,7 +7,7 @@ package html import ( "bytes" "io" - "io/ioutil" + "os" "reflect" "runtime" "strings" @@ -680,7 +680,7 @@ tests: } } // Anything tokenized along with untokenized input or data left in the reader. - assembled, err := ioutil.ReadAll(io.MultiReader(&tokenized, bytes.NewReader(z.Buffered()), r)) + assembled, err := io.ReadAll(io.MultiReader(&tokenized, bytes.NewReader(z.Buffered()), r)) if err != nil { t.Errorf("%s: ReadAll: %v", test.desc, err) continue tests @@ -866,7 +866,7 @@ const ( ) func benchmarkTokenizer(b *testing.B, level int) { - buf, err := ioutil.ReadFile("testdata/go1.html") + buf, err := os.ReadFile("testdata/go1.html") if err != nil { b.Fatalf("could not read testdata/go1.html: %v", err) } diff --git a/http2/clientconn_test.go b/http2/clientconn_test.go index 4237b14364..5533790992 100644 --- a/http2/clientconn_test.go +++ b/http2/clientconn_test.go @@ -9,12 +9,12 @@ package http2 import ( "bytes" + "context" "fmt" "io" - "net" "net/http" "reflect" - "slices" + "sync/atomic" "testing" "time" @@ -59,6 +59,7 @@ func TestTestClientConn(t *testing.T) { streamID: rt.streamID(), endStream: true, size: 10, + multiple: true, }) // tc.writeHeaders sends a HEADERS frame back to the client. @@ -93,17 +94,15 @@ type testClientConn struct { tr *Transport fr *Framer cc *ClientConn - hooks *testSyncHooks + group *synctestGroup + testConnFramer encbuf bytes.Buffer enc *hpack.Encoder roundtrips []*testRoundTrip - rerr error // returned by Read - netConnClosed bool // set when the ClientConn closes the net.Conn - rbuf bytes.Buffer // sent to the test conn - wbuf bytes.Buffer // sent by the test conn + netconn *synctestNetConn } func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientConn { @@ -111,19 +110,28 @@ func newTestClientConnFromClientConn(t *testing.T, cc *ClientConn) *testClientCo t: t, tr: cc.t, cc: cc, - hooks: cc.t.syncHooks, + group: cc.t.transportTestHooks.group.(*synctestGroup), } - cc.tconn = (*testClientConnNetConn)(tc) + cli, srv := synctestNetPipe(tc.group) + srv.SetReadDeadline(tc.group.Now()) + srv.autoWait = true + tc.netconn = srv tc.enc = hpack.NewEncoder(&tc.encbuf) - tc.fr = NewFramer(&tc.rbuf, &tc.wbuf) - tc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil) + + // all writes and reads are finished. + // + // cli is the ClientConn's side, srv is the side controlled by the test. + cc.tconn = cli + tc.fr = NewFramer(srv, srv) + tc.testConnFramer = testConnFramer{ + t: t, + fr: tc.fr, + dec: hpack.NewDecoder(initialHeaderTableSize, nil), + } + tc.fr.SetMaxReadFrameSize(10 << 20) t.Cleanup(func() { - tc.sync() - if tc.rerr == nil { - tc.rerr = io.EOF - } - tc.sync() + tc.closeWrite() }) return tc } @@ -132,7 +140,7 @@ func (tc *testClientConn) readClientPreface() { tc.t.Helper() // Read the client's HTTP/2 preface, sent prior to any HTTP/2 frames. buf := make([]byte, len(clientPreface)) - if _, err := io.ReadFull(&tc.wbuf, buf); err != nil { + if _, err := io.ReadFull(tc.netconn, buf); err != nil { tc.t.Fatalf("reading preface: %v", err) } if !bytes.Equal(buf, clientPreface) { @@ -145,7 +153,7 @@ func newTestClientConn(t *testing.T, opts ...func(*Transport)) *testClientConn { tt := newTestTransport(t, opts...) const singleUse = false - _, err := tt.tr.newClientConn(nil, singleUse, tt.tr.syncHooks) + _, err := tt.tr.newClientConn(nil, singleUse) if err != nil { t.Fatalf("newClientConn: %v", err) } @@ -156,182 +164,35 @@ func newTestClientConn(t *testing.T, opts ...func(*Transport)) *testClientConn { // sync waits for the ClientConn under test to reach a stable state, // with all goroutines blocked on some input. func (tc *testClientConn) sync() { - tc.hooks.waitInactive() + tc.group.Wait() } // advance advances synthetic time by a duration. func (tc *testClientConn) advance(d time.Duration) { - tc.hooks.advance(d) + tc.group.AdvanceTime(d) tc.sync() } // hasFrame reports whether a frame is available to be read. func (tc *testClientConn) hasFrame() bool { - return tc.wbuf.Len() > 0 -} - -// readFrame reads the next frame from the conn. -func (tc *testClientConn) readFrame() Frame { - if tc.wbuf.Len() == 0 { - return nil - } - fr, err := tc.fr.ReadFrame() - if err != nil { - return nil - } - return fr + return len(tc.netconn.Peek()) > 0 } -// testClientConnReadFrame reads a frame of a specific type from the conn. -func testClientConnReadFrame[T any](tc *testClientConn) T { - tc.t.Helper() - var v T - fr := tc.readFrame() - if fr == nil { - tc.t.Fatalf("got no frame, want frame %T", v) - } - v, ok := fr.(T) - if !ok { - tc.t.Fatalf("got frame %T, want %T", fr, v) - } - return v -} - -// wantFrameType reads the next frame from the conn. -// It produces an error if the frame type is not the expected value. -func (tc *testClientConn) wantFrameType(want FrameType) { - tc.t.Helper() - fr := tc.readFrame() - if fr == nil { - tc.t.Fatalf("got no frame, want frame %v", want) - } - if got := fr.Header().Type; got != want { - tc.t.Fatalf("got frame %v, want %v", got, want) - } +// isClosed reports whether the peer has closed the connection. +func (tc *testClientConn) isClosed() bool { + return tc.netconn.IsClosedByPeer() } -// wantUnorderedFrames reads frames from the conn until every condition in want has been satisfied. -// -// want is a list of func(*SomeFrame) bool. -// wantUnorderedFrames will call each func with frames of the appropriate type -// until the func returns true. -// It calls t.Fatal if an unexpected frame is received (no func has that frame type, -// or all funcs with that type have returned true), or if the conn runs out of frames -// with unsatisfied funcs. -// -// Example: -// -// // Read a SETTINGS frame, and any number of DATA frames for a stream. -// // The SETTINGS frame may appear anywhere in the sequence. -// // The last DATA frame must indicate the end of the stream. -// tc.wantUnorderedFrames( -// func(f *SettingsFrame) bool { -// return true -// }, -// func(f *DataFrame) bool { -// return f.StreamEnded() -// }, -// ) -func (tc *testClientConn) wantUnorderedFrames(want ...any) { - tc.t.Helper() - want = slices.Clone(want) - seen := 0 -frame: - for seen < len(want) && !tc.t.Failed() { - fr := tc.readFrame() - if fr == nil { - break - } - for i, f := range want { - if f == nil { - continue - } - typ := reflect.TypeOf(f) - if typ.Kind() != reflect.Func || - typ.NumIn() != 1 || - typ.NumOut() != 1 || - typ.Out(0) != reflect.TypeOf(true) { - tc.t.Fatalf("expected func(*SomeFrame) bool, got %T", f) - } - if typ.In(0) == reflect.TypeOf(fr) { - out := reflect.ValueOf(f).Call([]reflect.Value{reflect.ValueOf(fr)}) - if out[0].Bool() { - want[i] = nil - seen++ - } - continue frame - } - } - tc.t.Errorf("got unexpected frame type %T", fr) - } - if seen < len(want) { - for _, f := range want { - if f == nil { - continue - } - tc.t.Errorf("did not see expected frame: %v", reflect.TypeOf(f).In(0)) - } - tc.t.Fatalf("did not see %v expected frame types", len(want)-seen) - } -} - -type wantHeader struct { - streamID uint32 - endStream bool - header http.Header -} - -// wantHeaders reads a HEADERS frame and potential CONTINUATION frames, -// and asserts that they contain the expected headers. -func (tc *testClientConn) wantHeaders(want wantHeader) { - tc.t.Helper() - got := testClientConnReadFrame[*MetaHeadersFrame](tc) - if got, want := got.StreamID, want.streamID; got != want { - tc.t.Fatalf("got stream ID %v, want %v", got, want) - } - if got, want := got.StreamEnded(), want.endStream; got != want { - tc.t.Fatalf("got stream ended %v, want %v", got, want) - } - gotHeader := make(http.Header) - for _, f := range got.Fields { - gotHeader[f.Name] = append(gotHeader[f.Name], f.Value) - } - for k, v := range want.header { - if !reflect.DeepEqual(v, gotHeader[k]) { - tc.t.Fatalf("got header %q = %q; want %q", k, v, gotHeader[k]) - } - } -} - -type wantData struct { - streamID uint32 - endStream bool - size int -} - -// wantData reads zero or more DATA frames, and asserts that they match the expectation. -func (tc *testClientConn) wantData(want wantData) { - tc.t.Helper() - gotSize := 0 - gotEndStream := false - for tc.hasFrame() && !gotEndStream { - data := testClientConnReadFrame[*DataFrame](tc) - gotSize += len(data.Data()) - if data.StreamEnded() { - gotEndStream = true - } - } - if gotSize != want.size { - tc.t.Fatalf("got %v bytes of DATA frames, want %v", gotSize, want.size) - } - if gotEndStream != want.endStream { - tc.t.Fatalf("after %v bytes of DATA frames, got END_STREAM=%v; want %v", gotSize, gotEndStream, want.endStream) - } +// closeWrite causes the net.Conn used by the ClientConn to return a error +// from Read calls. +func (tc *testClientConn) closeWrite() { + tc.netconn.Close() } // testRequestBody is a Request.Body for use in tests. type testRequestBody struct { - tc *testClientConn + tc *testClientConn + gate gate // At most one of buf or bytes can be set at any given time: buf bytes.Buffer // specific bytes to read from the body @@ -342,16 +203,22 @@ type testRequestBody struct { func (tc *testClientConn) newRequestBody() *testRequestBody { b := &testRequestBody{ - tc: tc, + tc: tc, + gate: newGate(), } return b } +func (b *testRequestBody) unlock() { + b.gate.unlock(b.buf.Len() > 0 || b.bytes > 0 || b.err != nil) +} + // Read is called by the ClientConn to read from a request body. func (b *testRequestBody) Read(p []byte) (n int, _ error) { - b.tc.cc.syncHooks.blockUntil(func() bool { - return b.buf.Len() > 0 || b.bytes > 0 || b.err != nil - }) + if err := b.gate.waitAndLock(context.Background()); err != nil { + return 0, err + } + defer b.unlock() switch { case b.buf.Len() > 0: return b.buf.Read(p) @@ -376,6 +243,9 @@ func (b *testRequestBody) Close() error { // writeBytes adds n arbitrary bytes to the body. func (b *testRequestBody) writeBytes(n int) { + defer b.tc.sync() + b.gate.lock() + defer b.unlock() b.bytes += n b.checkWrite() b.tc.sync() @@ -383,9 +253,11 @@ func (b *testRequestBody) writeBytes(n int) { // Write adds bytes to the body. func (b *testRequestBody) Write(p []byte) (int, error) { + defer b.tc.sync() + b.gate.lock() + defer b.unlock() n, err := b.buf.Write(p) b.checkWrite() - b.tc.sync() return n, err } @@ -400,8 +272,10 @@ func (b *testRequestBody) checkWrite() { // closeWithError sets an error which will be returned by Read. func (b *testRequestBody) closeWithError(err error) { + defer b.tc.sync() + b.gate.lock() + defer b.unlock() b.err = err - b.tc.sync() } // roundTrip starts a RoundTrip call. @@ -414,13 +288,14 @@ func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip { donec: make(chan struct{}), } tc.roundtrips = append(tc.roundtrips, rt) - tc.hooks.newstream = func(cs *clientStream) { rt.cs = cs } - tc.cc.goRun(func() { + go func() { + tc.group.Join() defer close(rt.donec) - rt.resp, rt.respErr = tc.cc.RoundTrip(req) - }) + rt.resp, rt.respErr = tc.cc.roundTrip(req, func(cs *clientStream) { + rt.id.Store(cs.ID) + }) + }() tc.sync() - tc.hooks.newstream = nil tc.t.Cleanup(func() { if !rt.done() { @@ -443,38 +318,6 @@ func (tc *testClientConn) greet(settings ...Setting) { tc.wantFrameType(FrameSettings) // acknowledgement } -func (tc *testClientConn) writeSettings(settings ...Setting) { - tc.t.Helper() - if err := tc.fr.WriteSettings(settings...); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeSettingsAck() { - tc.t.Helper() - if err := tc.fr.WriteSettingsAck(); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeData(streamID uint32, endStream bool, data []byte) { - tc.t.Helper() - if err := tc.fr.WriteData(streamID, endStream, data); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) { - tc.t.Helper() - if err := tc.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - // makeHeaderBlockFragment encodes headers in a form suitable for inclusion // in a HEADERS or CONTINUATION frame. // @@ -490,87 +333,6 @@ func (tc *testClientConn) makeHeaderBlockFragment(s ...string) []byte { return tc.encbuf.Bytes() } -func (tc *testClientConn) writeHeaders(p HeadersFrameParam) { - tc.t.Helper() - if err := tc.fr.WriteHeaders(p); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -// writeHeadersMode writes header frames, as modified by mode: -// -// - noHeader: Don't write the header. -// - oneHeader: Write a single HEADERS frame. -// - splitHeader: Write a HEADERS frame and CONTINUATION frame. -func (tc *testClientConn) writeHeadersMode(mode headerType, p HeadersFrameParam) { - tc.t.Helper() - switch mode { - case noHeader: - case oneHeader: - tc.writeHeaders(p) - case splitHeader: - if len(p.BlockFragment) < 2 { - panic("too small") - } - contData := p.BlockFragment[1:] - contEnd := p.EndHeaders - p.BlockFragment = p.BlockFragment[:1] - p.EndHeaders = false - tc.writeHeaders(p) - tc.writeContinuation(p.StreamID, contEnd, contData) - default: - panic("bogus mode") - } -} - -func (tc *testClientConn) writeContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) { - tc.t.Helper() - if err := tc.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeRSTStream(streamID uint32, code ErrCode) { - tc.t.Helper() - if err := tc.fr.WriteRSTStream(streamID, code); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writePing(ack bool, data [8]byte) { - tc.t.Helper() - if err := tc.fr.WritePing(ack, data); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeGoAway(maxStreamID uint32, code ErrCode, debugData []byte) { - tc.t.Helper() - if err := tc.fr.WriteGoAway(maxStreamID, code, debugData); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -func (tc *testClientConn) writeWindowUpdate(streamID, incr uint32) { - tc.t.Helper() - if err := tc.fr.WriteWindowUpdate(streamID, incr); err != nil { - tc.t.Fatal(err) - } - tc.sync() -} - -// closeWrite causes the net.Conn used by the ClientConn to return a error -// from Read calls. -func (tc *testClientConn) closeWrite(err error) { - tc.rerr = err - tc.sync() -} - // inflowWindow returns the amount of inbound flow control available for a stream, // or for the connection if streamID is 0. func (tc *testClientConn) inflowWindow(streamID uint32) int32 { @@ -593,15 +355,16 @@ type testRoundTrip struct { resp *http.Response respErr error donec chan struct{} - cs *clientStream + id atomic.Uint32 } // streamID returns the HTTP/2 stream ID of the request. func (rt *testRoundTrip) streamID() uint32 { - if rt.cs == nil { + id := rt.id.Load() + if id == 0 { panic("stream ID unknown") } - return rt.cs.ID + return id } // done reports whether RoundTrip has returned. @@ -712,58 +475,36 @@ func diffHeaders(got, want http.Header) string { return fmt.Sprintf("got: %v\nwant: %v", got, want) } -// testClientConnNetConn implements net.Conn. -type testClientConnNetConn testClientConn - -func (nc *testClientConnNetConn) Read(b []byte) (n int, err error) { - nc.cc.syncHooks.blockUntil(func() bool { - return nc.rerr != nil || nc.rbuf.Len() > 0 - }) - if nc.rbuf.Len() > 0 { - return nc.rbuf.Read(b) - } - return 0, nc.rerr -} - -func (nc *testClientConnNetConn) Write(b []byte) (n int, err error) { - return nc.wbuf.Write(b) -} - -func (nc *testClientConnNetConn) Close() error { - nc.netConnClosed = true - return nil -} - -func (*testClientConnNetConn) LocalAddr() (_ net.Addr) { return } -func (*testClientConnNetConn) RemoteAddr() (_ net.Addr) { return } -func (*testClientConnNetConn) SetDeadline(t time.Time) error { return nil } -func (*testClientConnNetConn) SetReadDeadline(t time.Time) error { return nil } -func (*testClientConnNetConn) SetWriteDeadline(t time.Time) error { return nil } - // A testTransport allows testing Transport.RoundTrip against fake servers. // Tests that aren't specifically exercising RoundTrip's retry loop or connection pooling // should use testClientConn instead. type testTransport struct { - t *testing.T - tr *Transport + t *testing.T + tr *Transport + group *synctestGroup ccs []*testClientConn } func newTestTransport(t *testing.T, opts ...func(*Transport)) *testTransport { - tr := &Transport{ - syncHooks: newTestSyncHooks(), + tt := &testTransport{ + t: t, + group: newSynctest(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)), } + tt.group.Join() + + tr := &Transport{} for _, o := range opts { o(tr) } + tt.tr = tr - tt := &testTransport{ - t: t, - tr: tr, - } - tr.syncHooks.newclientconn = func(cc *ClientConn) { - tt.ccs = append(tt.ccs, newTestClientConnFromClientConn(t, cc)) + tr.transportTestHooks = &transportTestHooks{ + group: tt.group, + newclientconn: func(cc *ClientConn) { + tc := newTestClientConnFromClientConn(t, cc) + tt.ccs = append(tt.ccs, tc) + }, } t.Cleanup(func() { @@ -771,20 +512,18 @@ func newTestTransport(t *testing.T, opts ...func(*Transport)) *testTransport { if len(tt.ccs) > 0 { t.Fatalf("%v test ClientConns created, but not examined by test", len(tt.ccs)) } - if tt.tr.syncHooks.total != 0 { - t.Errorf("%v goroutines still running after test completed", tt.tr.syncHooks.total) - } + tt.group.Close(t) }) return tt } func (tt *testTransport) sync() { - tt.tr.syncHooks.waitInactive() + tt.group.Wait() } func (tt *testTransport) advance(d time.Duration) { - tt.tr.syncHooks.advance(d) + tt.group.AdvanceTime(d) tt.sync() } @@ -801,6 +540,7 @@ func (tt *testTransport) getConn() *testClientConn { tt.ccs = tt.ccs[1:] tc.sync() tc.readClientPreface() + tc.sync() return tc } @@ -809,10 +549,11 @@ func (tt *testTransport) roundTrip(req *http.Request) *testRoundTrip { t: tt.t, donec: make(chan struct{}), } - tt.tr.syncHooks.goRun(func() { + go func() { + tt.group.Join() defer close(rt.donec) rt.resp, rt.respErr = tt.tr.RoundTrip(req) - }) + }() tt.sync() tt.t.Cleanup(func() { diff --git a/http2/connframes_test.go b/http2/connframes_test.go new file mode 100644 index 0000000000..7db8b74e2e --- /dev/null +++ b/http2/connframes_test.go @@ -0,0 +1,414 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "bytes" + "context" + "io" + "net/http" + "os" + "reflect" + "slices" + "testing" + + "golang.org/x/net/http2/hpack" +) + +type testConnFramer struct { + t testing.TB + fr *Framer + dec *hpack.Decoder +} + +// readFrame reads the next frame. +// It returns nil if the conn is closed or no frames are available. +func (tf *testConnFramer) readFrame() Frame { + tf.t.Helper() + fr, err := tf.fr.ReadFrame() + if err == io.EOF || err == os.ErrDeadlineExceeded { + return nil + } + if err != nil { + tf.t.Fatalf("ReadFrame: %v", err) + } + return fr +} + +type readFramer interface { + readFrame() Frame +} + +// readFrame reads a frame of a specific type. +func readFrame[T any](t testing.TB, framer readFramer) T { + t.Helper() + var v T + fr := framer.readFrame() + if fr == nil { + t.Fatalf("got no frame, want frame %T", v) + } + v, ok := fr.(T) + if !ok { + t.Fatalf("got frame %T, want %T", fr, v) + } + return v +} + +// wantFrameType reads the next frame. +// It produces an error if the frame type is not the expected value. +func (tf *testConnFramer) wantFrameType(want FrameType) { + tf.t.Helper() + fr := tf.readFrame() + if fr == nil { + tf.t.Fatalf("got no frame, want frame %v", want) + } + if got := fr.Header().Type; got != want { + tf.t.Fatalf("got frame %v, want %v", got, want) + } +} + +// wantUnorderedFrames reads frames until every condition in want has been satisfied. +// +// want is a list of func(*SomeFrame) bool. +// wantUnorderedFrames will call each func with frames of the appropriate type +// until the func returns true. +// It calls t.Fatal if an unexpected frame is received (no func has that frame type, +// or all funcs with that type have returned true), or if the framer runs out of frames +// with unsatisfied funcs. +// +// Example: +// +// // Read a SETTINGS frame, and any number of DATA frames for a stream. +// // The SETTINGS frame may appear anywhere in the sequence. +// // The last DATA frame must indicate the end of the stream. +// tf.wantUnorderedFrames( +// func(f *SettingsFrame) bool { +// return true +// }, +// func(f *DataFrame) bool { +// return f.StreamEnded() +// }, +// ) +func (tf *testConnFramer) wantUnorderedFrames(want ...any) { + tf.t.Helper() + want = slices.Clone(want) + seen := 0 +frame: + for seen < len(want) && !tf.t.Failed() { + fr := tf.readFrame() + if fr == nil { + break + } + for i, f := range want { + if f == nil { + continue + } + typ := reflect.TypeOf(f) + if typ.Kind() != reflect.Func || + typ.NumIn() != 1 || + typ.NumOut() != 1 || + typ.Out(0) != reflect.TypeOf(true) { + tf.t.Fatalf("expected func(*SomeFrame) bool, got %T", f) + } + if typ.In(0) == reflect.TypeOf(fr) { + out := reflect.ValueOf(f).Call([]reflect.Value{reflect.ValueOf(fr)}) + if out[0].Bool() { + want[i] = nil + seen++ + } + continue frame + } + } + tf.t.Errorf("got unexpected frame type %T", fr) + } + if seen < len(want) { + for _, f := range want { + if f == nil { + continue + } + tf.t.Errorf("did not see expected frame: %v", reflect.TypeOf(f).In(0)) + } + tf.t.Fatalf("did not see %v expected frame types", len(want)-seen) + } +} + +type wantHeader struct { + streamID uint32 + endStream bool + header http.Header +} + +// wantHeaders reads a HEADERS frame and potential CONTINUATION frames, +// and asserts that they contain the expected headers. +func (tf *testConnFramer) wantHeaders(want wantHeader) { + tf.t.Helper() + + hf := readFrame[*HeadersFrame](tf.t, tf) + if got, want := hf.StreamID, want.streamID; got != want { + tf.t.Fatalf("got stream ID %v, want %v", got, want) + } + if got, want := hf.StreamEnded(), want.endStream; got != want { + tf.t.Fatalf("got stream ended %v, want %v", got, want) + } + + gotHeader := make(http.Header) + tf.dec.SetEmitFunc(func(hf hpack.HeaderField) { + gotHeader[hf.Name] = append(gotHeader[hf.Name], hf.Value) + }) + defer tf.dec.SetEmitFunc(nil) + if _, err := tf.dec.Write(hf.HeaderBlockFragment()); err != nil { + tf.t.Fatalf("decoding HEADERS frame: %v", err) + } + headersEnded := hf.HeadersEnded() + for !headersEnded { + cf := readFrame[*ContinuationFrame](tf.t, tf) + if cf == nil { + tf.t.Fatalf("got end of frames, want CONTINUATION") + } + if _, err := tf.dec.Write(cf.HeaderBlockFragment()); err != nil { + tf.t.Fatalf("decoding CONTINUATION frame: %v", err) + } + headersEnded = cf.HeadersEnded() + } + if err := tf.dec.Close(); err != nil { + tf.t.Fatalf("hpack decoding error: %v", err) + } + + for k, v := range want.header { + if !reflect.DeepEqual(v, gotHeader[k]) { + tf.t.Fatalf("got header %q = %q; want %q", k, v, gotHeader[k]) + } + } +} + +// decodeHeader supports some older server tests. +// TODO: rewrite those tests to use newer, more convenient test APIs. +func (tf *testConnFramer) decodeHeader(headerBlock []byte) (pairs [][2]string) { + tf.dec.SetEmitFunc(func(hf hpack.HeaderField) { + if hf.Name == "date" { + return + } + pairs = append(pairs, [2]string{hf.Name, hf.Value}) + }) + defer tf.dec.SetEmitFunc(nil) + if _, err := tf.dec.Write(headerBlock); err != nil { + tf.t.Fatalf("hpack decoding error: %v", err) + } + if err := tf.dec.Close(); err != nil { + tf.t.Fatalf("hpack decoding error: %v", err) + } + return pairs +} + +type wantData struct { + streamID uint32 + endStream bool + size int + data []byte + multiple bool // data may be spread across multiple DATA frames +} + +// wantData reads zero or more DATA frames, and asserts that they match the expectation. +func (tf *testConnFramer) wantData(want wantData) { + tf.t.Helper() + gotSize := 0 + gotEndStream := false + if want.data != nil { + want.size = len(want.data) + } + var gotData []byte + for { + fr := tf.readFrame() + if fr == nil { + break + } + data, ok := fr.(*DataFrame) + if !ok { + tf.t.Fatalf("got frame %T, want DataFrame", fr) + } + if want.data != nil { + gotData = append(gotData, data.Data()...) + } + gotSize += len(data.Data()) + if data.StreamEnded() { + gotEndStream = true + break + } + if !want.endStream && gotSize >= want.size { + break + } + if !want.multiple { + break + } + } + if gotSize != want.size { + tf.t.Fatalf("got %v bytes of DATA frames, want %v", gotSize, want.size) + } + if gotEndStream != want.endStream { + tf.t.Fatalf("after %v bytes of DATA frames, got END_STREAM=%v; want %v", gotSize, gotEndStream, want.endStream) + } + if want.data != nil && !bytes.Equal(gotData, want.data) { + tf.t.Fatalf("got data %q, want %q", gotData, want.data) + } +} + +func (tf *testConnFramer) wantRSTStream(streamID uint32, code ErrCode) { + tf.t.Helper() + fr := readFrame[*RSTStreamFrame](tf.t, tf) + if fr.StreamID != streamID || fr.ErrCode != code { + tf.t.Fatalf("got %v, want RST_STREAM StreamID=%v, code=%v", summarizeFrame(fr), streamID, code) + } +} + +func (tf *testConnFramer) wantSettingsAck() { + tf.t.Helper() + fr := readFrame[*SettingsFrame](tf.t, tf) + if !fr.Header().Flags.Has(FlagSettingsAck) { + tf.t.Fatal("Settings Frame didn't have ACK set") + } +} + +func (tf *testConnFramer) wantGoAway(maxStreamID uint32, code ErrCode) { + tf.t.Helper() + fr := readFrame[*GoAwayFrame](tf.t, tf) + if fr.LastStreamID != maxStreamID || fr.ErrCode != code { + tf.t.Fatalf("got %v, want GOAWAY LastStreamID=%v, code=%v", summarizeFrame(fr), maxStreamID, code) + } +} + +func (tf *testConnFramer) wantWindowUpdate(streamID, incr uint32) { + tf.t.Helper() + wu := readFrame[*WindowUpdateFrame](tf.t, tf) + if wu.FrameHeader.StreamID != streamID { + tf.t.Fatalf("WindowUpdate StreamID = %d; want %d", wu.FrameHeader.StreamID, streamID) + } + if wu.Increment != incr { + tf.t.Fatalf("WindowUpdate increment = %d; want %d", wu.Increment, incr) + } +} + +func (tf *testConnFramer) wantClosed() { + tf.t.Helper() + fr, err := tf.fr.ReadFrame() + if err == nil { + tf.t.Fatalf("got unexpected frame (want closed connection): %v", fr) + } + if err == context.DeadlineExceeded { + tf.t.Fatalf("connection is not closed; want it to be") + } +} + +func (tf *testConnFramer) wantIdle() { + tf.t.Helper() + fr, err := tf.fr.ReadFrame() + if err == nil { + tf.t.Fatalf("got unexpected frame (want idle connection): %v", fr) + } + if err != context.DeadlineExceeded { + tf.t.Fatalf("got unexpected frame error (want idle connection): %v", err) + } +} + +func (tf *testConnFramer) writeSettings(settings ...Setting) { + tf.t.Helper() + if err := tf.fr.WriteSettings(settings...); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeSettingsAck() { + tf.t.Helper() + if err := tf.fr.WriteSettingsAck(); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeData(streamID uint32, endStream bool, data []byte) { + tf.t.Helper() + if err := tf.fr.WriteData(streamID, endStream, data); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) { + tf.t.Helper() + if err := tf.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeHeaders(p HeadersFrameParam) { + tf.t.Helper() + if err := tf.fr.WriteHeaders(p); err != nil { + tf.t.Fatal(err) + } +} + +// writeHeadersMode writes header frames, as modified by mode: +// +// - noHeader: Don't write the header. +// - oneHeader: Write a single HEADERS frame. +// - splitHeader: Write a HEADERS frame and CONTINUATION frame. +func (tf *testConnFramer) writeHeadersMode(mode headerType, p HeadersFrameParam) { + tf.t.Helper() + switch mode { + case noHeader: + case oneHeader: + tf.writeHeaders(p) + case splitHeader: + if len(p.BlockFragment) < 2 { + panic("too small") + } + contData := p.BlockFragment[1:] + contEnd := p.EndHeaders + p.BlockFragment = p.BlockFragment[:1] + p.EndHeaders = false + tf.writeHeaders(p) + tf.writeContinuation(p.StreamID, contEnd, contData) + default: + panic("bogus mode") + } +} + +func (tf *testConnFramer) writeContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) { + tf.t.Helper() + if err := tf.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writePriority(id uint32, p PriorityParam) { + if err := tf.fr.WritePriority(id, p); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeRSTStream(streamID uint32, code ErrCode) { + tf.t.Helper() + if err := tf.fr.WriteRSTStream(streamID, code); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writePing(ack bool, data [8]byte) { + tf.t.Helper() + if err := tf.fr.WritePing(ack, data); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeGoAway(maxStreamID uint32, code ErrCode, debugData []byte) { + tf.t.Helper() + if err := tf.fr.WriteGoAway(maxStreamID, code, debugData); err != nil { + tf.t.Fatal(err) + } +} + +func (tf *testConnFramer) writeWindowUpdate(streamID, incr uint32) { + tf.t.Helper() + if err := tf.fr.WriteWindowUpdate(streamID, incr); err != nil { + tf.t.Fatal(err) + } +} diff --git a/http2/gate_test.go b/http2/gate_test.go new file mode 100644 index 0000000000..e5e6a315be --- /dev/null +++ b/http2/gate_test.go @@ -0,0 +1,85 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package http2 + +import "context" + +// An gate is a monitor (mutex + condition variable) with one bit of state. +// +// The condition may be either set or unset. +// Lock operations may be unconditional, or wait for the condition to be set. +// Unlock operations record the new state of the condition. +type gate struct { + // When unlocked, exactly one of set or unset contains a value. + // When locked, neither chan contains a value. + set chan struct{} + unset chan struct{} +} + +// newGate returns a new, unlocked gate with the condition unset. +func newGate() gate { + g := newLockedGate() + g.unlock(false) + return g +} + +// newLocked gate returns a new, locked gate. +func newLockedGate() gate { + return gate{ + set: make(chan struct{}, 1), + unset: make(chan struct{}, 1), + } +} + +// lock acquires the gate unconditionally. +// It reports whether the condition is set. +func (g *gate) lock() (set bool) { + select { + case <-g.set: + return true + case <-g.unset: + return false + } +} + +// waitAndLock waits until the condition is set before acquiring the gate. +// If the context expires, waitAndLock returns an error and does not acquire the gate. +func (g *gate) waitAndLock(ctx context.Context) error { + select { + case <-g.set: + return nil + default: + } + select { + case <-g.set: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// lockIfSet acquires the gate if and only if the condition is set. +func (g *gate) lockIfSet() (acquired bool) { + select { + case <-g.set: + return true + default: + return false + } +} + +// unlock sets the condition and releases the gate. +func (g *gate) unlock(set bool) { + if set { + g.set <- struct{}{} + } else { + g.unset <- struct{}{} + } +} + +// unlock sets the condition to the result of f and releases the gate. +// Useful in defers. +func (g *gate) unlockFunc(f func() bool) { + g.unlock(f()) +} diff --git a/http2/h2c/h2c_test.go b/http2/h2c/h2c_test.go index 038cbc3649..3e78f29135 100644 --- a/http2/h2c/h2c_test.go +++ b/http2/h2c/h2c_test.go @@ -9,7 +9,6 @@ import ( "crypto/tls" "fmt" "io" - "io/ioutil" "log" "net" "net/http" @@ -68,7 +67,7 @@ func TestContext(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = ioutil.ReadAll(resp.Body) + _, err = io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } @@ -162,7 +161,7 @@ func TestMaxBytesHandler(t *testing.T) { t.Fatal(err) } defer resp.Body.Close() - _, err = ioutil.ReadAll(resp.Body) + _, err = io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } diff --git a/http2/hpack/gen.go b/http2/hpack/gen.go index 21a4198b33..0efa8e558c 100644 --- a/http2/hpack/gen.go +++ b/http2/hpack/gen.go @@ -10,7 +10,6 @@ import ( "bytes" "fmt" "go/format" - "io/ioutil" "os" "sort" @@ -176,7 +175,7 @@ func genFile(name string, buf *bytes.Buffer) { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - if err := ioutil.WriteFile(name, b, 0644); err != nil { + if err := os.WriteFile(name, b, 0644); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } diff --git a/http2/http2.go b/http2/http2.go index 6f2df28187..003e649f30 100644 --- a/http2/http2.go +++ b/http2/http2.go @@ -17,6 +17,7 @@ package http2 // import "golang.org/x/net/http2" import ( "bufio" + "context" "crypto/tls" "fmt" "io" @@ -26,6 +27,7 @@ import ( "strconv" "strings" "sync" + "time" "golang.org/x/net/http/httpguts" ) @@ -210,12 +212,6 @@ type stringWriter interface { WriteString(s string) (n int, err error) } -// A gate lets two goroutines coordinate their activities. -type gate chan struct{} - -func (g gate) Done() { g <- struct{}{} } -func (g gate) Wait() { <-g } - // A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed). type closeWaiter chan struct{} @@ -383,3 +379,14 @@ func validPseudoPath(v string) bool { // makes that struct also non-comparable, and generally doesn't add // any size (as long as it's first). type incomparable [0]func() + +// synctestGroupInterface is the methods of synctestGroup used by Server and Transport. +// It's defined as an interface here to let us keep synctestGroup entirely test-only +// and not a part of non-test builds. +type synctestGroupInterface interface { + Join() + Now() time.Time + NewTimer(d time.Duration) timer + AfterFunc(d time.Duration, f func()) timer + ContextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) +} diff --git a/http2/http2_test.go b/http2/http2_test.go index a16774b7ff..b7c946b982 100644 --- a/http2/http2_test.go +++ b/http2/http2_test.go @@ -8,7 +8,6 @@ import ( "bytes" "flag" "fmt" - "io/ioutil" "net/http" "os" "path/filepath" @@ -266,7 +265,7 @@ func TestNoUnicodeStrings(t *testing.T) { return nil } - contents, err := ioutil.ReadFile(path) + contents, err := os.ReadFile(path) if err != nil { t.Fatal(err) } diff --git a/http2/netconn_test.go b/http2/netconn_test.go new file mode 100644 index 0000000000..8a61fbef10 --- /dev/null +++ b/http2/netconn_test.go @@ -0,0 +1,350 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "bytes" + "context" + "errors" + "io" + "math" + "net" + "net/netip" + "os" + "sync" + "time" +) + +// synctestNetPipe creates an in-memory, full duplex network connection. +// Read and write timeouts are managed by the synctest group. +// +// Unlike net.Pipe, the connection is not synchronous. +// Writes are made to a buffer, and return immediately. +// By default, the buffer size is unlimited. +func synctestNetPipe(group *synctestGroup) (r, w *synctestNetConn) { + s1addr := net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:8000")) + s2addr := net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:8001")) + s1 := newSynctestNetConnHalf(s1addr) + s2 := newSynctestNetConnHalf(s2addr) + return &synctestNetConn{group: group, loc: s1, rem: s2}, + &synctestNetConn{group: group, loc: s2, rem: s1} +} + +// A synctestNetConn is one endpoint of the connection created by synctestNetPipe. +type synctestNetConn struct { + group *synctestGroup + + // local and remote connection halves. + // Each half contains a buffer. + // Reads pull from the local buffer, and writes push to the remote buffer. + loc, rem *synctestNetConnHalf + + // When set, group.Wait is automatically called before reads and after writes. + autoWait bool +} + +// Read reads data from the connection. +func (c *synctestNetConn) Read(b []byte) (n int, err error) { + if c.autoWait { + c.group.Wait() + } + return c.loc.read(b) +} + +// Peek returns the available unread read buffer, +// without consuming its contents. +func (c *synctestNetConn) Peek() []byte { + if c.autoWait { + c.group.Wait() + } + return c.loc.peek() +} + +// Write writes data to the connection. +func (c *synctestNetConn) Write(b []byte) (n int, err error) { + if c.autoWait { + defer c.group.Wait() + } + return c.rem.write(b) +} + +// IsClosed reports whether the peer has closed its end of the connection. +func (c *synctestNetConn) IsClosedByPeer() bool { + if c.autoWait { + c.group.Wait() + } + return c.loc.isClosedByPeer() +} + +// Close closes the connection. +func (c *synctestNetConn) Close() error { + c.loc.setWriteError(errors.New("connection closed by peer")) + c.rem.setReadError(io.EOF) + if c.autoWait { + c.group.Wait() + } + return nil +} + +// LocalAddr returns the (fake) local network address. +func (c *synctestNetConn) LocalAddr() net.Addr { + return c.loc.addr +} + +// LocalAddr returns the (fake) remote network address. +func (c *synctestNetConn) RemoteAddr() net.Addr { + return c.rem.addr +} + +// SetDeadline sets the read and write deadlines for the connection. +func (c *synctestNetConn) SetDeadline(t time.Time) error { + c.SetReadDeadline(t) + c.SetWriteDeadline(t) + return nil +} + +// SetReadDeadline sets the read deadline for the connection. +func (c *synctestNetConn) SetReadDeadline(t time.Time) error { + c.loc.rctx.setDeadline(c.group, t) + return nil +} + +// SetWriteDeadline sets the write deadline for the connection. +func (c *synctestNetConn) SetWriteDeadline(t time.Time) error { + c.rem.wctx.setDeadline(c.group, t) + return nil +} + +// SetReadBufferSize sets the read buffer limit for the connection. +// Writes by the peer will block so long as the buffer is full. +func (c *synctestNetConn) SetReadBufferSize(size int) { + c.loc.setReadBufferSize(size) +} + +// synctestNetConnHalf is one data flow in the connection created by synctestNetPipe. +// Each half contains a buffer. Writes to the half push to the buffer, and reads pull from it. +type synctestNetConnHalf struct { + addr net.Addr + + // Read and write timeouts. + rctx, wctx deadlineContext + + // A half can be readable and/or writable. + // + // These four channels act as a lock, + // and allow waiting for readability/writability. + // When the half is unlocked, exactly one channel contains a value. + // When the half is locked, all channels are empty. + lockr chan struct{} // readable + lockw chan struct{} // writable + lockrw chan struct{} // readable and writable + lockc chan struct{} // neither readable nor writable + + bufMax int // maximum buffer size + buf bytes.Buffer + readErr error // error returned by reads + writeErr error // error returned by writes +} + +func newSynctestNetConnHalf(addr net.Addr) *synctestNetConnHalf { + h := &synctestNetConnHalf{ + addr: addr, + lockw: make(chan struct{}, 1), + lockr: make(chan struct{}, 1), + lockrw: make(chan struct{}, 1), + lockc: make(chan struct{}, 1), + bufMax: math.MaxInt, // unlimited + } + h.unlock() + return h +} + +func (h *synctestNetConnHalf) lock() { + select { + case <-h.lockw: + case <-h.lockr: + case <-h.lockrw: + case <-h.lockc: + } +} + +func (h *synctestNetConnHalf) unlock() { + canRead := h.readErr != nil || h.buf.Len() > 0 + canWrite := h.writeErr != nil || h.bufMax > h.buf.Len() + switch { + case canRead && canWrite: + h.lockrw <- struct{}{} + case canRead: + h.lockr <- struct{}{} + case canWrite: + h.lockw <- struct{}{} + default: + h.lockc <- struct{}{} + } +} + +func (h *synctestNetConnHalf) readWaitAndLock() error { + select { + case <-h.lockr: + return nil + case <-h.lockrw: + return nil + default: + } + ctx := h.rctx.context() + select { + case <-h.lockr: + return nil + case <-h.lockrw: + return nil + case <-ctx.Done(): + return context.Cause(ctx) + } +} + +func (h *synctestNetConnHalf) writeWaitAndLock() error { + select { + case <-h.lockw: + return nil + case <-h.lockrw: + return nil + default: + } + ctx := h.wctx.context() + select { + case <-h.lockw: + return nil + case <-h.lockrw: + return nil + case <-ctx.Done(): + return context.Cause(ctx) + } +} + +func (h *synctestNetConnHalf) peek() []byte { + h.lock() + defer h.unlock() + return h.buf.Bytes() +} + +func (h *synctestNetConnHalf) isClosedByPeer() bool { + h.lock() + defer h.unlock() + return h.readErr != nil +} + +func (h *synctestNetConnHalf) read(b []byte) (n int, err error) { + if err := h.readWaitAndLock(); err != nil { + return 0, err + } + defer h.unlock() + if h.buf.Len() == 0 && h.readErr != nil { + return 0, h.readErr + } + return h.buf.Read(b) +} + +func (h *synctestNetConnHalf) setReadBufferSize(size int) { + h.lock() + defer h.unlock() + h.bufMax = size +} + +func (h *synctestNetConnHalf) write(b []byte) (n int, err error) { + for n < len(b) { + nn, err := h.writePartial(b[n:]) + n += nn + if err != nil { + return n, err + } + } + return n, nil +} + +func (h *synctestNetConnHalf) writePartial(b []byte) (n int, err error) { + if err := h.writeWaitAndLock(); err != nil { + return 0, err + } + defer h.unlock() + if h.writeErr != nil { + return 0, h.writeErr + } + writeMax := h.bufMax - h.buf.Len() + if writeMax < len(b) { + b = b[:writeMax] + } + return h.buf.Write(b) +} + +func (h *synctestNetConnHalf) setReadError(err error) { + h.lock() + defer h.unlock() + if h.readErr == nil { + h.readErr = err + } +} + +func (h *synctestNetConnHalf) setWriteError(err error) { + h.lock() + defer h.unlock() + if h.writeErr == nil { + h.writeErr = err + } +} + +// deadlineContext converts a changable deadline (as in net.Conn.SetDeadline) into a Context. +type deadlineContext struct { + mu sync.Mutex + ctx context.Context + cancel context.CancelCauseFunc + timer timer +} + +// context returns a Context which expires when the deadline does. +func (t *deadlineContext) context() context.Context { + t.mu.Lock() + defer t.mu.Unlock() + if t.ctx == nil { + t.ctx, t.cancel = context.WithCancelCause(context.Background()) + } + return t.ctx +} + +// setDeadline sets the current deadline. +func (t *deadlineContext) setDeadline(group *synctestGroup, deadline time.Time) { + t.mu.Lock() + defer t.mu.Unlock() + // If t.ctx is non-nil and t.cancel is nil, then t.ctx was canceled + // and we should create a new one. + if t.ctx == nil || t.cancel == nil { + t.ctx, t.cancel = context.WithCancelCause(context.Background()) + } + // Stop any existing deadline from expiring. + if t.timer != nil { + t.timer.Stop() + } + if deadline.IsZero() { + // No deadline. + return + } + if !deadline.After(group.Now()) { + // Deadline has already expired. + t.cancel(os.ErrDeadlineExceeded) + t.cancel = nil + return + } + if t.timer != nil { + // Reuse existing deadline timer. + t.timer.Reset(deadline.Sub(group.Now())) + return + } + // Create a new timer to cancel the context at the deadline. + t.timer = group.AfterFunc(deadline.Sub(group.Now()), func() { + t.mu.Lock() + defer t.mu.Unlock() + t.cancel(os.ErrDeadlineExceeded) + t.cancel = nil + }) +} diff --git a/http2/pipe_test.go b/http2/pipe_test.go index 67562a92a1..326b94deb5 100644 --- a/http2/pipe_test.go +++ b/http2/pipe_test.go @@ -8,7 +8,6 @@ import ( "bytes" "errors" "io" - "io/ioutil" "testing" ) @@ -85,7 +84,7 @@ func TestPipeCloseWithError(t *testing.T) { io.WriteString(p, body) a := errors.New("test error") p.CloseWithError(a) - all, err := ioutil.ReadAll(p) + all, err := io.ReadAll(p) if string(all) != body { t.Errorf("read bytes = %q; want %q", all, body) } @@ -112,7 +111,7 @@ func TestPipeBreakWithError(t *testing.T) { io.WriteString(p, "foo") a := errors.New("test err") p.BreakWithError(a) - all, err := ioutil.ReadAll(p) + all, err := io.ReadAll(p) if string(all) != "" { t.Errorf("read bytes = %q; want empty string", all) } diff --git a/http2/server.go b/http2/server.go index c5d0810813..6c349f3ec6 100644 --- a/http2/server.go +++ b/http2/server.go @@ -154,6 +154,39 @@ type Server struct { // so that we don't embed a Mutex in this struct, which will make the // struct non-copyable, which might break some callers. state *serverInternalState + + // Synchronization group used for testing. + // Outside of tests, this is nil. + group synctestGroupInterface +} + +func (s *Server) markNewGoroutine() { + if s.group != nil { + s.group.Join() + } +} + +func (s *Server) now() time.Time { + if s.group != nil { + return s.group.Now() + } + return time.Now() +} + +// newTimer creates a new time.Timer, or a synthetic timer in tests. +func (s *Server) newTimer(d time.Duration) timer { + if s.group != nil { + return s.group.NewTimer(d) + } + return timeTimer{time.NewTimer(d)} +} + +// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests. +func (s *Server) afterFunc(d time.Duration, f func()) timer { + if s.group != nil { + return s.group.AfterFunc(d, f) + } + return timeTimer{time.AfterFunc(d, f)} } func (s *Server) initialConnRecvWindowSize() int32 { @@ -400,6 +433,10 @@ func (o *ServeConnOpts) handler() http.Handler { // // The opts parameter is optional. If nil, default values are used. func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { + s.serveConn(c, opts, nil) +} + +func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) { baseCtx, cancel := serverConnBaseContext(c, opts) defer cancel() @@ -426,6 +463,9 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { pushEnabled: true, sawClientPreface: opts.SawClientPreface, } + if newf != nil { + newf(sc) + } s.state.registerConn(sc) defer s.state.unregisterConn(sc) @@ -599,8 +639,8 @@ type serverConn struct { inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop needToSendGoAway bool // we need to schedule a GOAWAY frame write goAwayCode ErrCode - shutdownTimer *time.Timer // nil until used - idleTimer *time.Timer // nil if unused + shutdownTimer timer // nil until used + idleTimer timer // nil if unused // Owned by the writeFrameAsync goroutine: headerWriteBuf bytes.Buffer @@ -649,12 +689,12 @@ type stream struct { flow outflow // limits writing from Handler to client inflow inflow // what the client is allowed to POST/etc to us state streamState - resetQueued bool // RST_STREAM queued for write; set by sc.resetStream - gotTrailerHeader bool // HEADER frame for trailers was seen - wroteHeaders bool // whether we wrote headers (not status 100) - readDeadline *time.Timer // nil if unused - writeDeadline *time.Timer // nil if unused - closeErr error // set before cw is closed + resetQueued bool // RST_STREAM queued for write; set by sc.resetStream + gotTrailerHeader bool // HEADER frame for trailers was seen + wroteHeaders bool // whether we wrote headers (not status 100) + readDeadline timer // nil if unused + writeDeadline timer // nil if unused + closeErr error // set before cw is closed trailer http.Header // accumulated trailers reqTrailer http.Header // handler's Request.Trailer @@ -811,8 +851,9 @@ type readFrameResult struct { // consumer is done with the frame. // It's run on its own goroutine. func (sc *serverConn) readFrames() { - gate := make(gate) - gateDone := gate.Done + sc.srv.markNewGoroutine() + gate := make(chan struct{}) + gateDone := func() { gate <- struct{}{} } for { f, err := sc.framer.ReadFrame() select { @@ -843,6 +884,7 @@ type frameWriteResult struct { // At most one goroutine can be running writeFrameAsync at a time per // serverConn. func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest, wd *writeData) { + sc.srv.markNewGoroutine() var err error if wd == nil { err = wr.write.writeFrame(sc) @@ -922,13 +964,13 @@ func (sc *serverConn) serve() { sc.setConnState(http.StateIdle) if sc.srv.IdleTimeout > 0 { - sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer) + sc.idleTimer = sc.srv.afterFunc(sc.srv.IdleTimeout, sc.onIdleTimer) defer sc.idleTimer.Stop() } go sc.readFrames() // closed by defer sc.conn.Close above - settingsTimer := time.AfterFunc(firstSettingsTimeout, sc.onSettingsTimer) + settingsTimer := sc.srv.afterFunc(firstSettingsTimeout, sc.onSettingsTimer) defer settingsTimer.Stop() loopNum := 0 @@ -1057,10 +1099,10 @@ func (sc *serverConn) readPreface() error { errc <- nil } }() - timer := time.NewTimer(prefaceTimeout) // TODO: configurable on *Server? + timer := sc.srv.newTimer(prefaceTimeout) // TODO: configurable on *Server? defer timer.Stop() select { - case <-timer.C: + case <-timer.C(): return errPrefaceTimeout case err := <-errc: if err == nil { @@ -1425,7 +1467,7 @@ func (sc *serverConn) goAway(code ErrCode) { func (sc *serverConn) shutDownIn(d time.Duration) { sc.serveG.check() - sc.shutdownTimer = time.AfterFunc(d, sc.onShutdownTimer) + sc.shutdownTimer = sc.srv.afterFunc(d, sc.onShutdownTimer) } func (sc *serverConn) resetStream(se StreamError) { @@ -1639,7 +1681,7 @@ func (sc *serverConn) closeStream(st *stream, err error) { delete(sc.streams, st.id) if len(sc.streams) == 0 { sc.setConnState(http.StateIdle) - if sc.srv.IdleTimeout > 0 { + if sc.srv.IdleTimeout > 0 && sc.idleTimer != nil { sc.idleTimer.Reset(sc.srv.IdleTimeout) } if h1ServerKeepAlivesDisabled(sc.hs) { @@ -1661,6 +1703,7 @@ func (sc *serverConn) closeStream(st *stream, err error) { } } st.closeErr = err + st.cancelCtx() st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc sc.writeSched.CloseStream(st.id) } @@ -2021,7 +2064,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { // (in Go 1.8), though. That's a more sane option anyway. if sc.hs.ReadTimeout > 0 { sc.conn.SetReadDeadline(time.Time{}) - st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) + st.readDeadline = sc.srv.afterFunc(sc.hs.ReadTimeout, st.onReadTimeout) } return sc.scheduleHandler(id, rw, req, handler) @@ -2119,7 +2162,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream st.flow.add(sc.initialStreamSendWindowSize) st.inflow.init(sc.srv.initialStreamRecvWindowSize()) if sc.hs.WriteTimeout > 0 { - st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) + st.writeDeadline = sc.srv.afterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) } sc.streams[id] = st @@ -2343,6 +2386,7 @@ func (sc *serverConn) handlerDone() { // Run on its own goroutine. func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) { + sc.srv.markNewGoroutine() defer sc.sendServeMsg(handlerDoneMsg) didPanic := true defer func() { @@ -2639,7 +2683,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) { var date string if _, ok := rws.snapHeader["Date"]; !ok { // TODO(bradfitz): be faster here, like net/http? measure. - date = time.Now().UTC().Format(http.TimeFormat) + date = rws.conn.srv.now().UTC().Format(http.TimeFormat) } for _, v := range rws.snapHeader["Trailer"] { @@ -2761,7 +2805,7 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() { func (w *responseWriter) SetReadDeadline(deadline time.Time) error { st := w.rws.stream - if !deadline.IsZero() && deadline.Before(time.Now()) { + if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) { // If we're setting a deadline in the past, reset the stream immediately // so writes after SetWriteDeadline returns will fail. st.onReadTimeout() @@ -2777,9 +2821,9 @@ func (w *responseWriter) SetReadDeadline(deadline time.Time) error { if deadline.IsZero() { st.readDeadline = nil } else if st.readDeadline == nil { - st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout) + st.readDeadline = sc.srv.afterFunc(deadline.Sub(sc.srv.now()), st.onReadTimeout) } else { - st.readDeadline.Reset(deadline.Sub(time.Now())) + st.readDeadline.Reset(deadline.Sub(sc.srv.now())) } }) return nil @@ -2787,7 +2831,7 @@ func (w *responseWriter) SetReadDeadline(deadline time.Time) error { func (w *responseWriter) SetWriteDeadline(deadline time.Time) error { st := w.rws.stream - if !deadline.IsZero() && deadline.Before(time.Now()) { + if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) { // If we're setting a deadline in the past, reset the stream immediately // so writes after SetWriteDeadline returns will fail. st.onWriteTimeout() @@ -2803,9 +2847,9 @@ func (w *responseWriter) SetWriteDeadline(deadline time.Time) error { if deadline.IsZero() { st.writeDeadline = nil } else if st.writeDeadline == nil { - st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout) + st.writeDeadline = sc.srv.afterFunc(deadline.Sub(sc.srv.now()), st.onWriteTimeout) } else { - st.writeDeadline.Reset(deadline.Sub(time.Now())) + st.writeDeadline.Reset(deadline.Sub(sc.srv.now())) } }) return nil diff --git a/http2/server_push_test.go b/http2/server_push_test.go index cda8f43367..69e4c3b12d 100644 --- a/http2/server_push_test.go +++ b/http2/server_push_test.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "reflect" "runtime" @@ -40,7 +39,7 @@ func TestServer_Push_Success(t *testing.T) { if r.Body == nil { return fmt.Errorf("nil Body") } - if buf, err := ioutil.ReadAll(r.Body); err != nil || len(buf) != 0 { + if buf, err := io.ReadAll(r.Body); err != nil || len(buf) != 0 { return fmt.Errorf("ReadAll(Body)=%q,%v, want '',nil", buf, err) } return nil @@ -106,7 +105,7 @@ func TestServer_Push_Success(t *testing.T) { errc <- fmt.Errorf("unknown RequestURL %q", r.URL.RequestURI()) } }) - stURL = st.ts.URL + stURL = "https://" + st.authority() // Send one request, which should push two responses. st.greet() @@ -170,7 +169,7 @@ func TestServer_Push_Success(t *testing.T) { return checkPushPromise(f, 2, [][2]string{ {":method", "GET"}, {":scheme", "https"}, - {":authority", st.ts.Listener.Addr().String()}, + {":authority", st.authority()}, {":path", "/pushed?get"}, {"user-agent", userAgent}, }) @@ -179,7 +178,7 @@ func TestServer_Push_Success(t *testing.T) { return checkPushPromise(f, 4, [][2]string{ {":method", "HEAD"}, {":scheme", "https"}, - {":authority", st.ts.Listener.Addr().String()}, + {":authority", st.authority()}, {":path", "/pushed?head"}, {"cookie", cookie}, {"user-agent", userAgent}, @@ -219,12 +218,12 @@ func TestServer_Push_Success(t *testing.T) { consumed := map[uint32]int{} for k := 0; len(expected) > 0; k++ { - f, err := st.readFrame() - if err != nil { + f := st.readFrame() + if f == nil { for id, left := range expected { t.Errorf("stream %d: missing %d frames", id, len(left)) } - t.Fatalf("readFrame %d: %v", k, err) + break } id := f.Header().StreamID label := fmt.Sprintf("stream %d, frame %d", id, consumed[id]) @@ -340,10 +339,10 @@ func testServer_Push_RejectSingleRequest(t *testing.T, doPush func(http.Pusher, t.Error(err) } // Should not get a PUSH_PROMISE frame. - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Error("stream should end after headers") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) } func TestServer_Push_RejectIfDisabled(t *testing.T) { @@ -460,7 +459,7 @@ func TestServer_Push_StateTransitions(t *testing.T) { } getSlash(st) // After the PUSH_PROMISE is sent, the stream should be stateHalfClosedRemote. - st.wantPushPromise() + _ = readFrame[*PushPromiseFrame](t, st) if got, want := st.streamState(2), stateHalfClosedRemote; got != want { t.Fatalf("streamState(2)=%v, want %v", got, want) } @@ -469,10 +468,10 @@ func TestServer_Push_StateTransitions(t *testing.T) { // the stream before we check st.streamState(2) -- should that happen, we'll // see stateClosed and fail the above check. close(gotPromise) - st.wantHeaders() - if df := st.wantData(); !df.StreamEnded() { - t.Fatal("expected END_STREAM flag on DATA") - } + st.wantHeaders(wantHeader{ + streamID: 2, + endStream: false, + }) if got, want := st.streamState(2), stateClosed; got != want { t.Fatalf("streamState(2)=%v, want %v", got, want) } @@ -555,9 +554,9 @@ func TestServer_Push_Underflow(t *testing.T) { numPushPromises := 0 numHeaders := 0 for numHeaders < numRequests*2 || numPushPromises < numRequests { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) + f := st.readFrame() + if f == nil { + st.t.Fatal("conn is idle, want frame") } switch f := f.(type) { case *HeadersFrame: diff --git a/http2/server_test.go b/http2/server_test.go index 61c773a543..47c3c619c0 100644 --- a/http2/server_test.go +++ b/http2/server_test.go @@ -14,8 +14,8 @@ import ( "flag" "fmt" "io" - "io/ioutil" "log" + "math" "net" "net/http" "net/http/httptest" @@ -38,7 +38,7 @@ func stderrv() io.Writer { return os.Stderr } - return ioutil.Discard + return io.Discard } type safeBuffer struct { @@ -65,16 +65,16 @@ func (sb *safeBuffer) Len() int { } type serverTester struct { - cc net.Conn // client conn - t testing.TB - ts *httptest.Server - fr *Framer - serverLogBuf safeBuffer // logger for httptest.Server - logFilter []string // substrings to filter out - scMu sync.Mutex // guards sc - sc *serverConn - hpackDec *hpack.Decoder - decodedHeaders [][2]string + cc net.Conn // client conn + t testing.TB + group *synctestGroup + h1server *http.Server + h2server *Server + serverLogBuf safeBuffer // logger for httptest.Server + logFilter []string // substrings to filter out + scMu sync.Mutex // guards sc + sc *serverConn + testConnFramer // If http2debug!=2, then we capture Frame debug logs that will be written // to t.Log after a test fails. The read and write logs use separate locks @@ -101,23 +101,153 @@ func resetHooks() { testHookOnPanicMu.Unlock() } +func newTestServer(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *httptest.Server { + ts := httptest.NewUnstartedServer(handler) + ts.EnableHTTP2 = true + ts.Config.ErrorLog = log.New(twriter{t: t}, "", log.LstdFlags) + h2server := new(Server) + for _, opt := range opts { + switch v := opt.(type) { + case func(*httptest.Server): + v(ts) + case func(*http.Server): + v(ts.Config) + case func(*Server): + v(h2server) + default: + t.Fatalf("unknown newTestServer option type %T", v) + } + } + ConfigureServer(ts.Config, h2server) + + // ConfigureServer populates ts.Config.TLSConfig. + // Copy it to ts.TLS as well. + ts.TLS = ts.Config.TLSConfig + + // Go 1.22 changes the default minimum TLS version to TLS 1.2, + // in order to properly test cases where we want to reject low + // TLS versions, we need to explicitly configure the minimum + // version here. + ts.Config.TLSConfig.MinVersion = tls.VersionTLS10 + + ts.StartTLS() + t.Cleanup(func() { + ts.CloseClientConnections() + ts.Close() + }) + + return ts +} + type serverTesterOpt string -var optOnlyServer = serverTesterOpt("only_server") -var optQuiet = serverTesterOpt("quiet_logging") var optFramerReuseFrames = serverTesterOpt("frame_reuse_frames") +var optQuiet = func(server *http.Server) { + server.ErrorLog = log.New(io.Discard, "", 0) +} + func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *serverTester { + t.Helper() + g := newSynctest(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) + t.Cleanup(func() { + g.Close(t) + }) + + h1server := &http.Server{} + h2server := &Server{ + group: g, + } + tlsState := tls.ConnectionState{ + Version: tls.VersionTLS13, + ServerName: "go.dev", + CipherSuite: tls.TLS_AES_128_GCM_SHA256, + } + for _, opt := range opts { + switch v := opt.(type) { + case func(*Server): + v(h2server) + case func(*http.Server): + v(h1server) + case func(*tls.ConnectionState): + v(&tlsState) + default: + t.Fatalf("unknown newServerTester option type %T", v) + } + } + ConfigureServer(h1server, h2server) + + cli, srv := synctestNetPipe(g) + cli.SetReadDeadline(g.Now()) + cli.autoWait = true + + st := &serverTester{ + t: t, + cc: cli, + group: g, + h1server: h1server, + h2server: h2server, + } + st.hpackEnc = hpack.NewEncoder(&st.headerBuf) + if h1server.ErrorLog == nil { + h1server.ErrorLog = log.New(io.MultiWriter(stderrv(), twriter{t: t, st: st}, &st.serverLogBuf), "", log.LstdFlags) + } + + t.Cleanup(func() { + st.Close() + g.AdvanceTime(goAwayTimeout) // give server time to shut down + }) + + connc := make(chan *serverConn) + go func() { + g.Join() + h2server.serveConn(&netConnWithConnectionState{ + Conn: srv, + state: tlsState, + }, &ServeConnOpts{ + Handler: handler, + BaseConfig: h1server, + }, func(sc *serverConn) { + connc <- sc + }) + }() + st.sc = <-connc + + st.fr = NewFramer(st.cc, st.cc) + st.testConnFramer = testConnFramer{ + t: t, + fr: NewFramer(st.cc, st.cc), + dec: hpack.NewDecoder(initialHeaderTableSize, nil), + } + g.Wait() + return st +} + +type netConnWithConnectionState struct { + net.Conn + state tls.ConnectionState +} + +func (c *netConnWithConnectionState) ConnectionState() tls.ConnectionState { + return c.state +} + +// newServerTesterWithRealConn creates a test server listening on a localhost port. +// Mostly superseded by newServerTester, which creates a test server using a fake +// net.Conn and synthetic time. This function is still around because some benchmarks +// rely on it; new tests should use newServerTester. +func newServerTesterWithRealConn(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *serverTester { resetHooks() ts := httptest.NewUnstartedServer(handler) + t.Cleanup(ts.Close) tlsConfig := &tls.Config{ InsecureSkipVerify: true, NextProtos: []string{NextProtoTLS}, } - var onlyServer, quiet, framerReuseFrames bool + var framerReuseFrames bool h2server := new(Server) for _, opt := range opts { switch v := opt.(type) { @@ -125,14 +255,12 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} v(tlsConfig) case func(*httptest.Server): v(ts) + case func(*http.Server): + v(ts.Config) case func(*Server): v(h2server) case serverTesterOpt: switch v { - case optOnlyServer: - onlyServer = true - case optQuiet: - quiet = true case optFramerReuseFrames: framerReuseFrames = true } @@ -152,16 +280,12 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} ts.Config.TLSConfig.MinVersion = tls.VersionTLS10 st := &serverTester{ - t: t, - ts: ts, + t: t, } st.hpackEnc = hpack.NewEncoder(&st.headerBuf) - st.hpackDec = hpack.NewDecoder(initialHeaderTableSize, st.onHeaderField) ts.TLS = ts.Config.TLSConfig // the httptest.Server has its own copy of this TLS config - if quiet { - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) - } else { + if ts.Config.ErrorLog == nil { ts.Config.ErrorLog = log.New(io.MultiWriter(stderrv(), twriter{t: t, st: st}, &st.serverLogBuf), "", log.LstdFlags) } ts.StartTLS() @@ -175,36 +299,52 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} st.sc = v } log.SetOutput(io.MultiWriter(stderrv(), twriter{t: t, st: st})) - if !onlyServer { - cc, err := tls.Dial("tcp", ts.Listener.Addr().String(), tlsConfig) - if err != nil { - t.Fatal(err) + cc, err := tls.Dial("tcp", ts.Listener.Addr().String(), tlsConfig) + if err != nil { + t.Fatal(err) + } + st.cc = cc + st.testConnFramer = testConnFramer{ + t: t, + fr: NewFramer(st.cc, st.cc), + dec: hpack.NewDecoder(initialHeaderTableSize, nil), + } + if framerReuseFrames { + st.fr.SetReuseFrames() + } + if !logFrameReads && !logFrameWrites { + st.fr.debugReadLoggerf = func(m string, v ...interface{}) { + m = time.Now().Format("2006-01-02 15:04:05.999999999 ") + strings.TrimPrefix(m, "http2: ") + "\n" + st.frameReadLogMu.Lock() + fmt.Fprintf(&st.frameReadLogBuf, m, v...) + st.frameReadLogMu.Unlock() } - st.cc = cc - st.fr = NewFramer(cc, cc) - if framerReuseFrames { - st.fr.SetReuseFrames() - } - if !logFrameReads && !logFrameWrites { - st.fr.debugReadLoggerf = func(m string, v ...interface{}) { - m = time.Now().Format("2006-01-02 15:04:05.999999999 ") + strings.TrimPrefix(m, "http2: ") + "\n" - st.frameReadLogMu.Lock() - fmt.Fprintf(&st.frameReadLogBuf, m, v...) - st.frameReadLogMu.Unlock() - } - st.fr.debugWriteLoggerf = func(m string, v ...interface{}) { - m = time.Now().Format("2006-01-02 15:04:05.999999999 ") + strings.TrimPrefix(m, "http2: ") + "\n" - st.frameWriteLogMu.Lock() - fmt.Fprintf(&st.frameWriteLogBuf, m, v...) - st.frameWriteLogMu.Unlock() - } - st.fr.logReads = true - st.fr.logWrites = true + st.fr.debugWriteLoggerf = func(m string, v ...interface{}) { + m = time.Now().Format("2006-01-02 15:04:05.999999999 ") + strings.TrimPrefix(m, "http2: ") + "\n" + st.frameWriteLogMu.Lock() + fmt.Fprintf(&st.frameWriteLogBuf, m, v...) + st.frameWriteLogMu.Unlock() } + st.fr.logReads = true + st.fr.logWrites = true } return st } +// sync waits for all goroutines to idle. +func (st *serverTester) sync() { + st.group.Wait() +} + +// advance advances synthetic time by a duration. +func (st *serverTester) advance(d time.Duration) { + st.group.AdvanceTime(d) +} + +func (st *serverTester) authority() string { + return "dummy.tld" +} + func (st *serverTester) closeConn() { st.scMu.Lock() defer st.scMu.Unlock() @@ -280,7 +420,6 @@ func (st *serverTester) Close() { st.cc.Close() } } - st.ts.Close() if st.cc != nil { st.cc.Close() } @@ -290,13 +429,16 @@ func (st *serverTester) Close() { // greet initiates the client's HTTP/2 connection into a state where // frames may be sent. func (st *serverTester) greet() { + st.t.Helper() st.greetAndCheckSettings(func(Setting) error { return nil }) } func (st *serverTester) greetAndCheckSettings(checkSetting func(s Setting) error) { + st.t.Helper() st.writePreface() - st.writeInitialSettings() - st.wantSettings().ForeachSetting(checkSetting) + st.writeSettings() + st.sync() + readFrame[*SettingsFrame](st.t, st).ForeachSetting(checkSetting) st.writeSettingsAck() // The initial WINDOW_UPDATE and SETTINGS ACK can come in any order. @@ -304,9 +446,9 @@ func (st *serverTester) greetAndCheckSettings(checkSetting func(s Setting) error var gotWindowUpdate bool for i := 0; i < 2; i++ { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) + f := st.readFrame() + if f == nil { + st.t.Fatal("wanted a settings ACK and window update, got none") } switch f := f.(type) { case *SettingsFrame: @@ -348,34 +490,6 @@ func (st *serverTester) writePreface() { } } -func (st *serverTester) writeInitialSettings() { - if err := st.fr.WriteSettings(); err != nil { - if runtime.GOOS == "openbsd" && strings.HasSuffix(err.Error(), "write: broken pipe") { - st.t.Logf("Error writing initial SETTINGS frame from client to server: %v", err) - st.t.Skipf("Skipping test with known OpenBSD failure mode. (See https://go.dev/issue/52208.)") - } - st.t.Fatalf("Error writing initial SETTINGS frame from client to server: %v", err) - } -} - -func (st *serverTester) writeSettingsAck() { - if err := st.fr.WriteSettingsAck(); err != nil { - st.t.Fatalf("Error writing ACK of server's SETTINGS: %v", err) - } -} - -func (st *serverTester) writeHeaders(p HeadersFrameParam) { - if err := st.fr.WriteHeaders(p); err != nil { - st.t.Fatalf("Error writing HEADERS: %v", err) - } -} - -func (st *serverTester) writePriority(id uint32, p PriorityParam) { - if err := st.fr.WritePriority(id, p); err != nil { - st.t.Fatalf("Error writing PRIORITY: %v", err) - } -} - func (st *serverTester) encodeHeaderField(k, v string) { err := st.hpackEnc.WriteField(hpack.HeaderField{Name: k, Value: v}) if err != nil { @@ -409,7 +523,7 @@ func (st *serverTester) encodeHeader(headers ...string) []byte { } st.headerBuf.Reset() - defaultAuthority := st.ts.Listener.Addr().String() + defaultAuthority := st.authority() if len(headers) == 0 { // Fast path, mostly for benchmarks, so test code doesn't pollute @@ -474,144 +588,6 @@ func (st *serverTester) bodylessReq1(headers ...string) { }) } -func (st *serverTester) writeData(streamID uint32, endStream bool, data []byte) { - if err := st.fr.WriteData(streamID, endStream, data); err != nil { - st.t.Fatalf("Error writing DATA: %v", err) - } -} - -func (st *serverTester) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) { - if err := st.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil { - st.t.Fatalf("Error writing DATA: %v", err) - } -} - -// writeReadPing sends a PING and immediately reads the PING ACK. -// It will fail if any other unread data was pending on the connection. -func (st *serverTester) writeReadPing() { - data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} - if err := st.fr.WritePing(false, data); err != nil { - st.t.Fatalf("Error writing PING: %v", err) - } - p := st.wantPing() - if p.Flags&FlagPingAck == 0 { - st.t.Fatalf("got a PING, want a PING ACK") - } - if p.Data != data { - st.t.Fatalf("got PING data = %x, want %x", p.Data, data) - } -} - -func (st *serverTester) readFrame() (Frame, error) { - return st.fr.ReadFrame() -} - -func (st *serverTester) wantHeaders() *HeadersFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a HEADERS frame: %v", err) - } - hf, ok := f.(*HeadersFrame) - if !ok { - st.t.Fatalf("got a %T; want *HeadersFrame", f) - } - return hf -} - -func (st *serverTester) wantContinuation() *ContinuationFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a CONTINUATION frame: %v", err) - } - cf, ok := f.(*ContinuationFrame) - if !ok { - st.t.Fatalf("got a %T; want *ContinuationFrame", f) - } - return cf -} - -func (st *serverTester) wantData() *DataFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a DATA frame: %v", err) - } - df, ok := f.(*DataFrame) - if !ok { - st.t.Fatalf("got a %T; want *DataFrame", f) - } - return df -} - -func (st *serverTester) wantSettings() *SettingsFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a SETTINGS frame: %v", err) - } - sf, ok := f.(*SettingsFrame) - if !ok { - st.t.Fatalf("got a %T; want *SettingsFrame", f) - } - return sf -} - -func (st *serverTester) wantPing() *PingFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a PING frame: %v", err) - } - pf, ok := f.(*PingFrame) - if !ok { - st.t.Fatalf("got a %T; want *PingFrame", f) - } - return pf -} - -func (st *serverTester) wantGoAway() *GoAwayFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a GOAWAY frame: %v", err) - } - gf, ok := f.(*GoAwayFrame) - if !ok { - st.t.Fatalf("got a %T; want *GoAwayFrame", f) - } - return gf -} - -func (st *serverTester) wantRSTStream(streamID uint32, errCode ErrCode) { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting an RSTStream frame: %v", err) - } - rs, ok := f.(*RSTStreamFrame) - if !ok { - st.t.Fatalf("got a %T; want *RSTStreamFrame", f) - } - if rs.FrameHeader.StreamID != streamID { - st.t.Fatalf("RSTStream StreamID = %d; want %d", rs.FrameHeader.StreamID, streamID) - } - if rs.ErrCode != errCode { - st.t.Fatalf("RSTStream ErrCode = %d (%s); want %d (%s)", rs.ErrCode, rs.ErrCode, errCode, errCode) - } -} - -func (st *serverTester) wantWindowUpdate(streamID, incr uint32) { - f, err := st.readFrame() - if err != nil { - st.t.Fatalf("Error while expecting a WINDOW_UPDATE frame: %v", err) - } - wu, ok := f.(*WindowUpdateFrame) - if !ok { - st.t.Fatalf("got a %T; want *WindowUpdateFrame", f) - } - if wu.FrameHeader.StreamID != streamID { - st.t.Fatalf("WindowUpdate StreamID = %d; want %d", wu.FrameHeader.StreamID, streamID) - } - if wu.Increment != incr { - st.t.Fatalf("WindowUpdate increment = %d; want %d", wu.Increment, incr) - } -} - func (st *serverTester) wantFlowControlConsumed(streamID, consumed int32) { var initial int32 if streamID == 0 { @@ -634,32 +610,6 @@ func (st *serverTester) wantFlowControlConsumed(streamID, consumed int32) { <-donec } -func (st *serverTester) wantSettingsAck() { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } - sf, ok := f.(*SettingsFrame) - if !ok { - st.t.Fatalf("Wanting a settings ACK, received a %T", f) - } - if !sf.Header().Flags.Has(FlagSettingsAck) { - st.t.Fatal("Settings Frame didn't have ACK set") - } -} - -func (st *serverTester) wantPushPromise() *PushPromiseFrame { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } - ppf, ok := f.(*PushPromiseFrame) - if !ok { - st.t.Fatalf("Wanted PushPromise, received %T", ppf) - } - return ppf -} - func TestServer(t *testing.T) { gotReq := make(chan bool, 1) st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { @@ -668,12 +618,6 @@ func TestServer(t *testing.T) { }) defer st.Close() - covers("3.5", ` - The server connection preface consists of a potentially empty - SETTINGS frame ([SETTINGS]) that MUST be the first frame the - server sends in the HTTP/2 connection. - `) - st.greet() st.writeHeaders(HeadersFrameParam{ StreamID: 1, // clients send odd numbers @@ -866,7 +810,7 @@ func testBodyContents(t *testing.T, wantContentLength int64, wantBody string, wr if r.ContentLength != wantContentLength { t.Errorf("ContentLength = %v; want %d", r.ContentLength, wantContentLength) } - all, err := ioutil.ReadAll(r.Body) + all, err := io.ReadAll(r.Body) if err != nil { t.Fatal(err) } @@ -887,7 +831,7 @@ func testBodyContentsFail(t *testing.T, wantContentLength int64, wantReadError s if r.ContentLength != wantContentLength { t.Errorf("ContentLength = %v; want %d", r.ContentLength, wantContentLength) } - all, err := ioutil.ReadAll(r.Body) + all, err := io.ReadAll(r.Body) if err == nil { t.Fatalf("expected an error (%q) reading from the body. Successfully read %q instead.", wantReadError, all) @@ -1095,37 +1039,32 @@ func testRejectRequest(t *testing.T, send func(*serverTester)) { st.wantRSTStream(1, ErrCodeProtocol) } -func testRejectRequestWithProtocolError(t *testing.T, send func(*serverTester)) { +func newServerTesterForError(t *testing.T) *serverTester { + t.Helper() st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { t.Error("server request made it to handler; should've been rejected") }, optQuiet) - defer st.Close() - st.greet() - send(st) - gf := st.wantGoAway() - if gf.ErrCode != ErrCodeProtocol { - t.Errorf("err code = %v; want %v", gf.ErrCode, ErrCodeProtocol) - } + return st } // Section 5.1, on idle connections: "Receiving any frame other than // HEADERS or PRIORITY on a stream in this state MUST be treated as a // connection error (Section 5.4.1) of type PROTOCOL_ERROR." func TestRejectFrameOnIdle_WindowUpdate(t *testing.T) { - testRejectRequestWithProtocolError(t, func(st *serverTester) { - st.fr.WriteWindowUpdate(123, 456) - }) + st := newServerTesterForError(t) + st.fr.WriteWindowUpdate(123, 456) + st.wantGoAway(123, ErrCodeProtocol) } func TestRejectFrameOnIdle_Data(t *testing.T) { - testRejectRequestWithProtocolError(t, func(st *serverTester) { - st.fr.WriteData(123, true, nil) - }) + st := newServerTesterForError(t) + st.fr.WriteData(123, true, nil) + st.wantGoAway(123, ErrCodeProtocol) } func TestRejectFrameOnIdle_RSTStream(t *testing.T) { - testRejectRequestWithProtocolError(t, func(st *serverTester) { - st.fr.WriteRSTStream(123, ErrCodeCancel) - }) + st := newServerTesterForError(t) + st.fr.WriteRSTStream(123, ErrCodeCancel) + st.wantGoAway(123, ErrCodeProtocol) } func TestServer_Request_Connect(t *testing.T) { @@ -1199,7 +1138,7 @@ func TestServer_Ping(t *testing.T) { t.Fatal(err) } - pf := st.wantPing() + pf := readFrame[*PingFrame](t, st) if !pf.Flags.Has(FlagPingAck) { t.Error("response ping doesn't have ACK set") } @@ -1222,38 +1161,36 @@ func (l *filterListener) Accept() (net.Conn, error) { } func TestServer_MaxQueuedControlFrames(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } + // Goroutine debugging makes this test very slow. + disableGoroutineTracking(t) - st := newServerTester(t, nil, func(ts *httptest.Server) { - // TCP buffer sizes on test systems aren't under our control and can be large. - // Create a conn that blocks after 10000 bytes written. - ts.Listener = &filterListener{ - Listener: ts.Listener, - accept: func(conn net.Conn) (net.Conn, error) { - return newBlockingWriteConn(conn, 10000), nil - }, - } - }) - defer st.Close() + st := newServerTester(t, nil) st.greet() - const extraPings = 500000 // enough to fill the TCP buffers + st.cc.(*synctestNetConn).SetReadBufferSize(0) // all writes block + st.cc.(*synctestNetConn).autoWait = false // don't sync after every write + // Send maxQueuedControlFrames pings, plus a few extra + // to account for ones that enter the server's write buffer. + const extraPings = 2 for i := 0; i < maxQueuedControlFrames+extraPings; i++ { pingData := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} - if err := st.fr.WritePing(false, pingData); err != nil { - if i == 0 { - t.Fatal(err) - } - // We expect the connection to get closed by the server when the TCP - // buffer fills up and the write queue reaches MaxQueuedControlFrames. - t.Logf("sent %d PING frames", i) - return + st.fr.WritePing(false, pingData) + } + st.group.Wait() + + // Unblock the server. + // It should have closed the connection after exceeding the control frame limit. + st.cc.(*synctestNetConn).SetReadBufferSize(math.MaxInt) + + st.advance(goAwayTimeout) + // Some frames may have persisted in the server's buffers. + for i := 0; i < 10; i++ { + if st.readFrame() == nil { + break } } - t.Errorf("unexpected success sending all PING frames") + st.wantClosed() } func TestServer_RejectsLargeFrames(t *testing.T) { @@ -1269,15 +1206,9 @@ func TestServer_RejectsLargeFrames(t *testing.T) { // will only read the first 9 bytes (the headre) and then disconnect. st.fr.WriteRawFrame(0xff, 0, 0, make([]byte, defaultMaxReadFrameSize+1)) - gf := st.wantGoAway() - if gf.ErrCode != ErrCodeFrameSize { - t.Errorf("GOAWAY err = %v; want %v", gf.ErrCode, ErrCodeFrameSize) - } - if st.serverLogBuf.Len() != 0 { - // Previously we spun here for a bit until the GOAWAY disconnect - // timer fired, logging while we fired. - t.Errorf("unexpected server output: %.500s\n", st.serverLogBuf.Bytes()) - } + st.wantGoAway(0, ErrCodeFrameSize) + st.advance(goAwayTimeout) + st.wantClosed() } func TestServer_Handler_Sends_WindowUpdate(t *testing.T) { @@ -1303,7 +1234,6 @@ func TestServer_Handler_Sends_WindowUpdate(t *testing.T) { EndStream: false, // data coming EndHeaders: true, }) - st.writeReadPing() // Write less than half the max window of data and consume it. // The server doesn't return flow control yet, buffering the 1024 bytes to @@ -1311,20 +1241,17 @@ func TestServer_Handler_Sends_WindowUpdate(t *testing.T) { data := make([]byte, windowSize) st.writeData(1, false, data[:1024]) puppet.do(readBodyHandler(t, string(data[:1024]))) - st.writeReadPing() // Write up to the window limit. // The server returns the buffered credit. st.writeData(1, false, data[1024:]) st.wantWindowUpdate(0, 1024) st.wantWindowUpdate(1, 1024) - st.writeReadPing() // The handler consumes the data and the server returns credit. puppet.do(readBodyHandler(t, string(data[1024:]))) st.wantWindowUpdate(0, windowSize-1024) st.wantWindowUpdate(1, windowSize-1024) - st.writeReadPing() } // the version of the TestServer_Handler_Sends_WindowUpdate with padding. @@ -1348,7 +1275,6 @@ func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) { EndStream: false, EndHeaders: true, }) - st.writeReadPing() // Write half a window of data, with some padding. // The server doesn't return the padding yet, buffering the 5 bytes to combine @@ -1356,7 +1282,6 @@ func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) { data := make([]byte, windowSize/2) pad := make([]byte, 4) st.writeDataPadded(1, false, data, pad) - st.writeReadPing() // The handler consumes the body. // The server returns flow control for the body and padding @@ -1373,13 +1298,7 @@ func TestServer_Send_GoAway_After_Bogus_WindowUpdate(t *testing.T) { if err := st.fr.WriteWindowUpdate(0, 1<<31-1); err != nil { t.Fatal(err) } - gf := st.wantGoAway() - if gf.ErrCode != ErrCodeFlowControl { - t.Errorf("GOAWAY err = %v; want %v", gf.ErrCode, ErrCodeFlowControl) - } - if gf.LastStreamID != 0 { - t.Errorf("GOAWAY last stream ID = %v; want %v", gf.LastStreamID, 0) - } + st.wantGoAway(0, ErrCodeFlowControl) } func TestServer_Send_RstStream_After_Bogus_WindowUpdate(t *testing.T) { @@ -1586,10 +1505,10 @@ func TestServer_StateTransitions(t *testing.T) { st.writeData(1, true, nil) leaveHandler <- true - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Fatal("expected END_STREAM flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) if got, want := st.streamState(1), stateClosed; got != want { t.Errorf("at end, state is %v; want %v", got, want) @@ -1601,97 +1520,101 @@ func TestServer_StateTransitions(t *testing.T) { // test HEADERS w/o EndHeaders + another HEADERS (should get rejected) func TestServer_Rejects_HeadersNoEnd_Then_Headers(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: false, - }) - st.writeHeaders(HeadersFrameParam{ // Not a continuation. - StreamID: 3, // different stream. - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: true, - }) + st := newServerTesterForError(t) + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: false, + }) + st.writeHeaders(HeadersFrameParam{ // Not a continuation. + StreamID: 3, // different stream. + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, }) + st.wantGoAway(0, ErrCodeProtocol) } // test HEADERS w/o EndHeaders + PING (should get rejected) func TestServer_Rejects_HeadersNoEnd_Then_Ping(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: false, - }) - if err := st.fr.WritePing(false, [8]byte{}); err != nil { - t.Fatal(err) - } + st := newServerTesterForError(t) + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: false, }) + if err := st.fr.WritePing(false, [8]byte{}); err != nil { + t.Fatal(err) + } + st.wantGoAway(0, ErrCodeProtocol) } // test HEADERS w/ EndHeaders + a continuation HEADERS (should get rejected) func TestServer_Rejects_HeadersEnd_Then_Continuation(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: true, - }) - st.wantHeaders() - if err := st.fr.WriteContinuation(1, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { - t.Fatal(err) - } + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, optQuiet) + st.greet() + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) + if err := st.fr.WriteContinuation(1, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { + t.Fatal(err) + } + st.wantGoAway(1, ErrCodeProtocol) } // test HEADERS w/o EndHeaders + a continuation HEADERS on wrong stream ID func TestServer_Rejects_HeadersNoEnd_Then_ContinuationWrongStream(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.writeHeaders(HeadersFrameParam{ - StreamID: 1, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: false, - }) - if err := st.fr.WriteContinuation(3, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { - t.Fatal(err) - } + st := newServerTesterForError(t) + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: false, }) + if err := st.fr.WriteContinuation(3, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil { + t.Fatal(err) + } + st.wantGoAway(0, ErrCodeProtocol) } // No HEADERS on stream 0. func TestServer_Rejects_Headers0(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.fr.AllowIllegalWrites = true - st.writeHeaders(HeadersFrameParam{ - StreamID: 0, - BlockFragment: st.encodeHeader(), - EndStream: true, - EndHeaders: true, - }) + st := newServerTesterForError(t) + st.fr.AllowIllegalWrites = true + st.writeHeaders(HeadersFrameParam{ + StreamID: 0, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, }) + st.wantGoAway(0, ErrCodeProtocol) } // No CONTINUATION on stream 0. func TestServer_Rejects_Continuation0(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.fr.AllowIllegalWrites = true - if err := st.fr.WriteContinuation(0, true, st.encodeHeader()); err != nil { - t.Fatal(err) - } - }) + st := newServerTesterForError(t) + st.fr.AllowIllegalWrites = true + if err := st.fr.WriteContinuation(0, true, st.encodeHeader()); err != nil { + t.Fatal(err) + } + st.wantGoAway(0, ErrCodeProtocol) } // No PRIORITY on stream 0. func TestServer_Rejects_Priority0(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - st.fr.AllowIllegalWrites = true - st.writePriority(0, PriorityParam{StreamDep: 1}) - }) + st := newServerTesterForError(t) + st.fr.AllowIllegalWrites = true + st.writePriority(0, PriorityParam{StreamDep: 1}) + st.wantGoAway(0, ErrCodeProtocol) } // No HEADERS frame with a self-dependence. @@ -1717,36 +1640,15 @@ func TestServer_Rejects_PrioritySelfDependence(t *testing.T) { } func TestServer_Rejects_PushPromise(t *testing.T) { - testServerRejectsConn(t, func(st *serverTester) { - pp := PushPromiseParam{ - StreamID: 1, - PromiseID: 3, - } - if err := st.fr.WritePushPromise(pp); err != nil { - t.Fatal(err) - } - }) -} - -// testServerRejectsConn tests that the server hangs up with a GOAWAY -// frame and a server close after the client does something -// deserving a CONNECTION_ERROR. -func testServerRejectsConn(t *testing.T, writeReq func(*serverTester)) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}) - st.addLogFilter("connection error: PROTOCOL_ERROR") - defer st.Close() - st.greet() - writeReq(st) - - st.wantGoAway() - - fr, err := st.fr.ReadFrame() - if err == nil { - t.Errorf("ReadFrame got frame of type %T; want io.EOF", fr) + st := newServerTesterForError(t) + pp := PushPromiseParam{ + StreamID: 1, + PromiseID: 3, } - if err != io.EOF { - t.Errorf("ReadFrame = %v; want io.EOF", err) + if err := st.fr.WritePushPromise(pp); err != nil { + t.Fatal(err) } + st.wantGoAway(1, ErrCodeProtocol) } // testServerRejectsStream tests that the server sends a RST_STREAM with the provided @@ -1786,13 +1688,10 @@ func TestServer_Response_NoData(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Fatal("want END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) }) } @@ -1802,22 +1701,15 @@ func TestServer_Response_NoData_Header_FooBar(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Fatal("want END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"foo-bar", "some-value"}, - {"content-length", "0"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "foo-bar": []string{"some-value"}, + "content-length": []string{"0"}, + }, + }) }) } @@ -1862,15 +1754,14 @@ func TestServerIgnoresContentLengthSignWhenWritingChunks(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-length", tt.wantCL}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("For case %q, value %q, got = %q; want %q", tt.name, tt.cl, goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "content-length": []string{tt.wantCL}, + }, + }) }) } } @@ -1940,29 +1831,20 @@ func TestServer_Response_Data_Sniff_DoesntOverride(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("don't want END_STREAM, expecting data") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "foo/bar"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } - df := st.wantData() - if !df.StreamEnded() { - t.Error("expected DATA to have END_STREAM flag") - } - if got := string(df.Data()); got != msg { - t.Errorf("got DATA %q; want %q", got, msg) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"foo/bar"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(msg), + }) }) } @@ -1974,16 +1856,15 @@ func TestServer_Response_TransferEncoding_chunked(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) }) } @@ -1996,22 +1877,15 @@ func TestServer_Response_Data_IgnoreHeaderAfterWrite_After(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) }) } @@ -2025,23 +1899,16 @@ func TestServer_Response_Data_IgnoreHeaderAfterWrite_Overwrite(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"foo", "proper value"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "foo": []string{"proper value"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) }) } @@ -2052,29 +1919,20 @@ func TestServer_Response_Data_SniffLenType(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("don't want END_STREAM, expecting data") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", strconv.Itoa(len(msg))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } - df := st.wantData() - if !df.StreamEnded() { - t.Error("expected DATA to have END_STREAM flag") - } - if got := string(df.Data()); got != msg { - t.Errorf("got DATA %q; want %q", got, msg) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(msg))}, + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(msg), + }) }) } @@ -2088,40 +1946,25 @@ func TestServer_Response_Header_Flush_MidWrite(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/html; charset=utf-8"}, // sniffed - // and no content-length - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } - { - df := st.wantData() - if df.StreamEnded() { - t.Error("unexpected END_STREAM flag") - } - if got := string(df.Data()); got != msg { - t.Errorf("got DATA %q; want %q", got, msg) - } - } - { - df := st.wantData() - if !df.StreamEnded() { - t.Error("wanted END_STREAM flag on last data chunk") - } - if got := string(df.Data()); got != msg2 { - t.Errorf("got DATA %q; want %q", got, msg2) - } - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/html; charset=utf-8"}, // sniffed + // and no content-length + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: false, + data: []byte(msg), + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(msg2), + }) }) } @@ -2157,25 +2000,18 @@ func TestServer_Response_LargeWrite(t *testing.T) { if err := st.fr.WriteWindowUpdate(0, size); err != nil { t.Fatal(err) } - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, // sniffed - // and no content-length - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, // sniffed + // and no content-length + }, + }) var bytes, frames int for { - df := st.wantData() + df := readFrame[*DataFrame](t, st) bytes += len(df.Data()) frames++ for _, b := range df.Data() { @@ -2226,27 +2062,26 @@ func TestServer_Response_LargeWrite_FlowControlled(t *testing.T) { getSlash(st) // make the single request - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) - df := st.wantData() - if got := len(df.Data()); got != reads[0] { - t.Fatalf("Initial window size = %d but got DATA with %d bytes", reads[0], got) - } + st.wantData(wantData{ + streamID: 1, + endStream: false, + size: reads[0], + }) - for _, quota := range reads[1:] { + for i, quota := range reads[1:] { if err := st.fr.WriteWindowUpdate(1, uint32(quota)); err != nil { t.Fatal(err) } - df := st.wantData() - if int(quota) != len(df.Data()) { - t.Fatalf("read %d bytes after giving %d quota", len(df.Data()), quota) - } + st.wantData(wantData{ + streamID: 1, + endStream: i == len(reads[1:])-1, + size: quota, + }) } }) } @@ -2273,13 +2108,10 @@ func TestServer_Response_RST_Unblocks_LargeWrite(t *testing.T) { getSlash(st) // make the single request - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil { t.Fatal(err) @@ -2301,21 +2133,16 @@ func TestServer_Response_Empty_Data_Not_FlowControlled(t *testing.T) { getSlash(st) // make the single request - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) - df := st.wantData() - if got := len(df.Data()); got != 0 { - t.Fatalf("unexpected %d DATA bytes; want 0", got) - } - if !df.StreamEnded() { - t.Fatal("DATA didn't have END_STREAM") - } + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) }) } @@ -2340,49 +2167,33 @@ func TestServer_Response_Automatic100Continue(t *testing.T) { EndStream: false, EndHeaders: true, }) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "100"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Fatalf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"100"}, + }, + }) // Okay, they sent status 100, so we can send our // gigantic and/or sensitive "foo" payload now. st.writeData(1, true, []byte(msg)) - hf = st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("expected data to follow") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", strconv.Itoa(len(reply))}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } - - df := st.wantData() - if string(df.Data()) != reply { - t.Errorf("Client read %q; want %q", df.Data(), reply) - } - if !df.StreamEnded() { - t.Errorf("expect data stream end") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{strconv.Itoa(len(reply))}, + }, + }) + + st.wantData(wantData{ + streamID: 1, + endStream: true, + data: []byte(reply), + }) }) } @@ -2404,13 +2215,10 @@ func TestServer_HandlerWriteErrorOnDisconnect(t *testing.T) { EndStream: false, EndHeaders: true, }) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("unexpected END_STREAM flag") - } - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) // Close the connection and wait for the handler to (hopefully) notice. st.cc.Close() _ = <-errc @@ -2431,6 +2239,11 @@ func TestServer_Rejects_Too_Many_Streams(t *testing.T) { <-leaveHandler }) defer st.Close() + + // Automatically syncing after every write / before every read + // slows this test down substantially. + st.cc.(*synctestNetConn).autoWait = false + st.greet() nextStreamID := uint32(1) streamID := func() uint32 { @@ -2470,11 +2283,16 @@ func TestServer_Rejects_Too_Many_Streams(t *testing.T) { if err := st.fr.WriteContinuation(rejectID, true, frag2); err != nil { t.Fatal(err) } + st.sync() st.wantRSTStream(rejectID, ErrCodeProtocol) // But let a handler finish: leaveHandler <- true - st.wantHeaders() + st.sync() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) // And now another stream should be able to start: goodID := streamID() @@ -2494,14 +2312,14 @@ func TestServer_Response_ManyHeaders_With_Continuation(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() + hf := readFrame[*HeadersFrame](t, st) if hf.HeadersEnded() { t.Fatal("got unwanted END_HEADERS flag") } n := 0 for { n++ - cf := st.wantContinuation() + cf := readFrame[*ContinuationFrame](t, st) if cf.HeadersEnded() { break } @@ -2530,10 +2348,10 @@ func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) { EndStream: false, // DATA is coming EndHeaders: true, }) - hf := st.wantHeaders() - if !hf.HeadersEnded() || !hf.StreamEnded() { - t.Fatalf("want END_HEADERS+END_STREAM, got %v", hf) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) // Sent when the a Handler closes while a client has // indicated it's still sending DATA: @@ -2588,79 +2406,51 @@ func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) { func TestServer_Rejects_TLS10(t *testing.T) { testRejectTLS(t, tls.VersionTLS10) } func TestServer_Rejects_TLS11(t *testing.T) { testRejectTLS(t, tls.VersionTLS11) } -func testRejectTLS(t *testing.T, max uint16) { - st := newServerTester(t, nil, func(c *tls.Config) { +func testRejectTLS(t *testing.T, version uint16) { + st := newServerTester(t, nil, func(state *tls.ConnectionState) { // As of 1.18 the default minimum Go TLS version is // 1.2. In order to test rejection of lower versions, - // manually set the minimum version to 1.0 - c.MinVersion = tls.VersionTLS10 - c.MaxVersion = max + // manually set the version to 1.0 + state.Version = version }) defer st.Close() - gf := st.wantGoAway() - if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want { - t.Errorf("Got error code %v; want %v", got, want) - } + st.wantGoAway(0, ErrCodeInadequateSecurity) } func TestServer_Rejects_TLSBadCipher(t *testing.T) { - st := newServerTester(t, nil, func(c *tls.Config) { - // All TLS 1.3 ciphers are good. Test with TLS 1.2. - c.MaxVersion = tls.VersionTLS12 - // Only list bad ones: - c.CipherSuites = []uint16{ - tls.TLS_RSA_WITH_RC4_128_SHA, - tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, - tls.TLS_RSA_WITH_AES_128_CBC_SHA, - tls.TLS_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, - tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - cipher_TLS_RSA_WITH_AES_128_CBC_SHA256, - } + st := newServerTester(t, nil, func(state *tls.ConnectionState) { + state.Version = tls.VersionTLS12 + state.CipherSuite = tls.TLS_RSA_WITH_RC4_128_SHA }) defer st.Close() - gf := st.wantGoAway() - if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want { - t.Errorf("Got error code %v; want %v", got, want) - } + st.wantGoAway(0, ErrCodeInadequateSecurity) } func TestServer_Advertises_Common_Cipher(t *testing.T) { - const requiredSuite = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - st := newServerTester(t, nil, func(c *tls.Config) { - // Have the client only support the one required by the spec. - c.CipherSuites = []uint16{requiredSuite} - }, func(ts *httptest.Server) { - var srv *http.Server = ts.Config + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + }, func(srv *http.Server) { // Have the server configured with no specific cipher suites. // This tests that Go's defaults include the required one. srv.TLSConfig = nil }) - defer st.Close() - st.greet() -} -func (st *serverTester) onHeaderField(f hpack.HeaderField) { - if f.Name == "date" { - return - } - st.decodedHeaders = append(st.decodedHeaders, [2]string{f.Name, f.Value}) -} + // Have the client only support the one required by the spec. + const requiredSuite = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + tlsConfig := tlsConfigInsecure.Clone() + tlsConfig.MaxVersion = tls.VersionTLS12 + tlsConfig.CipherSuites = []uint16{requiredSuite} + tr := &Transport{TLSClientConfig: tlsConfig} + defer tr.CloseIdleConnections() -func (st *serverTester) decodeHeader(headerBlock []byte) (pairs [][2]string) { - st.decodedHeaders = nil - if _, err := st.hpackDec.Write(headerBlock); err != nil { - st.t.Fatalf("hpack decoding error: %v", err) + req, err := http.NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatal(err) } - if err := st.hpackDec.Close(); err != nil { - st.t.Fatalf("hpack decoding error: %v", err) + res, err := tr.RoundTrip(req) + if err != nil { + t.Fatal(err) } - return st.decodedHeaders + res.Body.Close() } // testServerResponse sets up an idle HTTP/2 connection. The client function should @@ -2804,19 +2594,15 @@ func TestServerDoS_MaxHeaderListSize(t *testing.T) { st.fr.WriteContinuation(1, len(b) == 0, chunk) } - h := st.wantHeaders() - if !h.HeadersEnded() { - t.Fatalf("Got HEADERS without END_HEADERS set: %v", h) - } - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "431"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", "63"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"431"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{"63"}, + }, + }) } func TestServer_Response_Stream_With_Missing_Trailer(t *testing.T) { @@ -2825,17 +2611,15 @@ func TestServer_Response_Stream_With_Missing_Trailer(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.HeadersEnded() { - t.Fatal("want END_HEADERS flag") - } - df := st.wantData() - if len(df.data) != 0 { - t.Fatal("did not want data") - } - if !df.StreamEnded() { - t.Fatal("want END_STREAM flag") - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) }) } @@ -2844,8 +2628,8 @@ func TestCompressionErrorOnWrite(t *testing.T) { var serverConfig *http.Server st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { // No response body. - }, func(ts *httptest.Server) { - serverConfig = ts.Config + }, func(s *http.Server) { + serverConfig = s serverConfig.MaxHeaderBytes = maxStrLen }) st.addLogFilter("connection error: COMPRESSION_ERROR") @@ -2873,20 +2657,16 @@ func TestCompressionErrorOnWrite(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - if !h.HeadersEnded() { - t.Fatalf("Got HEADERS without END_HEADERS set: %v", h) - } - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "431"}, - {"content-type", "text/html; charset=utf-8"}, - {"content-length", "63"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } - df := st.wantData() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"431"}, + "content-type": []string{"text/html; charset=utf-8"}, + "content-length": []string{"63"}, + }, + }) + df := readFrame[*DataFrame](t, st) if !strings.Contains(string(df.Data()), "HTTP Error 431") { t.Errorf("Unexpected data body: %q", df.Data()) } @@ -2902,10 +2682,7 @@ func TestCompressionErrorOnWrite(t *testing.T) { EndStream: true, EndHeaders: true, }) - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeCompression { - t.Errorf("GOAWAY err = %v; want ErrCodeCompression", ga.ErrCode) - } + st.wantGoAway(3, ErrCodeCompression) } func TestCompressionErrorOnClose(t *testing.T) { @@ -2924,10 +2701,7 @@ func TestCompressionErrorOnClose(t *testing.T) { EndStream: true, EndHeaders: true, }) - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeCompression { - t.Errorf("GOAWAY err = %v; want ErrCodeCompression", ga.ErrCode) - } + st.wantGoAway(1, ErrCodeCompression) } // test that a server handler can read trailers from a client @@ -2962,7 +2736,7 @@ func TestServerReadsTrailers(t *testing.T) { if !reflect.DeepEqual(r.Trailer, wantTrailer) { t.Errorf("initial Trailer = %v; want %v", r.Trailer, wantTrailer) } - slurp, err := ioutil.ReadAll(r.Body) + slurp, err := io.ReadAll(r.Body) if string(slurp) != testBody { t.Errorf("read body %q; want %q", slurp, testBody) } @@ -3017,66 +2791,51 @@ func testServerWritesTrailers(t *testing.T, withFlush bool) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if hf.StreamEnded() { - t.Fatal("response HEADERS had END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("response HEADERS didn't have END_HEADERS") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"foo", "Bar"}, - {"trailer", "Server-Trailer-A, Server-Trailer-B"}, - {"trailer", "Server-Trailer-C"}, - {"trailer", "Transfer-Encoding, Content-Length, Trailer"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", "5"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Header mismatch.\n got: %v\nwant: %v", goth, wanth) - } - df := st.wantData() - if string(df.Data()) != "Hello" { - t.Fatalf("Client read %q; want Hello", df.Data()) - } - if df.StreamEnded() { - t.Fatalf("data frame had STREAM_ENDED") - } - tf := st.wantHeaders() // for the trailers - if !tf.StreamEnded() { - t.Fatalf("trailers HEADERS lacked END_STREAM") - } - if !tf.HeadersEnded() { - t.Fatalf("trailers HEADERS lacked END_HEADERS") - } - wanth = [][2]string{ - {"post-header-trailer", "hi1"}, - {"post-header-trailer2", "hi2"}, - {"server-trailer-a", "valuea"}, - {"server-trailer-c", "valuec"}, - } - goth = st.decodeHeader(tf.HeaderBlockFragment()) - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Header mismatch.\n got: %v\nwant: %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "foo": []string{"Bar"}, + "trailer": []string{ + "Server-Trailer-A, Server-Trailer-B", + "Server-Trailer-C", + "Transfer-Encoding, Content-Length, Trailer", + }, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{"5"}, + }, + }) + st.wantData(wantData{ + streamID: 1, + endStream: false, + data: []byte("Hello"), + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + "post-header-trailer": []string{"hi1"}, + "post-header-trailer2": []string{"hi2"}, + "server-trailer-a": []string{"valuea"}, + "server-trailer-c": []string{"valuec"}, + }, + }) }) } func TestServerWritesUndeclaredTrailers(t *testing.T) { const trailer = "Trailer-Header" const value = "hi1" - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.Header().Set(http.TrailerPrefix+trailer, value) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() cl := &http.Client{Transport: tr} - resp, err := cl.Get(st.ts.URL) + resp, err := cl.Get(ts.URL) if err != nil { t.Fatal(err) } @@ -3099,31 +2858,24 @@ func TestServerDoesntWriteInvalidHeaders(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - if !hf.StreamEnded() { - t.Error("response HEADERS lacked END_STREAM") - } - if !hf.HeadersEnded() { - t.Fatal("response HEADERS didn't have END_HEADERS") - } - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"ok1", "x"}, - {"content-length", "0"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Header mismatch.\n got: %v\nwant: %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "ok1": []string{"x"}, + "content-length": []string{"0"}, + }, + }) }) } func BenchmarkServerGets(b *testing.B) { - defer disableGoroutineTracking()() + disableGoroutineTracking(b) b.ReportAllocs() const msg = "Hello, world" - st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) { + st := newServerTesterWithRealConn(b, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, msg) }) defer st.Close() @@ -3142,24 +2894,28 @@ func BenchmarkServerGets(b *testing.B) { EndStream: true, EndHeaders: true, }) - st.wantHeaders() - df := st.wantData() - if !df.StreamEnded() { - b.Fatalf("DATA didn't have END_STREAM; got %v", df) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) } } func BenchmarkServerPosts(b *testing.B) { - defer disableGoroutineTracking()() + disableGoroutineTracking(b) b.ReportAllocs() const msg = "Hello, world" - st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) { + st := newServerTesterWithRealConn(b, func(w http.ResponseWriter, r *http.Request) { // Consume the (empty) body from th peer before replying, otherwise // the server will sometimes (depending on scheduling) send the peer a // a RST_STREAM with the CANCEL error code. - if n, err := io.Copy(ioutil.Discard, r.Body); n != 0 || err != nil { + if n, err := io.Copy(io.Discard, r.Body); n != 0 || err != nil { b.Errorf("Copy error; got %v, %v; want 0, nil", n, err) } io.WriteString(w, msg) @@ -3181,11 +2937,15 @@ func BenchmarkServerPosts(b *testing.B) { EndHeaders: true, }) st.writeData(id, true, nil) - st.wantHeaders() - df := st.wantData() - if !df.StreamEnded() { - b.Fatalf("DATA didn't have END_STREAM; got %v", df) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) + st.wantData(wantData{ + streamID: 1, + endStream: true, + size: 0, + }) } } @@ -3203,7 +2963,7 @@ func BenchmarkServerToClientStreamReuseFrames(b *testing.B) { } func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { - defer disableGoroutineTracking()() + disableGoroutineTracking(b) b.ReportAllocs() const msgLen = 1 // default window size @@ -3219,11 +2979,11 @@ func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { return msg } - st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) { + st := newServerTesterWithRealConn(b, func(w http.ResponseWriter, r *http.Request) { // Consume the (empty) body from th peer before replying, otherwise // the server will sometimes (depending on scheduling) send the peer a // a RST_STREAM with the CANCEL error code. - if n, err := io.Copy(ioutil.Discard, r.Body); n != 0 || err != nil { + if n, err := io.Copy(io.Discard, r.Body); n != 0 || err != nil { b.Errorf("Copy error; got %v, %v; want 0, nil", n, err) } for i := 0; i < b.N; i += 1 { @@ -3244,18 +3004,22 @@ func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { }) st.writeData(id, true, nil) - st.wantHeaders() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) var pendingWindowUpdate = uint32(0) for i := 0; i < b.N; i += 1 { expected := nextMsg(i) - df := st.wantData() - if bytes.Compare(expected, df.data) != 0 { - b.Fatalf("Bad message received; want %v; got %v", expected, df.data) - } + st.wantData(wantData{ + streamID: 1, + endStream: false, + data: expected, + }) // try to send infrequent but large window updates so they don't overwhelm the test - pendingWindowUpdate += uint32(len(df.data)) + pendingWindowUpdate += uint32(len(expected)) if pendingWindowUpdate >= windowSize/2 { if err := st.fr.WriteWindowUpdate(0, pendingWindowUpdate); err != nil { b.Fatal(err) @@ -3266,10 +3030,10 @@ func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { pendingWindowUpdate = 0 } } - df := st.wantData() - if !df.StreamEnded() { - b.Fatalf("DATA didn't have END_STREAM; got %v", df) - } + st.wantData(wantData{ + streamID: 1, + endStream: true, + }) } // go-fuzz bug, originally reported at https://github.com/bradfitz/http2/issues/53 @@ -3433,14 +3197,13 @@ func TestServerNoAutoContentLengthOnHead(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "200"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + }, + }) } // golang.org/issue/13495 @@ -3457,16 +3220,15 @@ func TestServerNoDuplicateContentType(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "200"}, - {"content-type", ""}, - {"content-length", "41"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{""}, + "content-length": []string{"41"}, + }, + }) } func TestServerContentLengthCanBeDisabled(t *testing.T) { @@ -3482,29 +3244,28 @@ func TestServerContentLengthCanBeDisabled(t *testing.T) { EndStream: true, EndHeaders: true, }) - h := st.wantHeaders() - headers := st.decodeHeader(h.HeaderBlockFragment()) - want := [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - } - if !reflect.DeepEqual(headers, want) { - t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + }, + }) } -func disableGoroutineTracking() (restore func()) { +func disableGoroutineTracking(t testing.TB) { old := DebugGoroutines DebugGoroutines = false - return func() { DebugGoroutines = old } + t.Cleanup(func() { DebugGoroutines = old }) } func BenchmarkServer_GetRequest(b *testing.B) { - defer disableGoroutineTracking()() + disableGoroutineTracking(b) b.ReportAllocs() const msg = "Hello, world." - st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) { - n, err := io.Copy(ioutil.Discard, r.Body) + st := newServerTesterWithRealConn(b, func(w http.ResponseWriter, r *http.Request) { + n, err := io.Copy(io.Discard, r.Body) if err != nil || n > 0 { b.Errorf("Read %d bytes, error %v; want 0 bytes.", n, err) } @@ -3526,17 +3287,23 @@ func BenchmarkServer_GetRequest(b *testing.B) { EndStream: true, EndHeaders: true, }) - st.wantHeaders() - st.wantData() + st.wantHeaders(wantHeader{ + streamID: streamID, + endStream: false, + }) + st.wantData(wantData{ + streamID: streamID, + endStream: true, + }) } } func BenchmarkServer_PostRequest(b *testing.B) { - defer disableGoroutineTracking()() + disableGoroutineTracking(b) b.ReportAllocs() const msg = "Hello, world." - st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) { - n, err := io.Copy(ioutil.Discard, r.Body) + st := newServerTesterWithRealConn(b, func(w http.ResponseWriter, r *http.Request) { + n, err := io.Copy(io.Discard, r.Body) if err != nil || n > 0 { b.Errorf("Read %d bytes, error %v; want 0 bytes.", n, err) } @@ -3558,8 +3325,14 @@ func BenchmarkServer_PostRequest(b *testing.B) { EndHeaders: true, }) st.writeData(streamID, true, nil) - st.wantHeaders() - st.wantData() + st.wantHeaders(wantHeader{ + streamID: streamID, + endStream: false, + }) + st.wantData(wantData{ + streamID: streamID, + endStream: true, + }) } } @@ -3610,7 +3383,7 @@ func TestServerHandleCustomConn(t *testing.T) { EndStream: true, EndHeaders: true, }) - go io.Copy(ioutil.Discard, c2) + go io.Copy(io.Discard, c2) <-handlerDone }() const testString = "my custom ConnectionState" @@ -3644,17 +3417,16 @@ func TestServer_Rejects_ConnHeaders(t *testing.T) { defer st.Close() st.greet() st.bodylessReq1("connection", "foo") - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "400"}, - {"content-type", "text/plain; charset=utf-8"}, - {"x-content-type-options", "nosniff"}, - {"content-length", "51"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"400"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "x-content-type-options": []string{"nosniff"}, + "content-length": []string{"51"}, + }, + }) } type hpackEncoder struct { @@ -3731,7 +3503,7 @@ func TestExpect100ContinueAfterHandlerWrites(t *testing.T) { doRead := make(chan bool, 1) defer close(doRead) // fallback cleanup - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, msg) w.(http.Flusher).Flush() @@ -3740,14 +3512,12 @@ func TestExpect100ContinueAfterHandlerWrites(t *testing.T) { r.Body.Read(make([]byte, 10)) io.WriteString(w, msg2) - - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, _ := http.NewRequest("POST", st.ts.URL, io.LimitReader(neverEnding('A'), 2<<20)) + req, _ := http.NewRequest("POST", ts.URL, io.LimitReader(neverEnding('A'), 2<<20)) req.Header.Set("Expect", "100-continue") res, err := tr.RoundTrip(req) @@ -3808,14 +3578,13 @@ func TestUnreadFlowControlReturned_Server(t *testing.T) { unblock := make(chan bool, 1) defer close(unblock) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { // Don't read the 16KB request body. Wait until the client's // done sending it and then return. This should cause the Server // to then return those 16KB of flow control to the client. tt.reqFn(r) <-unblock - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() @@ -3833,7 +3602,7 @@ func TestUnreadFlowControlReturned_Server(t *testing.T) { return 0, io.EOF }), ) - req, _ := http.NewRequest("POST", st.ts.URL, body) + req, _ := http.NewRequest("POST", ts.URL, body) res, err := tr.RoundTrip(req) if err != nil { t.Fatal(tt.name, err) @@ -3862,12 +3631,18 @@ func TestServerReturnsStreamAndConnFlowControlOnBodyClose(t *testing.T) { BlockFragment: st.encodeHeader(), EndHeaders: true, }) - st.wantHeaders() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + }) const size = inflowMinRefresh // enough to trigger flow control return st.writeData(1, false, make([]byte, size)) st.wantWindowUpdate(0, size) // conn-level flow control is returned unblockHandler <- struct{}{} - st.wantData() + st.wantData(wantData{ + streamID: 1, + endStream: true, + }) } func TestServerIdleTimeout(t *testing.T) { @@ -3882,22 +3657,24 @@ func TestServerIdleTimeout(t *testing.T) { defer st.Close() st.greet() - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeNo { - t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode) - } + st.advance(500 * time.Millisecond) + st.wantGoAway(0, ErrCodeNo) } func TestServerIdleTimeout_AfterRequest(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } - const timeout = 250 * time.Millisecond + const ( + requestTimeout = 2 * time.Second + idleTimeout = 1 * time.Second + ) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { - time.Sleep(timeout * 2) + var st *serverTester + st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + st.group.Sleep(requestTimeout) }, func(h2s *Server) { - h2s.IdleTimeout = timeout + h2s.IdleTimeout = idleTimeout }) defer st.Close() @@ -3906,14 +3683,16 @@ func TestServerIdleTimeout_AfterRequest(t *testing.T) { // Send a request which takes twice the timeout. Verifies the // idle timeout doesn't fire while we're in a request: st.bodylessReq1() - st.wantHeaders() + st.advance(requestTimeout) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) // But the idle timeout should be rearmed after the request // is done: - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeNo { - t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode) - } + st.advance(idleTimeout) + st.wantGoAway(1, ErrCodeNo) } // grpc-go closes the Request.Body currently with a Read. @@ -3949,22 +3728,21 @@ func TestIssue20704Race(t *testing.T) { itemCount = 100 ) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { for i := 0; i < itemCount; i++ { _, err := w.Write(make([]byte, itemSize)) if err != nil { return } } - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() cl := &http.Client{Transport: tr} for i := 0; i < 1000; i++ { - resp, err := cl.Get(st.ts.URL) + resp, err := cl.Get(ts.URL) if err != nil { t.Fatal(err) } @@ -3976,7 +3754,7 @@ func TestIssue20704Race(t *testing.T) { func TestServer_Rejects_TooSmall(t *testing.T) { testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { - ioutil.ReadAll(r.Body) + io.ReadAll(r.Body) return nil }, func(st *serverTester) { st.writeHeaders(HeadersFrameParam{ @@ -4016,13 +3794,10 @@ func TestServerHandlerConnectionClose(t *testing.T) { var sawRes bool var sawWindowUpdate bool for { - f, err := st.readFrame() - if err == io.EOF { + f := st.readFrame() + if f == nil { break } - if err != nil { - t.Fatal(err) - } switch f := f.(type) { case *GoAwayFrame: sawGoAway = true @@ -4074,6 +3849,8 @@ func TestServerHandlerConnectionClose(t *testing.T) { } sawWindowUpdate = true unblockHandler <- true + st.sync() + st.advance(goAwayTimeout) default: t.Logf("unexpected frame: %v", summarizeFrame(f)) } @@ -4139,20 +3916,9 @@ func TestServer_Headers_HalfCloseRemote(t *testing.T) { } func TestServerGracefulShutdown(t *testing.T) { - var st *serverTester handlerDone := make(chan struct{}) - st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) { - defer close(handlerDone) - go st.ts.Config.Shutdown(context.Background()) - - ga := st.wantGoAway() - if ga.ErrCode != ErrCodeNo { - t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode) - } - if ga.LastStreamID != 1 { - t.Errorf("GOAWAY LastStreamID = %v; want 1", ga.LastStreamID) - } - + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + <-handlerDone w.Header().Set("x-foo", "bar") }) defer st.Close() @@ -4160,17 +3926,23 @@ func TestServerGracefulShutdown(t *testing.T) { st.greet() st.bodylessReq1() - <-handlerDone - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "200"}, - {"x-foo", "bar"}, - {"content-length", "0"}, - } - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got headers %v; want %v", goth, wanth) - } + st.sync() + st.h1server.Shutdown(context.Background()) + + st.wantGoAway(1, ErrCodeNo) + + close(handlerDone) + st.sync() + + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + header: http.Header{ + ":status": []string{"200"}, + "x-foo": []string{"bar"}, + "content-length": []string{"0"}, + }, + }) n, err := st.cc.Read([]byte{0}) if n != 0 || err == nil { @@ -4241,26 +4013,25 @@ func TestContentEncodingNoSniffing(t *testing.T) { for _, tt := range resps { t.Run(tt.name, func(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if tt.contentEncoding != nil { w.Header().Set("Content-Encoding", tt.contentEncoding.(string)) } w.Write(tt.body) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) res, err := tr.RoundTrip(req) if err != nil { - t.Fatalf("GET %s: %v", st.ts.URL, err) + t.Fatalf("GET %s: %v", ts.URL, err) } defer res.Body.Close() g := res.Header.Get("Content-Encoding") - t.Logf("%s: Content-Encoding: %s", st.ts.URL, g) + t.Logf("%s: Content-Encoding: %s", ts.URL, g) if w := tt.contentEncoding; g != w { if w != nil { // The case where contentEncoding was set explicitly. @@ -4274,7 +4045,7 @@ func TestContentEncodingNoSniffing(t *testing.T) { if w := tt.wantContentType; g != w { t.Errorf("Content-Type mismatch\n\tgot: %q\n\twant: %q", g, w) } - t.Logf("%s: Content-Type: %s", st.ts.URL, g) + t.Logf("%s: Content-Type: %s", ts.URL, g) }) } } @@ -4322,13 +4093,10 @@ func TestServerWindowUpdateOnBodyClose(t *testing.T) { // Wait for flow control credit for the portion of the request written so far. increments := windowSize / 2 for { - f, err := st.readFrame() - if err == io.EOF { + f := st.readFrame() + if f == nil { break } - if err != nil { - t.Fatal(err) - } if wu, ok := f.(*WindowUpdateFrame); ok && wu.StreamID == 0 { increments -= int(wu.Increment) if increments == 0 { @@ -4362,24 +4130,16 @@ func TestNoErrorLoggedOnPostAfterGOAWAY(t *testing.T) { EndStream: false, EndHeaders: true, }) - st.wantHeaders() + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: true, + }) st.sc.startGracefulShutdown() - for { - f, err := st.readFrame() - if err == io.EOF { - st.t.Fatal("got a EOF; want *GoAwayFrame") - } - if err != nil { - t.Fatal(err) - } - if gf, ok := f.(*GoAwayFrame); ok && gf.StreamID == 0 { - break - } - } + st.wantRSTStream(1, ErrCodeNo) + st.wantGoAway(1, ErrCodeNo) st.writeData(1, true, []byte(content)) - time.Sleep(200 * time.Millisecond) st.Close() if bytes.Contains(st.serverLogBuf.Bytes(), []byte("PROTOCOL_ERROR")) { @@ -4395,27 +4155,22 @@ func TestServerSendsProcessing(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "102"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } - - hf = st.wantHeaders() - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "200"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", "5"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"102"}, + }, + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{"5"}, + }, + }) }) } @@ -4435,45 +4190,43 @@ func TestServerSendsEarlyHints(t *testing.T) { return nil }, func(st *serverTester) { getSlash(st) - hf := st.wantHeaders() - goth := st.decodeHeader(hf.HeaderBlockFragment()) - wanth := [][2]string{ - {":status", "103"}, - {"link", "; rel=preload; as=style"}, - {"link", "; rel=preload; as=script"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } - - hf = st.wantHeaders() - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "103"}, - {"link", "; rel=preload; as=style"}, - {"link", "; rel=preload; as=script"}, - {"link", "; rel=preload; as=script"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } - - hf = st.wantHeaders() - goth = st.decodeHeader(hf.HeaderBlockFragment()) - wanth = [][2]string{ - {":status", "200"}, - {"link", "; rel=preload; as=style"}, - {"link", "; rel=preload; as=script"}, - {"link", "; rel=preload; as=script"}, - {"content-type", "text/plain; charset=utf-8"}, - {"content-length", "123"}, - } - - if !reflect.DeepEqual(goth, wanth) { - t.Errorf("Got = %q; want %q", goth, wanth) - } + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"103"}, + "link": []string{ + "; rel=preload; as=style", + "; rel=preload; as=script", + }, + }, + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"103"}, + "link": []string{ + "; rel=preload; as=style", + "; rel=preload; as=script", + "; rel=preload; as=script", + }, + }, + }) + st.wantHeaders(wantHeader{ + streamID: 1, + endStream: false, + header: http.Header{ + ":status": []string{"200"}, + "link": []string{ + "; rel=preload; as=style", + "; rel=preload; as=script", + "; rel=preload; as=script", + }, + "content-type": []string{"text/plain; charset=utf-8"}, + "content-length": []string{"123"}, + }, + }) }) } @@ -4495,7 +4248,6 @@ func TestProtocolErrorAfterGoAway(t *testing.T) { EndHeaders: true, }) st.writeData(1, false, []byte(content[:5])) - st.writeReadPing() // Send a GOAWAY with ErrCodeNo, followed by a bogus window update. // The server should close the connection. @@ -4506,14 +4258,9 @@ func TestProtocolErrorAfterGoAway(t *testing.T) { t.Fatal(err) } - for { - if _, err := st.readFrame(); err != nil { - if err != io.EOF { - t.Errorf("unexpected readFrame error: %v", err) - } - break - } - } + st.advance(goAwayTimeout) + st.wantGoAway(1, ErrCodeNo) + st.wantClosed() } func TestServerInitialFlowControlWindow(t *testing.T) { @@ -4534,9 +4281,9 @@ func TestServerInitialFlowControlWindow(t *testing.T) { }, func(s *Server) { s.MaxUploadBufferPerConnection = want }) - defer st.Close() st.writePreface() - st.writeInitialSettings() + st.writeSettings() + _ = readFrame[*SettingsFrame](t, st) st.writeSettingsAck() st.writeHeaders(HeadersFrameParam{ StreamID: 1, @@ -4547,10 +4294,7 @@ func TestServerInitialFlowControlWindow(t *testing.T) { window := 65535 Frames: for { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } + f := st.readFrame() switch f := f.(type) { case *WindowUpdateFrame: if f.FrameHeader.StreamID != 0 { @@ -4560,6 +4304,8 @@ func TestServerInitialFlowControlWindow(t *testing.T) { window += int(f.Increment) case *HeadersFrame: break Frames + case nil: + break Frames default: } } @@ -4606,7 +4352,7 @@ func TestCanonicalHeaderCacheGrowth(t *testing.T) { // We should not access the slice after this point. func TestServerWriteDoesNotRetainBufferAfterReturn(t *testing.T) { donec := make(chan struct{}) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { defer close(donec) buf := make([]byte, 1<<20) var i byte @@ -4620,13 +4366,12 @@ func TestServerWriteDoesNotRetainBufferAfterReturn(t *testing.T) { return } } - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) res, err := tr.RoundTrip(req) if err != nil { t.Fatal(err) @@ -4642,7 +4387,7 @@ func TestServerWriteDoesNotRetainBufferAfterReturn(t *testing.T) { // We should not access the slice after this point. func TestServerWriteDoesNotRetainBufferAfterServerClose(t *testing.T) { donec := make(chan struct{}, 1) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { donec <- struct{}{} defer close(donec) buf := make([]byte, 1<<20) @@ -4657,20 +4402,19 @@ func TestServerWriteDoesNotRetainBufferAfterServerClose(t *testing.T) { return } } - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) res, err := tr.RoundTrip(req) if err != nil { t.Fatal(err) } defer res.Body.Close() <-donec - st.ts.Config.Close() + ts.Config.Close() <-donec } @@ -4697,9 +4441,7 @@ func TestServerMaxHandlerGoroutines(t *testing.T) { }) defer st.Close() - st.writePreface() - st.writeInitialSettings() - st.writeSettingsAck() + st.greet() // Make maxHandlers concurrent requests. // Reset them all, but only after the handler goroutines have started. @@ -4766,20 +4508,9 @@ func TestServerMaxHandlerGoroutines(t *testing.T) { st.fr.WriteRSTStream(streamID, ErrCodeCancel) streamID += 2 } -Frames: - for { - f, err := st.readFrame() - if err != nil { - st.t.Fatal(err) - } - switch f := f.(type) { - case *GoAwayFrame: - if f.ErrCode != ErrCodeEnhanceYourCalm { - t.Errorf("err code = %v; want %v", f.ErrCode, ErrCodeEnhanceYourCalm) - } - break Frames - default: - } + fr := readFrame[*GoAwayFrame](t, st) + if fr.ErrCode != ErrCodeEnhanceYourCalm { + t.Errorf("err code = %v; want %v", fr.ErrCode, ErrCodeEnhanceYourCalm) } for _, s := range stops { @@ -4790,14 +4521,12 @@ Frames: func TestServerContinuationFlood(t *testing.T) { st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { fmt.Println(r.Header) - }, func(ts *httptest.Server) { - ts.Config.MaxHeaderBytes = 4096 + }, func(s *http.Server) { + s.MaxHeaderBytes = 4096 }) defer st.Close() - st.writePreface() - st.writeInitialSettings() - st.writeSettingsAck() + st.greet() st.writeHeaders(HeadersFrameParam{ StreamID: 1, @@ -4814,8 +4543,8 @@ func TestServerContinuationFlood(t *testing.T) { )) for { - f, err := st.readFrame() - if err != nil { + f := st.readFrame() + if f == nil { break } switch f := f.(type) { @@ -4847,9 +4576,7 @@ func TestServerContinuationAfterInvalidHeader(t *testing.T) { }) defer st.Close() - st.writePreface() - st.writeInitialSettings() - st.writeSettingsAck() + st.greet() st.writeHeaders(HeadersFrameParam{ StreamID: 1, @@ -4865,8 +4592,8 @@ func TestServerContinuationAfterInvalidHeader(t *testing.T) { var sawGoAway bool for { - f, err := st.readFrame() - if err != nil { + f := st.readFrame() + if f == nil { break } switch f.(type) { @@ -4880,3 +4607,70 @@ func TestServerContinuationAfterInvalidHeader(t *testing.T) { t.Errorf("connection closed with no GOAWAY frame; want one") } } + +func TestServerUpgradeRequestPrefaceFailure(t *testing.T) { + // An h2c upgrade request fails when the client preface is not as expected. + s2 := &Server{ + // Setting IdleTimeout triggers #67168. + IdleTimeout: 60 * time.Minute, + } + c1, c2 := net.Pipe() + donec := make(chan struct{}) + go func() { + defer close(donec) + s2.ServeConn(c1, &ServeConnOpts{ + UpgradeRequest: httptest.NewRequest("GET", "/", nil), + }) + }() + // The server expects to see the HTTP/2 preface, + // but we close the connection instead. + c2.Close() + <-donec +} + +// Issue 67036: A stream error should result in the handler's request context being canceled. +func TestServerRequestCancelOnError(t *testing.T) { + recvc := make(chan struct{}) // handler has started + donec := make(chan struct{}) // handler has finished + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + close(recvc) + <-r.Context().Done() + close(donec) + }) + defer st.Close() + + st.greet() + + // Client sends request headers, handler starts. + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + <-recvc + + // Client sends an invalid second set of request headers. + // The stream is reset. + // The handler's context is canceled, and the handler exits. + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + <-donec +} + +func TestServerSetReadWriteDeadlineRace(t *testing.T) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + ctl := http.NewResponseController(w) + ctl.SetReadDeadline(time.Now().Add(3600 * time.Second)) + ctl.SetWriteDeadline(time.Now().Add(3600 * time.Second)) + }) + resp, err := ts.Client().Get(ts.URL) + if err != nil { + t.Fatal(err) + } + resp.Body.Close() +} diff --git a/http2/sync_test.go b/http2/sync_test.go new file mode 100644 index 0000000000..aeddbd6f3c --- /dev/null +++ b/http2/sync_test.go @@ -0,0 +1,293 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "context" + "fmt" + "runtime" + "strconv" + "strings" + "sync" + "testing" + "time" +) + +// A synctestGroup synchronizes between a set of cooperating goroutines. +type synctestGroup struct { + mu sync.Mutex + gids map[int]bool + now time.Time + timers map[*fakeTimer]struct{} +} + +type goroutine struct { + id int + parent int + state string +} + +// newSynctest creates a new group with the synthetic clock set the provided time. +func newSynctest(now time.Time) *synctestGroup { + return &synctestGroup{ + gids: map[int]bool{ + currentGoroutine(): true, + }, + now: now, + } +} + +// Join adds the current goroutine to the group. +func (g *synctestGroup) Join() { + g.mu.Lock() + defer g.mu.Unlock() + g.gids[currentGoroutine()] = true +} + +// Count returns the number of goroutines in the group. +func (g *synctestGroup) Count() int { + gs := stacks(true) + count := 0 + for _, gr := range gs { + if !g.gids[gr.id] && !g.gids[gr.parent] { + continue + } + count++ + } + return count +} + +// Close calls t.Fatal if the group contains any running goroutines. +func (g *synctestGroup) Close(t testing.TB) { + if count := g.Count(); count != 1 { + buf := make([]byte, 16*1024) + n := runtime.Stack(buf, true) + t.Logf("stacks:\n%s", buf[:n]) + t.Fatalf("%v goroutines still running after test completed, expect 1", count) + } +} + +// Wait blocks until every goroutine in the group and their direct children are idle. +func (g *synctestGroup) Wait() { + for i := 0; ; i++ { + if g.idle() { + return + } + runtime.Gosched() + } +} + +func (g *synctestGroup) idle() bool { + gs := stacks(true) + g.mu.Lock() + defer g.mu.Unlock() + for _, gr := range gs[1:] { + if !g.gids[gr.id] && !g.gids[gr.parent] { + continue + } + // From runtime/runtime2.go. + switch gr.state { + case "IO wait": + case "chan receive (nil chan)": + case "chan send (nil chan)": + case "select": + case "select (no cases)": + case "chan receive": + case "chan send": + case "sync.Cond.Wait": + case "sync.Mutex.Lock": + case "sync.RWMutex.RLock": + case "sync.RWMutex.Lock": + default: + return false + } + } + return true +} + +func currentGoroutine() int { + s := stacks(false) + return s[0].id +} + +func stacks(all bool) []goroutine { + buf := make([]byte, 16*1024) + for { + n := runtime.Stack(buf, all) + if n < len(buf) { + buf = buf[:n] + break + } + buf = make([]byte, len(buf)*2) + } + + var goroutines []goroutine + for _, gs := range strings.Split(string(buf), "\n\n") { + skip, rest, ok := strings.Cut(gs, "goroutine ") + if skip != "" || !ok { + panic(fmt.Errorf("1 unparsable goroutine stack:\n%s", gs)) + } + ids, rest, ok := strings.Cut(rest, " [") + if !ok { + panic(fmt.Errorf("2 unparsable goroutine stack:\n%s", gs)) + } + id, err := strconv.Atoi(ids) + if err != nil { + panic(fmt.Errorf("3 unparsable goroutine stack:\n%s", gs)) + } + state, rest, ok := strings.Cut(rest, "]") + var parent int + _, rest, ok = strings.Cut(rest, "\ncreated by ") + if ok && strings.Contains(rest, " in goroutine ") { + _, rest, ok := strings.Cut(rest, " in goroutine ") + if !ok { + panic(fmt.Errorf("4 unparsable goroutine stack:\n%s", gs)) + } + parents, rest, ok := strings.Cut(rest, "\n") + if !ok { + panic(fmt.Errorf("5 unparsable goroutine stack:\n%s", gs)) + } + parent, err = strconv.Atoi(parents) + if err != nil { + panic(fmt.Errorf("6 unparsable goroutine stack:\n%s", gs)) + } + } + goroutines = append(goroutines, goroutine{ + id: id, + parent: parent, + state: state, + }) + } + return goroutines +} + +// AdvanceTime advances the synthetic clock by d. +func (g *synctestGroup) AdvanceTime(d time.Duration) { + defer g.Wait() + g.mu.Lock() + defer g.mu.Unlock() + g.now = g.now.Add(d) + for tm := range g.timers { + if tm.when.After(g.now) { + continue + } + tm.run() + delete(g.timers, tm) + } +} + +// Now returns the current synthetic time. +func (g *synctestGroup) Now() time.Time { + g.mu.Lock() + defer g.mu.Unlock() + return g.now +} + +// TimeUntilEvent returns the amount of time until the next scheduled timer. +func (g *synctestGroup) TimeUntilEvent() (d time.Duration, scheduled bool) { + g.mu.Lock() + defer g.mu.Unlock() + for tm := range g.timers { + if dd := tm.when.Sub(g.now); !scheduled || dd < d { + d = dd + scheduled = true + } + } + return d, scheduled +} + +// Sleep is time.Sleep, but using synthetic time. +func (g *synctestGroup) Sleep(d time.Duration) { + tm := g.NewTimer(d) + <-tm.C() +} + +// NewTimer is time.NewTimer, but using synthetic time. +func (g *synctestGroup) NewTimer(d time.Duration) Timer { + return g.addTimer(d, &fakeTimer{ + ch: make(chan time.Time), + }) +} + +// AfterFunc is time.AfterFunc, but using synthetic time. +func (g *synctestGroup) AfterFunc(d time.Duration, f func()) Timer { + return g.addTimer(d, &fakeTimer{ + f: f, + }) +} + +// ContextWithTimeout is context.WithTimeout, but using synthetic time. +func (g *synctestGroup) ContextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(ctx) + tm := g.AfterFunc(d, cancel) + return ctx, func() { + tm.Stop() + cancel() + } +} + +func (g *synctestGroup) addTimer(d time.Duration, tm *fakeTimer) *fakeTimer { + g.mu.Lock() + defer g.mu.Unlock() + tm.g = g + tm.when = g.now.Add(d) + if g.timers == nil { + g.timers = make(map[*fakeTimer]struct{}) + } + if tm.when.After(g.now) { + g.timers[tm] = struct{}{} + } else { + tm.run() + } + return tm +} + +type Timer = interface { + C() <-chan time.Time + Reset(d time.Duration) bool + Stop() bool +} + +type fakeTimer struct { + g *synctestGroup + when time.Time + ch chan time.Time + f func() +} + +func (tm *fakeTimer) run() { + if tm.ch != nil { + tm.ch <- tm.g.now + } else { + go func() { + tm.g.Join() + tm.f() + }() + } +} + +func (tm *fakeTimer) C() <-chan time.Time { return tm.ch } + +func (tm *fakeTimer) Reset(d time.Duration) bool { + tm.g.mu.Lock() + defer tm.g.mu.Unlock() + _, stopped := tm.g.timers[tm] + if d <= 0 { + delete(tm.g.timers, tm) + tm.run() + } else { + tm.when = tm.g.now.Add(d) + tm.g.timers[tm] = struct{}{} + } + return stopped +} + +func (tm *fakeTimer) Stop() bool { + tm.g.mu.Lock() + defer tm.g.mu.Unlock() + _, stopped := tm.g.timers[tm] + delete(tm.g.timers, tm) + return stopped +} diff --git a/http2/testdata/draft-ietf-httpbis-http2.xml b/http2/testdata/draft-ietf-httpbis-http2.xml deleted file mode 100644 index 39d756de7a..0000000000 --- a/http2/testdata/draft-ietf-httpbis-http2.xml +++ /dev/null @@ -1,5021 +0,0 @@ - - - - - - - - - - - - - - - - - - - Hypertext Transfer Protocol version 2 - - - Twist -
- mbelshe@chromium.org -
-
- - - Google, Inc -
- fenix@google.com -
-
- - - Mozilla -
- - 331 E Evelyn Street - Mountain View - CA - 94041 - US - - martin.thomson@gmail.com -
-
- - - Applications - HTTPbis - HTTP - SPDY - Web - - - - This specification describes an optimized expression of the semantics of the Hypertext - Transfer Protocol (HTTP). HTTP/2 enables a more efficient use of network resources and a - reduced perception of latency by introducing header field compression and allowing multiple - concurrent messages on the same connection. It also introduces unsolicited push of - representations from servers to clients. - - - This specification is an alternative to, but does not obsolete, the HTTP/1.1 message syntax. - HTTP's existing semantics remain unchanged. - - - - - - Discussion of this draft takes place on the HTTPBIS working group mailing list - (ietf-http-wg@w3.org), which is archived at . - - - Working Group information can be found at ; that specific to HTTP/2 are at . - - - The changes in this draft are summarized in . - - - -
- - -
- - - The Hypertext Transfer Protocol (HTTP) is a wildly successful protocol. However, the - HTTP/1.1 message format () has - several characteristics that have a negative overall effect on application performance - today. - - - In particular, HTTP/1.0 allowed only one request to be outstanding at a time on a given - TCP connection. HTTP/1.1 added request pipelining, but this only partially addressed - request concurrency and still suffers from head-of-line blocking. Therefore, HTTP/1.1 - clients that need to make many requests typically use multiple connections to a server in - order to achieve concurrency and thereby reduce latency. - - - Furthermore, HTTP header fields are often repetitive and verbose, causing unnecessary - network traffic, as well as causing the initial TCP congestion - window to quickly fill. This can result in excessive latency when multiple requests are - made on a new TCP connection. - - - HTTP/2 addresses these issues by defining an optimized mapping of HTTP's semantics to an - underlying connection. Specifically, it allows interleaving of request and response - messages on the same connection and uses an efficient coding for HTTP header fields. It - also allows prioritization of requests, letting more important requests complete more - quickly, further improving performance. - - - The resulting protocol is more friendly to the network, because fewer TCP connections can - be used in comparison to HTTP/1.x. This means less competition with other flows, and - longer-lived connections, which in turn leads to better utilization of available network - capacity. - - - Finally, HTTP/2 also enables more efficient processing of messages through use of binary - message framing. - -
- -
- - HTTP/2 provides an optimized transport for HTTP semantics. HTTP/2 supports all of the core - features of HTTP/1.1, but aims to be more efficient in several ways. - - - The basic protocol unit in HTTP/2 is a frame. Each frame - type serves a different purpose. For example, HEADERS and - DATA frames form the basis of HTTP requests and - responses; other frame types like SETTINGS, - WINDOW_UPDATE, and PUSH_PROMISE are used in support of other - HTTP/2 features. - - - Multiplexing of requests is achieved by having each HTTP request-response exchange - associated with its own stream. Streams are largely - independent of each other, so a blocked or stalled request or response does not prevent - progress on other streams. - - - Flow control and prioritization ensure that it is possible to efficiently use multiplexed - streams. Flow control helps to ensure that only data that - can be used by a receiver is transmitted. Prioritization ensures that limited resources can be directed - to the most important streams first. - - - HTTP/2 adds a new interaction mode, whereby a server can push - responses to a client. Server push allows a server to speculatively send a client - data that the server anticipates the client will need, trading off some network usage - against a potential latency gain. The server does this by synthesizing a request, which it - sends as a PUSH_PROMISE frame. The server is then able to send a response to - the synthetic request on a separate stream. - - - Frames that contain HTTP header fields are compressed. - HTTP requests can be highly redundant, so compression can reduce the size of requests and - responses significantly. - - -
- - The HTTP/2 specification is split into four parts: - - - Starting HTTP/2 covers how an HTTP/2 connection is - initiated. - - - The framing and streams layers describe the way HTTP/2 frames are - structured and formed into multiplexed streams. - - - Frame and error - definitions include details of the frame and error types used in HTTP/2. - - - HTTP mappings and additional - requirements describe how HTTP semantics are expressed using frames and - streams. - - - - - While some of the frame and stream layer concepts are isolated from HTTP, this - specification does not define a completely generic framing layer. The framing and streams - layers are tailored to the needs of the HTTP protocol and server push. - -
- -
- - The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD - NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as - described in RFC 2119. - - - All numeric values are in network byte order. Values are unsigned unless otherwise - indicated. Literal values are provided in decimal or hexadecimal as appropriate. - Hexadecimal literals are prefixed with 0x to distinguish them - from decimal literals. - - - The following terms are used: - - - The endpoint initiating the HTTP/2 connection. - - - A transport-layer connection between two endpoints. - - - An error that affects the entire HTTP/2 connection. - - - Either the client or server of the connection. - - - The smallest unit of communication within an HTTP/2 connection, consisting of a header - and a variable-length sequence of octets structured according to the frame type. - - - An endpoint. When discussing a particular endpoint, "peer" refers to the endpoint - that is remote to the primary subject of discussion. - - - An endpoint that is receiving frames. - - - An endpoint that is transmitting frames. - - - The endpoint which did not initiate the HTTP/2 connection. - - - A bi-directional flow of frames across a virtual channel within the HTTP/2 connection. - - - An error on the individual HTTP/2 stream. - - - - - Finally, the terms "gateway", "intermediary", "proxy", and "tunnel" are defined - in . - -
-
- -
- - An HTTP/2 connection is an application layer protocol running on top of a TCP connection - (). The client is the TCP connection initiator. - - - HTTP/2 uses the same "http" and "https" URI schemes used by HTTP/1.1. HTTP/2 shares the same - default port numbers: 80 for "http" URIs and 443 for "https" URIs. As a result, - implementations processing requests for target resource URIs like http://example.org/foo or https://example.com/bar are required to first discover whether the - upstream server (the immediate peer to which the client wishes to establish a connection) - supports HTTP/2. - - - - The means by which support for HTTP/2 is determined is different for "http" and "https" - URIs. Discovery for "http" URIs is described in . Discovery - for "https" URIs is described in . - - -
- - The protocol defined in this document has two identifiers. - - - - The string "h2" identifies the protocol where HTTP/2 uses TLS. This identifier is used in the TLS application layer protocol negotiation extension (ALPN) - field and any place that HTTP/2 over TLS is identified. - - - The "h2" string is serialized into an ALPN protocol identifier as the two octet - sequence: 0x68, 0x32. - - - - - The string "h2c" identifies the protocol where HTTP/2 is run over cleartext TCP. - This identifier is used in the HTTP/1.1 Upgrade header field and any place that - HTTP/2 over TCP is identified. - - - - - - Negotiating "h2" or "h2c" implies the use of the transport, security, framing and message - semantics described in this document. - - - RFC Editor's Note: please remove the remainder of this section prior to the - publication of a final version of this document. - - - Only implementations of the final, published RFC can identify themselves as "h2" or "h2c". - Until such an RFC exists, implementations MUST NOT identify themselves using these - strings. - - - Examples and text throughout the rest of this document use "h2" as a matter of - editorial convenience only. Implementations of draft versions MUST NOT identify using - this string. - - - Implementations of draft versions of the protocol MUST add the string "-" and the - corresponding draft number to the identifier. For example, draft-ietf-httpbis-http2-11 - over TLS is identified using the string "h2-11". - - - Non-compatible experiments that are based on these draft versions MUST append the string - "-" and an experiment name to the identifier. For example, an experimental implementation - of packet mood-based encoding based on draft-ietf-httpbis-http2-09 might identify itself - as "h2-09-emo". Note that any label MUST conform to the "token" syntax defined in - . Experimenters are - encouraged to coordinate their experiments on the ietf-http-wg@w3.org mailing list. - -
- -
- - A client that makes a request for an "http" URI without prior knowledge about support for - HTTP/2 uses the HTTP Upgrade mechanism (). The client makes an HTTP/1.1 request that includes an Upgrade - header field identifying HTTP/2 with the "h2c" token. The HTTP/1.1 request MUST include - exactly one HTTP2-Settings header field. - -
- For example: - - -]]> -
- - Requests that contain an entity body MUST be sent in their entirety before the client can - send HTTP/2 frames. This means that a large request entity can block the use of the - connection until it is completely sent. - - - If concurrency of an initial request with subsequent requests is important, an OPTIONS - request can be used to perform the upgrade to HTTP/2, at the cost of an additional - round-trip. - - - A server that does not support HTTP/2 can respond to the request as though the Upgrade - header field were absent: - -
- -HTTP/1.1 200 OK -Content-Length: 243 -Content-Type: text/html - -... - -
- - A server MUST ignore a "h2" token in an Upgrade header field. Presence of a token with - "h2" implies HTTP/2 over TLS, which is instead negotiated as described in . - - - A server that supports HTTP/2 can accept the upgrade with a 101 (Switching Protocols) - response. After the empty line that terminates the 101 response, the server can begin - sending HTTP/2 frames. These frames MUST include a response to the request that initiated - the Upgrade. - - -
- - For example: - - -HTTP/1.1 101 Switching Protocols -Connection: Upgrade -Upgrade: h2c - -[ HTTP/2 connection ... - -
- - The first HTTP/2 frame sent by the server is a SETTINGS frame () as the server connection preface (). Upon receiving the 101 response, the client sends a connection preface, which includes a - SETTINGS frame. - - - The HTTP/1.1 request that is sent prior to upgrade is assigned stream identifier 1 and is - assigned default priority values. Stream 1 is - implicitly half closed from the client toward the server, since the request is completed - as an HTTP/1.1 request. After commencing the HTTP/2 connection, stream 1 is used for the - response. - - -
- - A request that upgrades from HTTP/1.1 to HTTP/2 MUST include exactly one HTTP2-Settings header field. The HTTP2-Settings header field is a connection-specific header field - that includes parameters that govern the HTTP/2 connection, provided in anticipation of - the server accepting the request to upgrade. - -
- -
- - A server MUST NOT upgrade the connection to HTTP/2 if this header field is not present, - or if more than one is present. A server MUST NOT send this header field. - - - - The content of the HTTP2-Settings header field is the - payload of a SETTINGS frame (), encoded as a - base64url string (that is, the URL- and filename-safe Base64 encoding described in , with any trailing '=' characters omitted). The - ABNF production for token68 is - defined in . - - - Since the upgrade is only intended to apply to the immediate connection, a client - sending HTTP2-Settings MUST also send HTTP2-Settings as a connection option in the Connection header field to prevent it from being forwarded - downstream. - - - A server decodes and interprets these values as it would any other - SETTINGS frame. Acknowledgement of the - SETTINGS parameters is not necessary, since a 101 response serves as implicit - acknowledgment. Providing these values in the Upgrade request gives a client an - opportunity to provide parameters prior to receiving any frames from the server. - -
-
- -
- - A client that makes a request to an "https" URI uses TLS - with the application layer protocol negotiation extension. - - - HTTP/2 over TLS uses the "h2" application token. The "h2c" token MUST NOT be sent by a - client or selected by a server. - - - Once TLS negotiation is complete, both the client and the server send a connection preface. - -
- -
- - A client can learn that a particular server supports HTTP/2 by other means. For example, - describes a mechanism for advertising this capability. - - - A client MAY immediately send HTTP/2 frames to a server that is known to support HTTP/2, - after the connection preface; a server can - identify such a connection by the presence of the connection preface. This only affects - the establishment of HTTP/2 connections over cleartext TCP; implementations that support - HTTP/2 over TLS MUST use protocol negotiation in TLS. - - - Without additional information, prior support for HTTP/2 is not a strong signal that a - given server will support HTTP/2 for future connections. For example, it is possible for - server configurations to change, for configurations to differ between instances in - clustered servers, or for network conditions to change. - -
- -
- - Upon establishment of a TCP connection and determination that HTTP/2 will be used by both - peers, each endpoint MUST send a connection preface as a final confirmation and to - establish the initial SETTINGS parameters for the HTTP/2 connection. The client and - server each send a different connection preface. - - - The client connection preface starts with a sequence of 24 octets, which in hex notation - are: - -
- -
- - (the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n). This sequence - is followed by a SETTINGS frame (). The - SETTINGS frame MAY be empty. The client sends the client connection - preface immediately upon receipt of a 101 Switching Protocols response (indicating a - successful upgrade), or as the first application data octets of a TLS connection. If - starting an HTTP/2 connection with prior knowledge of server support for the protocol, the - client connection preface is sent upon connection establishment. - - - - - The client connection preface is selected so that a large proportion of HTTP/1.1 or - HTTP/1.0 servers and intermediaries do not attempt to process further frames. Note - that this does not address the concerns raised in . - - - - - The server connection preface consists of a potentially empty SETTINGS - frame () that MUST be the first frame the server sends in the - HTTP/2 connection. - - - The SETTINGS frames received from a peer as part of the connection preface - MUST be acknowledged (see ) after sending the connection - preface. - - - To avoid unnecessary latency, clients are permitted to send additional frames to the - server immediately after sending the client connection preface, without waiting to receive - the server connection preface. It is important to note, however, that the server - connection preface SETTINGS frame might include parameters that necessarily - alter how a client is expected to communicate with the server. Upon receiving the - SETTINGS frame, the client is expected to honor any parameters established. - In some configurations, it is possible for the server to transmit SETTINGS - before the client sends additional frames, providing an opportunity to avoid this issue. - - - Clients and servers MUST treat an invalid connection preface as a connection error of type - PROTOCOL_ERROR. A GOAWAY frame () - MAY be omitted in this case, since an invalid preface indicates that the peer is not using - HTTP/2. - -
-
- -
- - Once the HTTP/2 connection is established, endpoints can begin exchanging frames. - - -
- - All frames begin with a fixed 9-octet header followed by a variable-length payload. - -
- -
- - The fields of the frame header are defined as: - - - - The length of the frame payload expressed as an unsigned 24-bit integer. Values - greater than 214 (16,384) MUST NOT be sent unless the receiver has - set a larger value for SETTINGS_MAX_FRAME_SIZE. - - - The 9 octets of the frame header are not included in this value. - - - - - The 8-bit type of the frame. The frame type determines the format and semantics of - the frame. Implementations MUST ignore and discard any frame that has a type that - is unknown. - - - - - An 8-bit field reserved for frame-type specific boolean flags. - - - Flags are assigned semantics specific to the indicated frame type. Flags that have - no defined semantics for a particular frame type MUST be ignored, and MUST be left - unset (0) when sending. - - - - - A reserved 1-bit field. The semantics of this bit are undefined and the bit MUST - remain unset (0) when sending and MUST be ignored when receiving. - - - - - A 31-bit stream identifier (see ). The value 0 is - reserved for frames that are associated with the connection as a whole as opposed to - an individual stream. - - - - - - The structure and content of the frame payload is dependent entirely on the frame type. - -
- -
- - The size of a frame payload is limited by the maximum size that a receiver advertises in - the SETTINGS_MAX_FRAME_SIZE setting. This setting can have any value - between 214 (16,384) and 224-1 (16,777,215) octets, - inclusive. - - - All implementations MUST be capable of receiving and minimally processing frames up to - 214 octets in length, plus the 9 octet frame - header. The size of the frame header is not included when describing frame sizes. - - - Certain frame types, such as PING, impose additional limits - on the amount of payload data allowed. - - - - - If a frame size exceeds any defined limit, or is too small to contain mandatory frame - data, the endpoint MUST send a FRAME_SIZE_ERROR error. A frame size error - in a frame that could alter the state of the entire connection MUST be treated as a connection error; this includes any frame carrying - a header block (that is, HEADERS, - PUSH_PROMISE, and CONTINUATION), SETTINGS, - and any WINDOW_UPDATE frame with a stream identifier of 0. - - - Endpoints are not obligated to use all available space in a frame. Responsiveness can be - improved by using frames that are smaller than the permitted maximum size. Sending large - frames can result in delays in sending time-sensitive frames (such - RST_STREAM, WINDOW_UPDATE, or PRIORITY) - which if blocked by the transmission of a large frame, could affect performance. - -
- -
- - Just as in HTTP/1, a header field in HTTP/2 is a name with one or more associated values. - They are used within HTTP request and response messages as well as server push operations - (see ). - - - Header lists are collections of zero or more header fields. When transmitted over a - connection, a header list is serialized into a header block using HTTP Header Compression. The serialized header block is then - divided into one or more octet sequences, called header block fragments, and transmitted - within the payload of HEADERS, PUSH_PROMISE or CONTINUATION frames. - - - The Cookie header field is treated specially by the HTTP - mapping (see ). - - - A receiving endpoint reassembles the header block by concatenating its fragments, then - decompresses the block to reconstruct the header list. - - - A complete header block consists of either: - - - a single HEADERS or PUSH_PROMISE frame, - with the END_HEADERS flag set, or - - - a HEADERS or PUSH_PROMISE frame with the END_HEADERS - flag cleared and one or more CONTINUATION frames, - where the last CONTINUATION frame has the END_HEADERS flag set. - - - - - Header compression is stateful. One compression context and one decompression context is - used for the entire connection. Each header block is processed as a discrete unit. - Header blocks MUST be transmitted as a contiguous sequence of frames, with no interleaved - frames of any other type or from any other stream. The last frame in a sequence of - HEADERS or CONTINUATION frames MUST have the END_HEADERS - flag set. The last frame in a sequence of PUSH_PROMISE or - CONTINUATION frames MUST have the END_HEADERS flag set. This allows a - header block to be logically equivalent to a single frame. - - - Header block fragments can only be sent as the payload of HEADERS, - PUSH_PROMISE or CONTINUATION frames, because these frames - carry data that can modify the compression context maintained by a receiver. An endpoint - receiving HEADERS, PUSH_PROMISE or - CONTINUATION frames MUST reassemble header blocks and perform decompression - even if the frames are to be discarded. A receiver MUST terminate the connection with a - connection error of type - COMPRESSION_ERROR if it does not decompress a header block. - -
-
- -
- - A "stream" is an independent, bi-directional sequence of frames exchanged between the client - and server within an HTTP/2 connection. Streams have several important characteristics: - - - A single HTTP/2 connection can contain multiple concurrently open streams, with either - endpoint interleaving frames from multiple streams. - - - Streams can be established and used unilaterally or shared by either the client or - server. - - - Streams can be closed by either endpoint. - - - The order in which frames are sent on a stream is significant. Recipients process frames - in the order they are received. In particular, the order of HEADERS, - and DATA frames is semantically significant. - - - Streams are identified by an integer. Stream identifiers are assigned to streams by the - endpoint initiating the stream. - - - - -
- - The lifecycle of a stream is shown in . - - -
- - | |<-----------' | - | R | closed | R | - `-------------------->| |<--------------------' - +--------+ - - H: HEADERS frame (with implied CONTINUATIONs) - PP: PUSH_PROMISE frame (with implied CONTINUATIONs) - ES: END_STREAM flag - R: RST_STREAM frame -]]> - -
- - - Note that this diagram shows stream state transitions and the frames and flags that affect - those transitions only. In this regard, CONTINUATION frames do not result - in state transitions; they are effectively part of the HEADERS or - PUSH_PROMISE that they follow. For this purpose, the END_STREAM flag is - processed as a separate event to the frame that bears it; a HEADERS frame - with the END_STREAM flag set can cause two state transitions. - - - Both endpoints have a subjective view of the state of a stream that could be different - when frames are in transit. Endpoints do not coordinate the creation of streams; they are - created unilaterally by either endpoint. The negative consequences of a mismatch in - states are limited to the "closed" state after sending RST_STREAM, where - frames might be received for some time after closing. - - - Streams have the following states: - - - - - - All streams start in the "idle" state. In this state, no frames have been - exchanged. - - - The following transitions are valid from this state: - - - Sending or receiving a HEADERS frame causes the stream to become - "open". The stream identifier is selected as described in . The same HEADERS frame can also - cause a stream to immediately become "half closed". - - - Sending a PUSH_PROMISE frame marks the associated stream for - later use. The stream state for the reserved stream transitions to "reserved - (local)". - - - Receiving a PUSH_PROMISE frame marks the associated stream as - reserved by the remote peer. The state of the stream becomes "reserved - (remote)". - - - - - Receiving any frames other than HEADERS or - PUSH_PROMISE on a stream in this state MUST be treated as a connection error of type - PROTOCOL_ERROR. - - - - - - - A stream in the "reserved (local)" state is one that has been promised by sending a - PUSH_PROMISE frame. A PUSH_PROMISE frame reserves an - idle stream by associating the stream with an open stream that was initiated by the - remote peer (see ). - - - In this state, only the following transitions are possible: - - - The endpoint can send a HEADERS frame. This causes the stream to - open in a "half closed (remote)" state. - - - Either endpoint can send a RST_STREAM frame to cause the stream - to become "closed". This releases the stream reservation. - - - - - An endpoint MUST NOT send any type of frame other than HEADERS or - RST_STREAM in this state. - - - A PRIORITY frame MAY be received in this state. Receiving any type - of frame other than RST_STREAM or PRIORITY on a stream - in this state MUST be treated as a connection - error of type PROTOCOL_ERROR. - - - - - - - A stream in the "reserved (remote)" state has been reserved by a remote peer. - - - In this state, only the following transitions are possible: - - - Receiving a HEADERS frame causes the stream to transition to - "half closed (local)". - - - Either endpoint can send a RST_STREAM frame to cause the stream - to become "closed". This releases the stream reservation. - - - - - An endpoint MAY send a PRIORITY frame in this state to reprioritize - the reserved stream. An endpoint MUST NOT send any type of frame other than - RST_STREAM, WINDOW_UPDATE, or PRIORITY - in this state. - - - Receiving any type of frame other than HEADERS or - RST_STREAM on a stream in this state MUST be treated as a connection error of type - PROTOCOL_ERROR. - - - - - - - A stream in the "open" state may be used by both peers to send frames of any type. - In this state, sending peers observe advertised stream - level flow control limits. - - - From this state either endpoint can send a frame with an END_STREAM flag set, which - causes the stream to transition into one of the "half closed" states: an endpoint - sending an END_STREAM flag causes the stream state to become "half closed (local)"; - an endpoint receiving an END_STREAM flag causes the stream state to become "half - closed (remote)". - - - Either endpoint can send a RST_STREAM frame from this state, causing - it to transition immediately to "closed". - - - - - - - A stream that is in the "half closed (local)" state cannot be used for sending - frames. Only WINDOW_UPDATE, PRIORITY and - RST_STREAM frames can be sent in this state. - - - A stream transitions from this state to "closed" when a frame that contains an - END_STREAM flag is received, or when either peer sends a RST_STREAM - frame. - - - A receiver can ignore WINDOW_UPDATE frames in this state, which might - arrive for a short period after a frame bearing the END_STREAM flag is sent. - - - PRIORITY frames received in this state are used to reprioritize - streams that depend on the current stream. - - - - - - - A stream that is "half closed (remote)" is no longer being used by the peer to send - frames. In this state, an endpoint is no longer obligated to maintain a receiver - flow control window if it performs flow control. - - - If an endpoint receives additional frames for a stream that is in this state, other - than WINDOW_UPDATE, PRIORITY or - RST_STREAM, it MUST respond with a stream error of type - STREAM_CLOSED. - - - A stream that is "half closed (remote)" can be used by the endpoint to send frames - of any type. In this state, the endpoint continues to observe advertised stream level flow control limits. - - - A stream can transition from this state to "closed" by sending a frame that contains - an END_STREAM flag, or when either peer sends a RST_STREAM frame. - - - - - - - The "closed" state is the terminal state. - - - An endpoint MUST NOT send frames other than PRIORITY on a closed - stream. An endpoint that receives any frame other than PRIORITY - after receiving a RST_STREAM MUST treat that as a stream error of type - STREAM_CLOSED. Similarly, an endpoint that receives any frames after - receiving a frame with the END_STREAM flag set MUST treat that as a connection error of type - STREAM_CLOSED, unless the frame is permitted as described below. - - - WINDOW_UPDATE or RST_STREAM frames can be received in - this state for a short period after a DATA or HEADERS - frame containing an END_STREAM flag is sent. Until the remote peer receives and - processes RST_STREAM or the frame bearing the END_STREAM flag, it - might send frames of these types. Endpoints MUST ignore - WINDOW_UPDATE or RST_STREAM frames received in this - state, though endpoints MAY choose to treat frames that arrive a significant time - after sending END_STREAM as a connection - error of type PROTOCOL_ERROR. - - - PRIORITY frames can be sent on closed streams to prioritize streams - that are dependent on the closed stream. Endpoints SHOULD process - PRIORITY frame, though they can be ignored if the stream has been - removed from the dependency tree (see ). - - - If this state is reached as a result of sending a RST_STREAM frame, - the peer that receives the RST_STREAM might have already sent - or - enqueued for sending - frames on the stream that cannot be withdrawn. An endpoint - MUST ignore frames that it receives on closed streams after it has sent a - RST_STREAM frame. An endpoint MAY choose to limit the period over - which it ignores frames and treat frames that arrive after this time as being in - error. - - - Flow controlled frames (i.e., DATA) received after sending - RST_STREAM are counted toward the connection flow control window. - Even though these frames might be ignored, because they are sent before the sender - receives the RST_STREAM, the sender will consider the frames to count - against the flow control window. - - - An endpoint might receive a PUSH_PROMISE frame after it sends - RST_STREAM. PUSH_PROMISE causes a stream to become - "reserved" even if the associated stream has been reset. Therefore, a - RST_STREAM is needed to close an unwanted promised stream. - - - - - - In the absence of more specific guidance elsewhere in this document, implementations - SHOULD treat the receipt of a frame that is not expressly permitted in the description of - a state as a connection error of type - PROTOCOL_ERROR. Frame of unknown types are ignored. - - - An example of the state transitions for an HTTP request/response exchange can be found in - . An example of the state transitions for server push can be - found in and . - - -
- - Streams are identified with an unsigned 31-bit integer. Streams initiated by a client - MUST use odd-numbered stream identifiers; those initiated by the server MUST use - even-numbered stream identifiers. A stream identifier of zero (0x0) is used for - connection control messages; the stream identifier zero cannot be used to establish a - new stream. - - - HTTP/1.1 requests that are upgraded to HTTP/2 (see ) are - responded to with a stream identifier of one (0x1). After the upgrade - completes, stream 0x1 is "half closed (local)" to the client. Therefore, stream 0x1 - cannot be selected as a new stream identifier by a client that upgrades from HTTP/1.1. - - - The identifier of a newly established stream MUST be numerically greater than all - streams that the initiating endpoint has opened or reserved. This governs streams that - are opened using a HEADERS frame and streams that are reserved using - PUSH_PROMISE. An endpoint that receives an unexpected stream identifier - MUST respond with a connection error of - type PROTOCOL_ERROR. - - - The first use of a new stream identifier implicitly closes all streams in the "idle" - state that might have been initiated by that peer with a lower-valued stream identifier. - For example, if a client sends a HEADERS frame on stream 7 without ever - sending a frame on stream 5, then stream 5 transitions to the "closed" state when the - first frame for stream 7 is sent or received. - - - Stream identifiers cannot be reused. Long-lived connections can result in an endpoint - exhausting the available range of stream identifiers. A client that is unable to - establish a new stream identifier can establish a new connection for new streams. A - server that is unable to establish a new stream identifier can send a - GOAWAY frame so that the client is forced to open a new connection for - new streams. - -
- -
- - A peer can limit the number of concurrently active streams using the - SETTINGS_MAX_CONCURRENT_STREAMS parameter (see ) within a SETTINGS frame. The maximum concurrent - streams setting is specific to each endpoint and applies only to the peer that receives - the setting. That is, clients specify the maximum number of concurrent streams the - server can initiate, and servers specify the maximum number of concurrent streams the - client can initiate. - - - Streams that are in the "open" state, or either of the "half closed" states count toward - the maximum number of streams that an endpoint is permitted to open. Streams in any of - these three states count toward the limit advertised in the - SETTINGS_MAX_CONCURRENT_STREAMS setting. Streams in either of the - "reserved" states do not count toward the stream limit. - - - Endpoints MUST NOT exceed the limit set by their peer. An endpoint that receives a - HEADERS frame that causes their advertised concurrent stream limit to be - exceeded MUST treat this as a stream error. An - endpoint that wishes to reduce the value of - SETTINGS_MAX_CONCURRENT_STREAMS to a value that is below the current - number of open streams can either close streams that exceed the new value or allow - streams to complete. - -
-
- -
- - Using streams for multiplexing introduces contention over use of the TCP connection, - resulting in blocked streams. A flow control scheme ensures that streams on the same - connection do not destructively interfere with each other. Flow control is used for both - individual streams and for the connection as a whole. - - - HTTP/2 provides for flow control through use of the WINDOW_UPDATE frame. - - -
- - HTTP/2 stream flow control aims to allow a variety of flow control algorithms to be - used without requiring protocol changes. Flow control in HTTP/2 has the following - characteristics: - - - Flow control is specific to a connection; i.e., it is "hop-by-hop", not - "end-to-end". - - - Flow control is based on window update frames. Receivers advertise how many octets - they are prepared to receive on a stream and for the entire connection. This is a - credit-based scheme. - - - Flow control is directional with overall control provided by the receiver. A - receiver MAY choose to set any window size that it desires for each stream and for - the entire connection. A sender MUST respect flow control limits imposed by a - receiver. Clients, servers and intermediaries all independently advertise their - flow control window as a receiver and abide by the flow control limits set by - their peer when sending. - - - The initial value for the flow control window is 65,535 octets for both new streams - and the overall connection. - - - The frame type determines whether flow control applies to a frame. Of the frames - specified in this document, only DATA frames are subject to flow - control; all other frame types do not consume space in the advertised flow control - window. This ensures that important control frames are not blocked by flow control. - - - Flow control cannot be disabled. - - - HTTP/2 defines only the format and semantics of the WINDOW_UPDATE - frame (). This document does not stipulate how a - receiver decides when to send this frame or the value that it sends, nor does it - specify how a sender chooses to send packets. Implementations are able to select - any algorithm that suits their needs. - - - - - Implementations are also responsible for managing how requests and responses are sent - based on priority; choosing how to avoid head of line blocking for requests; and - managing the creation of new streams. Algorithm choices for these could interact with - any flow control algorithm. - -
- -
- - Flow control is defined to protect endpoints that are operating under resource - constraints. For example, a proxy needs to share memory between many connections, and - also might have a slow upstream connection and a fast downstream one. Flow control - addresses cases where the receiver is unable process data on one stream, yet wants to - continue to process other streams in the same connection. - - - Deployments that do not require this capability can advertise a flow control window of - the maximum size, incrementing the available space when new data is received. This - effectively disables flow control for that receiver. Conversely, a sender is always - subject to the flow control window advertised by the receiver. - - - Deployments with constrained resources (for example, memory) can employ flow control to - limit the amount of memory a peer can consume. Note, however, that this can lead to - suboptimal use of available network resources if flow control is enabled without - knowledge of the bandwidth-delay product (see ). - - - Even with full awareness of the current bandwidth-delay product, implementation of flow - control can be difficult. When using flow control, the receiver MUST read from the TCP - receive buffer in a timely fashion. Failure to do so could lead to a deadlock when - critical frames, such as WINDOW_UPDATE, are not read and acted upon. - -
-
- -
- - A client can assign a priority for a new stream by including prioritization information in - the HEADERS frame that opens the stream. For an existing - stream, the PRIORITY frame can be used to change the - priority. - - - The purpose of prioritization is to allow an endpoint to express how it would prefer its - peer allocate resources when managing concurrent streams. Most importantly, priority can - be used to select streams for transmitting frames when there is limited capacity for - sending. - - - Streams can be prioritized by marking them as dependent on the completion of other streams - (). Each dependency is assigned a relative weight, a number - that is used to determine the relative proportion of available resources that are assigned - to streams dependent on the same stream. - - - - Explicitly setting the priority for a stream is input to a prioritization process. It - does not guarantee any particular processing or transmission order for the stream relative - to any other stream. An endpoint cannot force a peer to process concurrent streams in a - particular order using priority. Expressing priority is therefore only ever a suggestion. - - - Providing prioritization information is optional, so default values are used if no - explicit indicator is provided (). - - -
- - Each stream can be given an explicit dependency on another stream. Including a - dependency expresses a preference to allocate resources to the identified stream rather - than to the dependent stream. - - - A stream that is not dependent on any other stream is given a stream dependency of 0x0. - In other words, the non-existent stream 0 forms the root of the tree. - - - A stream that depends on another stream is a dependent stream. The stream upon which a - stream is dependent is a parent stream. A dependency on a stream that is not currently - in the tree - such as a stream in the "idle" state - results in that stream being given - a default priority. - - - When assigning a dependency on another stream, the stream is added as a new dependency - of the parent stream. Dependent streams that share the same parent are not ordered with - respect to each other. For example, if streams B and C are dependent on stream A, and - if stream D is created with a dependency on stream A, this results in a dependency order - of A followed by B, C, and D in any order. - -
- /|\ - B C B D C -]]> -
- - An exclusive flag allows for the insertion of a new level of dependencies. The - exclusive flag causes the stream to become the sole dependency of its parent stream, - causing other dependencies to become dependent on the exclusive stream. In the - previous example, if stream D is created with an exclusive dependency on stream A, this - results in D becoming the dependency parent of B and C. - -
- D - B C / \ - B C -]]> -
- - Inside the dependency tree, a dependent stream SHOULD only be allocated resources if all - of the streams that it depends on (the chain of parent streams up to 0x0) are either - closed, or it is not possible to make progress on them. - - - A stream cannot depend on itself. An endpoint MUST treat this as a stream error of type PROTOCOL_ERROR. - -
- -
- - All dependent streams are allocated an integer weight between 1 and 256 (inclusive). - - - Streams with the same parent SHOULD be allocated resources proportionally based on their - weight. Thus, if stream B depends on stream A with weight 4, and C depends on stream A - with weight 12, and if no progress can be made on A, stream B ideally receives one third - of the resources allocated to stream C. - -
- -
- - Stream priorities are changed using the PRIORITY frame. Setting a - dependency causes a stream to become dependent on the identified parent stream. - - - Dependent streams move with their parent stream if the parent is reprioritized. Setting - a dependency with the exclusive flag for a reprioritized stream moves all the - dependencies of the new parent stream to become dependent on the reprioritized stream. - - - If a stream is made dependent on one of its own dependencies, the formerly dependent - stream is first moved to be dependent on the reprioritized stream's previous parent. - The moved dependency retains its weight. - -
- - For example, consider an original dependency tree where B and C depend on A, D and E - depend on C, and F depends on D. If A is made dependent on D, then D takes the place - of A. All other dependency relationships stay the same, except for F, which becomes - dependent on A if the reprioritization is exclusive. - - F B C ==> F A OR A - / \ | / \ /|\ - D E E B C B C F - | | | - F E E - (intermediate) (non-exclusive) (exclusive) -]]> -
-
- -
- - When a stream is removed from the dependency tree, its dependencies can be moved to - become dependent on the parent of the closed stream. The weights of new dependencies - are recalculated by distributing the weight of the dependency of the closed stream - proportionally based on the weights of its dependencies. - - - Streams that are removed from the dependency tree cause some prioritization information - to be lost. Resources are shared between streams with the same parent stream, which - means that if a stream in that set closes or becomes blocked, any spare capacity - allocated to a stream is distributed to the immediate neighbors of the stream. However, - if the common dependency is removed from the tree, those streams share resources with - streams at the next highest level. - - - For example, assume streams A and B share a parent, and streams C and D both depend on - stream A. Prior to the removal of stream A, if streams A and D are unable to proceed, - then stream C receives all the resources dedicated to stream A. If stream A is removed - from the tree, the weight of stream A is divided between streams C and D. If stream D - is still unable to proceed, this results in stream C receiving a reduced proportion of - resources. For equal starting weights, C receives one third, rather than one half, of - available resources. - - - It is possible for a stream to become closed while prioritization information that - creates a dependency on that stream is in transit. If a stream identified in a - dependency has no associated priority information, then the dependent stream is instead - assigned a default priority. This potentially creates - suboptimal prioritization, since the stream could be given a priority that is different - to what is intended. - - - To avoid these problems, an endpoint SHOULD retain stream prioritization state for a - period after streams become closed. The longer state is retained, the lower the chance - that streams are assigned incorrect or default priority values. - - - This could create a large state burden for an endpoint, so this state MAY be limited. - An endpoint MAY apply a fixed upper limit on the number of closed streams for which - prioritization state is tracked to limit state exposure. The amount of additional state - an endpoint maintains could be dependent on load; under high load, prioritization state - can be discarded to limit resource commitments. In extreme cases, an endpoint could - even discard prioritization state for active or reserved streams. If a fixed limit is - applied, endpoints SHOULD maintain state for at least as many streams as allowed by - their setting for SETTINGS_MAX_CONCURRENT_STREAMS. - - - An endpoint receiving a PRIORITY frame that changes the priority of a - closed stream SHOULD alter the dependencies of the streams that depend on it, if it has - retained enough state to do so. - -
- -
- - Providing priority information is optional. Streams are assigned a non-exclusive - dependency on stream 0x0 by default. Pushed streams - initially depend on their associated stream. In both cases, streams are assigned a - default weight of 16. - -
-
- -
- - HTTP/2 framing permits two classes of error: - - - An error condition that renders the entire connection unusable is a connection error. - - - An error in an individual stream is a stream error. - - - - - A list of error codes is included in . - - -
- - A connection error is any error which prevents further processing of the framing layer, - or which corrupts any connection state. - - - An endpoint that encounters a connection error SHOULD first send a GOAWAY - frame () with the stream identifier of the last stream that it - successfully received from its peer. The GOAWAY frame includes an error - code that indicates why the connection is terminating. After sending the - GOAWAY frame, the endpoint MUST close the TCP connection. - - - It is possible that the GOAWAY will not be reliably received by the - receiving endpoint (see ). In the event of a connection error, - GOAWAY only provides a best effort attempt to communicate with the peer - about why the connection is being terminated. - - - An endpoint can end a connection at any time. In particular, an endpoint MAY choose to - treat a stream error as a connection error. Endpoints SHOULD send a - GOAWAY frame when ending a connection, providing that circumstances - permit it. - -
- -
- - A stream error is an error related to a specific stream that does not affect processing - of other streams. - - - An endpoint that detects a stream error sends a RST_STREAM frame () that contains the stream identifier of the stream where the error - occurred. The RST_STREAM frame includes an error code that indicates the - type of error. - - - A RST_STREAM is the last frame that an endpoint can send on a stream. - The peer that sends the RST_STREAM frame MUST be prepared to receive any - frames that were sent or enqueued for sending by the remote peer. These frames can be - ignored, except where they modify connection state (such as the state maintained for - header compression, or flow control). - - - Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for - any stream. However, an endpoint MAY send additional RST_STREAM frames if - it receives frames on a closed stream after more than a round-trip time. This behavior - is permitted to deal with misbehaving implementations. - - - An endpoint MUST NOT send a RST_STREAM in response to an - RST_STREAM frame, to avoid looping. - -
- -
- - If the TCP connection is closed or reset while streams remain in open or half closed - states, then the endpoint MUST assume that those streams were abnormally interrupted and - could be incomplete. - -
-
- -
- - HTTP/2 permits extension of the protocol. Protocol extensions can be used to provide - additional services or alter any aspect of the protocol, within the limitations described - in this section. Extensions are effective only within the scope of a single HTTP/2 - connection. - - - Extensions are permitted to use new frame types, new - settings, or new error - codes. Registries are established for managing these extension points: frame types, settings and - error codes. - - - Implementations MUST ignore unknown or unsupported values in all extensible protocol - elements. Implementations MUST discard frames that have unknown or unsupported types. - This means that any of these extension points can be safely used by extensions without - prior arrangement or negotiation. However, extension frames that appear in the middle of - a header block are not permitted; these MUST be treated - as a connection error of type - PROTOCOL_ERROR. - - - However, extensions that could change the semantics of existing protocol components MUST - be negotiated before being used. For example, an extension that changes the layout of the - HEADERS frame cannot be used until the peer has given a positive signal - that this is acceptable. In this case, it could also be necessary to coordinate when the - revised layout comes into effect. Note that treating any frame other than - DATA frames as flow controlled is such a change in semantics, and can only - be done through negotiation. - - - This document doesn't mandate a specific method for negotiating the use of an extension, - but notes that a setting could be used for that - purpose. If both peers set a value that indicates willingness to use the extension, then - the extension can be used. If a setting is used for extension negotiation, the initial - value MUST be defined so that the extension is initially disabled. - -
-
- -
- - This specification defines a number of frame types, each identified by a unique 8-bit type - code. Each frame type serves a distinct purpose either in the establishment and management - of the connection as a whole, or of individual streams. - - - The transmission of specific frame types can alter the state of a connection. If endpoints - fail to maintain a synchronized view of the connection state, successful communication - within the connection will no longer be possible. Therefore, it is important that endpoints - have a shared comprehension of how the state is affected by the use any given frame. - - -
- - DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated - with a stream. One or more DATA frames are used, for instance, to carry HTTP request or - response payloads. - - - DATA frames MAY also contain arbitrary padding. Padding can be added to DATA frames to - obscure the size of messages. - -
- -
- - The DATA frame contains the following fields: - - - An 8-bit field containing the length of the frame padding in units of octets. This - field is optional and is only present if the PADDED flag is set. - - - Application data. The amount of data is the remainder of the frame payload after - subtracting the length of the other fields that are present. - - - Padding octets that contain no application semantic value. Padding octets MUST be set - to zero when sending and ignored when receiving. - - - - - - The DATA frame defines the following flags: - - - Bit 1 being set indicates that this frame is the last that the endpoint will send for - the identified stream. Setting this flag causes the stream to enter one of the "half closed" states or the "closed" state. - - - Bit 4 being set indicates that the Pad Length field and any padding that it describes - is present. - - - - - DATA frames MUST be associated with a stream. If a DATA frame is received whose stream - identifier field is 0x0, the recipient MUST respond with a connection error of type - PROTOCOL_ERROR. - - - DATA frames are subject to flow control and can only be sent when a stream is in the - "open" or "half closed (remote)" states. The entire DATA frame payload is included in flow - control, including Pad Length and Padding fields if present. If a DATA frame is received - whose stream is not in "open" or "half closed (local)" state, the recipient MUST respond - with a stream error of type - STREAM_CLOSED. - - - The total number of padding octets is determined by the value of the Pad Length field. If - the length of the padding is greater than the length of the frame payload, the recipient - MUST treat this as a connection error of - type PROTOCOL_ERROR. - - - A frame can be increased in size by one octet by including a Pad Length field with a - value of zero. - - - - - Padding is a security feature; see . - -
- -
- - The HEADERS frame (type=0x1) is used to open a stream, - and additionally carries a header block fragment. HEADERS frames can be sent on a stream - in the "open" or "half closed (remote)" states. - -
- -
- - The HEADERS frame payload has the following fields: - - - An 8-bit field containing the length of the frame padding in units of octets. This - field is only present if the PADDED flag is set. - - - A single bit flag indicates that the stream dependency is exclusive, see . This field is only present if the PRIORITY flag is set. - - - A 31-bit stream identifier for the stream that this stream depends on, see . This field is only present if the PRIORITY flag is set. - - - An 8-bit weight for the stream, see . Add one to the - value to obtain a weight between 1 and 256. This field is only present if the - PRIORITY flag is set. - - - A header block fragment. - - - Padding octets that contain no application semantic value. Padding octets MUST be set - to zero when sending and ignored when receiving. - - - - - - The HEADERS frame defines the following flags: - - - - Bit 1 being set indicates that the header block is - the last that the endpoint will send for the identified stream. Setting this flag - causes the stream to enter one of "half closed" - states. - - - A HEADERS frame carries the END_STREAM flag that signals the end of a stream. - However, a HEADERS frame with the END_STREAM flag set can be followed by - CONTINUATION frames on the same stream. Logically, the - CONTINUATION frames are part of the HEADERS frame. - - - - - Bit 3 being set indicates that this frame contains an entire header block and is not followed by any - CONTINUATION frames. - - - A HEADERS frame without the END_HEADERS flag set MUST be followed by a - CONTINUATION frame for the same stream. A receiver MUST treat the - receipt of any other type of frame or a frame on a different stream as a connection error of type - PROTOCOL_ERROR. - - - - - Bit 4 being set indicates that the Pad Length field and any padding that it - describes is present. - - - - - Bit 6 being set indicates that the Exclusive Flag (E), Stream Dependency, and Weight - fields are present; see . - - - - - - - The payload of a HEADERS frame contains a header block - fragment. A header block that does not fit within a HEADERS frame is continued in - a CONTINUATION frame. - - - - HEADERS frames MUST be associated with a stream. If a HEADERS frame is received whose - stream identifier field is 0x0, the recipient MUST respond with a connection error of type - PROTOCOL_ERROR. - - - - The HEADERS frame changes the connection state as described in . - - - - The HEADERS frame includes optional padding. Padding fields and flags are identical to - those defined for DATA frames. - - - Prioritization information in a HEADERS frame is logically equivalent to a separate - PRIORITY frame, but inclusion in HEADERS avoids the potential for churn in - stream prioritization when new streams are created. Priorization fields in HEADERS frames - subsequent to the first on a stream reprioritize the - stream. - -
- -
- - The PRIORITY frame (type=0x2) specifies the sender-advised - priority of a stream. It can be sent at any time for an existing stream, including - closed streams. This enables reprioritization of existing streams. - -
- -
- - The payload of a PRIORITY frame contains the following fields: - - - A single bit flag indicates that the stream dependency is exclusive, see . - - - A 31-bit stream identifier for the stream that this stream depends on, see . - - - An 8-bit weight for the identified stream dependency, see . Add one to the value to obtain a weight between 1 and 256. - - - - - - The PRIORITY frame does not define any flags. - - - - The PRIORITY frame is associated with an existing stream. If a PRIORITY frame is received - with a stream identifier of 0x0, the recipient MUST respond with a connection error of type - PROTOCOL_ERROR. - - - The PRIORITY frame can be sent on a stream in any of the "reserved (remote)", "open", - "half closed (local)", "half closed (remote)", or "closed" states, though it cannot be - sent between consecutive frames that comprise a single header - block. Note that this frame could arrive after processing or frame sending has - completed, which would cause it to have no effect on the current stream. For a stream - that is in the "half closed (remote)" or "closed" - state, this frame can only affect - processing of the current stream and not frame transmission. - - - The PRIORITY frame is the only frame that can be sent for a stream in the "closed" state. - This allows for the reprioritization of a group of dependent streams by altering the - priority of a parent stream, which might be closed. However, a PRIORITY frame sent on a - closed stream risks being ignored due to the peer having discarded priority state - information for that stream. - -
- -
- - The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream. When sent by - the initiator of a stream, it indicates that they wish to cancel the stream or that an - error condition has occurred. When sent by the receiver of a stream, it indicates that - either the receiver is rejecting the stream, requesting that the stream be cancelled, or - that an error condition has occurred. - -
- -
- - - The RST_STREAM frame contains a single unsigned, 32-bit integer identifying the error code. The error code indicates why the stream is being - terminated. - - - - The RST_STREAM frame does not define any flags. - - - - The RST_STREAM frame fully terminates the referenced stream and causes it to enter the - closed state. After receiving a RST_STREAM on a stream, the receiver MUST NOT send - additional frames for that stream, with the exception of PRIORITY. However, - after sending the RST_STREAM, the sending endpoint MUST be prepared to receive and process - additional frames sent on the stream that might have been sent by the peer prior to the - arrival of the RST_STREAM. - - - - RST_STREAM frames MUST be associated with a stream. If a RST_STREAM frame is received - with a stream identifier of 0x0, the recipient MUST treat this as a connection error of type - PROTOCOL_ERROR. - - - - RST_STREAM frames MUST NOT be sent for a stream in the "idle" state. If a RST_STREAM - frame identifying an idle stream is received, the recipient MUST treat this as a connection error of type - PROTOCOL_ERROR. - - -
- -
- - The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints - communicate, such as preferences and constraints on peer behavior. The SETTINGS frame is - also used to acknowledge the receipt of those parameters. Individually, a SETTINGS - parameter can also be referred to as a "setting". - - - SETTINGS parameters are not negotiated; they describe characteristics of the sending peer, - which are used by the receiving peer. Different values for the same parameter can be - advertised by each peer. For example, a client might set a high initial flow control - window, whereas a server might set a lower value to conserve resources. - - - - A SETTINGS frame MUST be sent by both endpoints at the start of a connection, and MAY be - sent at any other time by either endpoint over the lifetime of the connection. - Implementations MUST support all of the parameters defined by this specification. - - - - Each parameter in a SETTINGS frame replaces any existing value for that parameter. - Parameters are processed in the order in which they appear, and a receiver of a SETTINGS - frame does not need to maintain any state other than the current value of its - parameters. Therefore, the value of a SETTINGS parameter is the last value that is seen by - a receiver. - - - SETTINGS parameters are acknowledged by the receiving peer. To enable this, the SETTINGS - frame defines the following flag: - - - Bit 1 being set indicates that this frame acknowledges receipt and application of the - peer's SETTINGS frame. When this bit is set, the payload of the SETTINGS frame MUST - be empty. Receipt of a SETTINGS frame with the ACK flag set and a length field value - other than 0 MUST be treated as a connection - error of type FRAME_SIZE_ERROR. For more info, see Settings Synchronization. - - - - - SETTINGS frames always apply to a connection, never a single stream. The stream - identifier for a SETTINGS frame MUST be zero (0x0). If an endpoint receives a SETTINGS - frame whose stream identifier field is anything other than 0x0, the endpoint MUST respond - with a connection error of type - PROTOCOL_ERROR. - - - The SETTINGS frame affects connection state. A badly formed or incomplete SETTINGS frame - MUST be treated as a connection error of type - PROTOCOL_ERROR. - - -
- - The payload of a SETTINGS frame consists of zero or more parameters, each consisting of - an unsigned 16-bit setting identifier and an unsigned 32-bit value. - - -
- -
-
- -
- - The following parameters are defined: - - - - Allows the sender to inform the remote endpoint of the maximum size of the header - compression table used to decode header blocks, in octets. The encoder can select - any size equal to or less than this value by using signaling specific to the - header compression format inside a header block. The initial value is 4,096 - octets. - - - - - This setting can be use to disable server - push. An endpoint MUST NOT send a PUSH_PROMISE frame if it - receives this parameter set to a value of 0. An endpoint that has both set this - parameter to 0 and had it acknowledged MUST treat the receipt of a - PUSH_PROMISE frame as a connection error of type - PROTOCOL_ERROR. - - - The initial value is 1, which indicates that server push is permitted. Any value - other than 0 or 1 MUST be treated as a connection error of type - PROTOCOL_ERROR. - - - - - Indicates the maximum number of concurrent streams that the sender will allow. - This limit is directional: it applies to the number of streams that the sender - permits the receiver to create. Initially there is no limit to this value. It is - recommended that this value be no smaller than 100, so as to not unnecessarily - limit parallelism. - - - A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special - by endpoints. A zero value does prevent the creation of new streams, however this - can also happen for any limit that is exhausted with active streams. Servers - SHOULD only set a zero value for short durations; if a server does not wish to - accept requests, closing the connection could be preferable. - - - - - Indicates the sender's initial window size (in octets) for stream level flow - control. The initial value is 216-1 (65,535) octets. - - - This setting affects the window size of all streams, including existing streams, - see . - - - Values above the maximum flow control window size of 231-1 MUST - be treated as a connection error of - type FLOW_CONTROL_ERROR. - - - - - Indicates the size of the largest frame payload that the sender is willing to - receive, in octets. - - - The initial value is 214 (16,384) octets. The value advertised by - an endpoint MUST be between this initial value and the maximum allowed frame size - (224-1 or 16,777,215 octets), inclusive. Values outside this range - MUST be treated as a connection error - of type PROTOCOL_ERROR. - - - - - This advisory setting informs a peer of the maximum size of header list that the - sender is prepared to accept, in octets. The value is based on the uncompressed - size of header fields, including the length of the name and value in octets plus - an overhead of 32 octets for each header field. - - - For any given request, a lower limit than what is advertised MAY be enforced. The - initial value of this setting is unlimited. - - - - - - An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier - MUST ignore that setting. - -
- -
- - Most values in SETTINGS benefit from or require an understanding of when the peer has - received and applied the changed parameter values. In order to provide - such synchronization timepoints, the recipient of a SETTINGS frame in which the ACK flag - is not set MUST apply the updated parameters as soon as possible upon receipt. - - - The values in the SETTINGS frame MUST be processed in the order they appear, with no - other frame processing between values. Unsupported parameters MUST be ignored. Once - all values have been processed, the recipient MUST immediately emit a SETTINGS frame - with the ACK flag set. Upon receiving a SETTINGS frame with the ACK flag set, the sender - of the altered parameters can rely on the setting having been applied. - - - If the sender of a SETTINGS frame does not receive an acknowledgement within a - reasonable amount of time, it MAY issue a connection error of type - SETTINGS_TIMEOUT. - -
-
- -
- - The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of - streams the sender intends to initiate. The PUSH_PROMISE frame includes the unsigned - 31-bit identifier of the stream the endpoint plans to create along with a set of headers - that provide additional context for the stream. contains a - thorough description of the use of PUSH_PROMISE frames. - - -
- -
- - The PUSH_PROMISE frame payload has the following fields: - - - An 8-bit field containing the length of the frame padding in units of octets. This - field is only present if the PADDED flag is set. - - - A single reserved bit. - - - An unsigned 31-bit integer that identifies the stream that is reserved by the - PUSH_PROMISE. The promised stream identifier MUST be a valid choice for the next - stream sent by the sender (see new stream - identifier). - - - A header block fragment containing request header - fields. - - - Padding octets. - - - - - - The PUSH_PROMISE frame defines the following flags: - - - - Bit 3 being set indicates that this frame contains an entire header block and is not followed by any - CONTINUATION frames. - - - A PUSH_PROMISE frame without the END_HEADERS flag set MUST be followed by a - CONTINUATION frame for the same stream. A receiver MUST treat the receipt of any - other type of frame or a frame on a different stream as a connection error of type - PROTOCOL_ERROR. - - - - - Bit 4 being set indicates that the Pad Length field and any padding that it - describes is present. - - - - - - - PUSH_PROMISE frames MUST be associated with an existing, peer-initiated stream. The stream - identifier of a PUSH_PROMISE frame indicates the stream it is associated with. If the - stream identifier field specifies the value 0x0, a recipient MUST respond with a connection error of type - PROTOCOL_ERROR. - - - - Promised streams are not required to be used in the order they are promised. The - PUSH_PROMISE only reserves stream identifiers for later use. - - - - PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of the - peer endpoint is set to 0. An endpoint that has set this setting and has received - acknowledgement MUST treat the receipt of a PUSH_PROMISE frame as a connection error of type - PROTOCOL_ERROR. - - - Recipients of PUSH_PROMISE frames can choose to reject promised streams by returning a - RST_STREAM referencing the promised stream identifier back to the sender of - the PUSH_PROMISE. - - - - A PUSH_PROMISE frame modifies the connection state in two ways. The inclusion of a header block potentially modifies the state maintained for - header compression. PUSH_PROMISE also reserves a stream for later use, causing the - promised stream to enter the "reserved" state. A sender MUST NOT send a PUSH_PROMISE on a - stream unless that stream is either "open" or "half closed (remote)"; the sender MUST - ensure that the promised stream is a valid choice for a new stream identifier (that is, the promised stream MUST - be in the "idle" state). - - - Since PUSH_PROMISE reserves a stream, ignoring a PUSH_PROMISE frame causes the stream - state to become indeterminate. A receiver MUST treat the receipt of a PUSH_PROMISE on a - stream that is neither "open" nor "half closed (local)" as a connection error of type - PROTOCOL_ERROR. However, an endpoint that has sent - RST_STREAM on the associated stream MUST handle PUSH_PROMISE frames that - might have been created before the RST_STREAM frame is received and - processed. - - - A receiver MUST treat the receipt of a PUSH_PROMISE that promises an illegal stream identifier (that is, an identifier for a - stream that is not currently in the "idle" state) as a connection error of type - PROTOCOL_ERROR. - - - - The PUSH_PROMISE frame includes optional padding. Padding fields and flags are identical - to those defined for DATA frames. - -
- -
- - The PING frame (type=0x6) is a mechanism for measuring a minimal round trip time from the - sender, as well as determining whether an idle connection is still functional. PING - frames can be sent from any endpoint. - -
- -
- - - In addition to the frame header, PING frames MUST contain 8 octets of data in the payload. - A sender can include any value it chooses and use those bytes in any fashion. - - - Receivers of a PING frame that does not include an ACK flag MUST send a PING frame with - the ACK flag set in response, with an identical payload. PING responses SHOULD be given - higher priority than any other frame. - - - - The PING frame defines the following flags: - - - Bit 1 being set indicates that this PING frame is a PING response. An endpoint MUST - set this flag in PING responses. An endpoint MUST NOT respond to PING frames - containing this flag. - - - - - PING frames are not associated with any individual stream. If a PING frame is received - with a stream identifier field value other than 0x0, the recipient MUST respond with a - connection error of type - PROTOCOL_ERROR. - - - Receipt of a PING frame with a length field value other than 8 MUST be treated as a connection error of type - FRAME_SIZE_ERROR. - - -
- -
- - The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this - connection. GOAWAY can be sent by either the client or the server. Once sent, the sender - will ignore frames sent on any new streams with identifiers higher than the included last - stream identifier. Receivers of a GOAWAY frame MUST NOT open additional streams on the - connection, although a new connection can be established for new streams. - - - The purpose of this frame is to allow an endpoint to gracefully stop accepting new - streams, while still finishing processing of previously established streams. This enables - administrative actions, like server maintenance. - - - There is an inherent race condition between an endpoint starting new streams and the - remote sending a GOAWAY frame. To deal with this case, the GOAWAY contains the stream - identifier of the last peer-initiated stream which was or might be processed on the - sending endpoint in this connection. For instance, if the server sends a GOAWAY frame, - the identified stream is the highest numbered stream initiated by the client. - - - If the receiver of the GOAWAY has sent data on streams with a higher stream identifier - than what is indicated in the GOAWAY frame, those streams are not or will not be - processed. The receiver of the GOAWAY frame can treat the streams as though they had - never been created at all, thereby allowing those streams to be retried later on a new - connection. - - - Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote - can know whether a stream has been partially processed or not. For example, if an HTTP - client sends a POST at the same time that a server closes a connection, the client cannot - know if the server started to process that POST request if the server does not send a - GOAWAY frame to indicate what streams it might have acted on. - - - An endpoint might choose to close a connection without sending GOAWAY for misbehaving - peers. - - -
- -
- - The GOAWAY frame does not define any flags. - - - The GOAWAY frame applies to the connection, not a specific stream. An endpoint MUST treat - a GOAWAY frame with a stream identifier other than 0x0 as a connection error of type - PROTOCOL_ERROR. - - - The last stream identifier in the GOAWAY frame contains the highest numbered stream - identifier for which the sender of the GOAWAY frame might have taken some action on, or - might yet take action on. All streams up to and including the identified stream might - have been processed in some way. The last stream identifier can be set to 0 if no streams - were processed. - - - In this context, "processed" means that some data from the stream was passed to some - higher layer of software that might have taken some action as a result. - - - If a connection terminates without a GOAWAY frame, the last stream identifier is - effectively the highest possible stream identifier. - - - On streams with lower or equal numbered identifiers that were not closed completely prior - to the connection being closed, re-attempting requests, transactions, or any protocol - activity is not possible, with the exception of idempotent actions like HTTP GET, PUT, or - DELETE. Any protocol activity that uses higher numbered streams can be safely retried - using a new connection. - - - Activity on streams numbered lower or equal to the last stream identifier might still - complete successfully. The sender of a GOAWAY frame might gracefully shut down a - connection by sending a GOAWAY frame, maintaining the connection in an open state until - all in-progress streams complete. - - - An endpoint MAY send multiple GOAWAY frames if circumstances change. For instance, an - endpoint that sends GOAWAY with NO_ERROR during graceful shutdown could - subsequently encounter an condition that requires immediate termination of the connection. - The last stream identifier from the last GOAWAY frame received indicates which streams - could have been acted upon. Endpoints MUST NOT increase the value they send in the last - stream identifier, since the peers might already have retried unprocessed requests on - another connection. - - - A client that is unable to retry requests loses all requests that are in flight when the - server closes the connection. This is especially true for intermediaries that might - not be serving clients using HTTP/2. A server that is attempting to gracefully shut down - a connection SHOULD send an initial GOAWAY frame with the last stream identifier set to - 231-1 and a NO_ERROR code. This signals to the client that - a shutdown is imminent and that no further requests can be initiated. After waiting at - least one round trip time, the server can send another GOAWAY frame with an updated last - stream identifier. This ensures that a connection can be cleanly shut down without losing - requests. - - - - After sending a GOAWAY frame, the sender can discard frames for streams with identifiers - higher than the identified last stream. However, any frames that alter connection state - cannot be completely ignored. For instance, HEADERS, - PUSH_PROMISE and CONTINUATION frames MUST be minimally - processed to ensure the state maintained for header compression is consistent (see ); similarly DATA frames MUST be counted toward the connection flow - control window. Failure to process these frames can cause flow control or header - compression state to become unsynchronized. - - - - The GOAWAY frame also contains a 32-bit error code that - contains the reason for closing the connection. - - - Endpoints MAY append opaque data to the payload of any GOAWAY frame. Additional debug - data is intended for diagnostic purposes only and carries no semantic value. Debug - information could contain security- or privacy-sensitive data. Logged or otherwise - persistently stored debug data MUST have adequate safeguards to prevent unauthorized - access. - -
- -
- - The WINDOW_UPDATE frame (type=0x8) is used to implement flow control; see for an overview. - - - Flow control operates at two levels: on each individual stream and on the entire - connection. - - - Both types of flow control are hop-by-hop; that is, only between the two endpoints. - Intermediaries do not forward WINDOW_UPDATE frames between dependent connections. - However, throttling of data transfer by any receiver can indirectly cause the propagation - of flow control information toward the original sender. - - - Flow control only applies to frames that are identified as being subject to flow control. - Of the frame types defined in this document, this includes only DATA frames. - Frames that are exempt from flow control MUST be accepted and processed, unless the - receiver is unable to assign resources to handling the frame. A receiver MAY respond with - a stream error or connection error of type - FLOW_CONTROL_ERROR if it is unable to accept a frame. - -
- -
- - The payload of a WINDOW_UPDATE frame is one reserved bit, plus an unsigned 31-bit integer - indicating the number of octets that the sender can transmit in addition to the existing - flow control window. The legal range for the increment to the flow control window is 1 to - 231-1 (0x7fffffff) octets. - - - The WINDOW_UPDATE frame does not define any flags. - - - The WINDOW_UPDATE frame can be specific to a stream or to the entire connection. In the - former case, the frame's stream identifier indicates the affected stream; in the latter, - the value "0" indicates that the entire connection is the subject of the frame. - - - A receiver MUST treat the receipt of a WINDOW_UPDATE frame with an flow control window - increment of 0 as a stream error of type - PROTOCOL_ERROR; errors on the connection flow control window MUST be - treated as a connection error. - - - WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag. - This means that a receiver could receive a WINDOW_UPDATE frame on a "half closed (remote)" - or "closed" stream. A receiver MUST NOT treat this as an error, see . - - - A receiver that receives a flow controlled frame MUST always account for its contribution - against the connection flow control window, unless the receiver treats this as a connection error. This is necessary even if the - frame is in error. Since the sender counts the frame toward the flow control window, if - the receiver does not, the flow control window at sender and receiver can become - different. - - -
- - Flow control in HTTP/2 is implemented using a window kept by each sender on every - stream. The flow control window is a simple integer value that indicates how many octets - of data the sender is permitted to transmit; as such, its size is a measure of the - buffering capacity of the receiver. - - - Two flow control windows are applicable: the stream flow control window and the - connection flow control window. The sender MUST NOT send a flow controlled frame with a - length that exceeds the space available in either of the flow control windows advertised - by the receiver. Frames with zero length with the END_STREAM flag set (that is, an - empty DATA frame) MAY be sent if there is no available space in either - flow control window. - - - For flow control calculations, the 9 octet frame header is not counted. - - - After sending a flow controlled frame, the sender reduces the space available in both - windows by the length of the transmitted frame. - - - The receiver of a frame sends a WINDOW_UPDATE frame as it consumes data and frees up - space in flow control windows. Separate WINDOW_UPDATE frames are sent for the stream - and connection level flow control windows. - - - A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the - amount specified in the frame. - - - A sender MUST NOT allow a flow control window to exceed 231-1 octets. - If a sender receives a WINDOW_UPDATE that causes a flow control window to exceed this - maximum it MUST terminate either the stream or the connection, as appropriate. For - streams, the sender sends a RST_STREAM with the error code of - FLOW_CONTROL_ERROR code; for the connection, a GOAWAY - frame with a FLOW_CONTROL_ERROR code. - - - Flow controlled frames from the sender and WINDOW_UPDATE frames from the receiver are - completely asynchronous with respect to each other. This property allows a receiver to - aggressively update the window size kept by the sender to prevent streams from stalling. - -
- -
- - When an HTTP/2 connection is first established, new streams are created with an initial - flow control window size of 65,535 octets. The connection flow control window is 65,535 - octets. Both endpoints can adjust the initial window size for new streams by including - a value for SETTINGS_INITIAL_WINDOW_SIZE in the SETTINGS - frame that forms part of the connection preface. The connection flow control window can - only be changed using WINDOW_UPDATE frames. - - - Prior to receiving a SETTINGS frame that sets a value for - SETTINGS_INITIAL_WINDOW_SIZE, an endpoint can only use the default - initial window size when sending flow controlled frames. Similarly, the connection flow - control window is set to the default initial window size until a WINDOW_UPDATE frame is - received. - - - A SETTINGS frame can alter the initial flow control window size for all - current streams. When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, - a receiver MUST adjust the size of all stream flow control windows that it maintains by - the difference between the new value and the old value. - - - A change to SETTINGS_INITIAL_WINDOW_SIZE can cause the available space in - a flow control window to become negative. A sender MUST track the negative flow control - window, and MUST NOT send new flow controlled frames until it receives WINDOW_UPDATE - frames that cause the flow control window to become positive. - - - For example, if the client sends 60KB immediately on connection establishment, and the - server sets the initial window size to be 16KB, the client will recalculate the - available flow control window to be -44KB on receipt of the SETTINGS - frame. The client retains a negative flow control window until WINDOW_UPDATE frames - restore the window to being positive, after which the client can resume sending. - - - A SETTINGS frame cannot alter the connection flow control window. - - - An endpoint MUST treat a change to SETTINGS_INITIAL_WINDOW_SIZE that - causes any flow control window to exceed the maximum size as a connection error of type - FLOW_CONTROL_ERROR. - -
- -
- - A receiver that wishes to use a smaller flow control window than the current size can - send a new SETTINGS frame. However, the receiver MUST be prepared to - receive data that exceeds this window size, since the sender might send data that - exceeds the lower limit prior to processing the SETTINGS frame. - - - After sending a SETTINGS frame that reduces the initial flow control window size, a - receiver has two options for handling streams that exceed flow control limits: - - - The receiver can immediately send RST_STREAM with - FLOW_CONTROL_ERROR error code for the affected streams. - - - The receiver can accept the streams and tolerate the resulting head of line - blocking, sending WINDOW_UPDATE frames as it consumes data. - - - -
-
- -
- - The CONTINUATION frame (type=0x9) is used to continue a sequence of header block fragments. Any number of CONTINUATION frames can - be sent on an existing stream, as long as the preceding frame is on the same stream and is - a HEADERS, PUSH_PROMISE or CONTINUATION frame without the - END_HEADERS flag set. - - -
- -
- - The CONTINUATION frame payload contains a header block - fragment. - - - - The CONTINUATION frame defines the following flag: - - - - Bit 3 being set indicates that this frame ends a header - block. - - - If the END_HEADERS bit is not set, this frame MUST be followed by another - CONTINUATION frame. A receiver MUST treat the receipt of any other type of frame or - a frame on a different stream as a connection - error of type PROTOCOL_ERROR. - - - - - - - The CONTINUATION frame changes the connection state as defined in . - - - - CONTINUATION frames MUST be associated with a stream. If a CONTINUATION frame is received - whose stream identifier field is 0x0, the recipient MUST respond with a connection error of type PROTOCOL_ERROR. - - - - A CONTINUATION frame MUST be preceded by a HEADERS, - PUSH_PROMISE or CONTINUATION frame without the END_HEADERS flag set. A - recipient that observes violation of this rule MUST respond with a connection error of type - PROTOCOL_ERROR. - -
-
- -
- - Error codes are 32-bit fields that are used in RST_STREAM and - GOAWAY frames to convey the reasons for the stream or connection error. - - - - Error codes share a common code space. Some error codes apply only to either streams or the - entire connection and have no defined semantics in the other context. - - - - The following error codes are defined: - - - The associated condition is not as a result of an error. For example, a - GOAWAY might include this code to indicate graceful shutdown of a - connection. - - - The endpoint detected an unspecific protocol error. This error is for use when a more - specific error code is not available. - - - The endpoint encountered an unexpected internal error. - - - The endpoint detected that its peer violated the flow control protocol. - - - The endpoint sent a SETTINGS frame, but did not receive a response in a - timely manner. See Settings Synchronization. - - - The endpoint received a frame after a stream was half closed. - - - The endpoint received a frame with an invalid size. - - - The endpoint refuses the stream prior to performing any application processing, see - for details. - - - Used by the endpoint to indicate that the stream is no longer needed. - - - The endpoint is unable to maintain the header compression context for the connection. - - - The connection established in response to a CONNECT - request was reset or abnormally closed. - - - The endpoint detected that its peer is exhibiting a behavior that might be generating - excessive load. - - - The underlying transport has properties that do not meet minimum security - requirements (see ). - - - - - Unknown or unsupported error codes MUST NOT trigger any special behavior. These MAY be - treated by an implementation as being equivalent to INTERNAL_ERROR. - -
- -
- - HTTP/2 is intended to be as compatible as possible with current uses of HTTP. This means - that, from the application perspective, the features of the protocol are largely - unchanged. To achieve this, all request and response semantics are preserved, although the - syntax of conveying those semantics has changed. - - - Thus, the specification and requirements of HTTP/1.1 Semantics and Content , Conditional Requests , Range Requests , Caching and Authentication are applicable to HTTP/2. Selected portions of HTTP/1.1 Message Syntax - and Routing , such as the HTTP and HTTPS URI schemes, are also - applicable in HTTP/2, but the expression of those semantics for this protocol are defined - in the sections below. - - -
- - A client sends an HTTP request on a new stream, using a previously unused stream identifier. A server sends an HTTP response on - the same stream as the request. - - - An HTTP message (request or response) consists of: - - - for a response only, zero or more HEADERS frames (each followed by zero - or more CONTINUATION frames) containing the message headers of - informational (1xx) HTTP responses (see and ), - and - - - one HEADERS frame (followed by zero or more CONTINUATION - frames) containing the message headers (see ), and - - - zero or more DATA frames containing the message payload (see ), and - - - optionally, one HEADERS frame, followed by zero or more - CONTINUATION frames containing the trailer-part, if present (see ). - - - The last frame in the sequence bears an END_STREAM flag, noting that a - HEADERS frame bearing the END_STREAM flag can be followed by - CONTINUATION frames that carry any remaining portions of the header block. - - - Other frames (from any stream) MUST NOT occur between either HEADERS frame - and any CONTINUATION frames that might follow. - - - - Trailing header fields are carried in a header block that also terminates the stream. - That is, a sequence starting with a HEADERS frame, followed by zero or more - CONTINUATION frames, where the HEADERS frame bears an - END_STREAM flag. Header blocks after the first that do not terminate the stream are not - part of an HTTP request or response. - - - A HEADERS frame (and associated CONTINUATION frames) can - only appear at the start or end of a stream. An endpoint that receives a - HEADERS frame without the END_STREAM flag set after receiving a final - (non-informational) status code MUST treat the corresponding request or response as malformed. - - - - An HTTP request/response exchange fully consumes a single stream. A request starts with - the HEADERS frame that puts the stream into an "open" state. The request - ends with a frame bearing END_STREAM, which causes the stream to become "half closed - (local)" for the client and "half closed (remote)" for the server. A response starts with - a HEADERS frame and ends with a frame bearing END_STREAM, which places the - stream in the "closed" state. - - - -
- - HTTP/2 removes support for the 101 (Switching Protocols) informational status code - (). - - - The semantics of 101 (Switching Protocols) aren't applicable to a multiplexed protocol. - Alternative protocols are able to use the same mechanisms that HTTP/2 uses to negotiate - their use (see ). - -
- -
- - HTTP header fields carry information as a series of key-value pairs. For a listing of - registered HTTP headers, see the Message Header Field Registry maintained at . - - -
- - While HTTP/1.x used the message start-line (see ) to convey the target URI and method of the request, and the - status code for the response, HTTP/2 uses special pseudo-header fields beginning with - ':' character (ASCII 0x3a) for this purpose. - - - Pseudo-header fields are not HTTP header fields. Endpoints MUST NOT generate - pseudo-header fields other than those defined in this document. - - - Pseudo-header fields are only valid in the context in which they are defined. - Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header - fields defined for responses MUST NOT appear in requests. Pseudo-header fields MUST - NOT appear in trailers. Endpoints MUST treat a request or response that contains - undefined or invalid pseudo-header fields as malformed. - - - Just as in HTTP/1.x, header field names are strings of ASCII characters that are - compared in a case-insensitive fashion. However, header field names MUST be converted - to lowercase prior to their encoding in HTTP/2. A request or response containing - uppercase header field names MUST be treated as malformed. - - - All pseudo-header fields MUST appear in the header block before regular header fields. - Any request or response that contains a pseudo-header field that appears in a header - block after a regular header field MUST be treated as malformed. - -
- -
- - HTTP/2 does not use the Connection header field to - indicate connection-specific header fields; in this protocol, connection-specific - metadata is conveyed by other means. An endpoint MUST NOT generate a HTTP/2 message - containing connection-specific header fields; any message containing - connection-specific header fields MUST be treated as malformed. - - - This means that an intermediary transforming an HTTP/1.x message to HTTP/2 will need - to remove any header fields nominated by the Connection header field, along with the - Connection header field itself. Such intermediaries SHOULD also remove other - connection-specific header fields, such as Keep-Alive, Proxy-Connection, - Transfer-Encoding and Upgrade, even if they are not nominated by Connection. - - - One exception to this is the TE header field, which MAY be present in an HTTP/2 - request, but when it is MUST NOT contain any value other than "trailers". - - - - - HTTP/2 purposefully does not support upgrade to another protocol. The handshake - methods described in are believed sufficient to - negotiate the use of alternative protocols. - - - -
- -
- - The following pseudo-header fields are defined for HTTP/2 requests: - - - - The :method pseudo-header field includes the HTTP - method (). - - - - - The :scheme pseudo-header field includes the scheme - portion of the target URI (). - - - :scheme is not restricted to http and https schemed URIs. A - proxy or gateway can translate requests for non-HTTP schemes, enabling the use - of HTTP to interact with non-HTTP services. - - - - - The :authority pseudo-header field includes the - authority portion of the target URI (). The authority MUST NOT include the deprecated userinfo subcomponent for http - or https schemed URIs. - - - To ensure that the HTTP/1.1 request line can be reproduced accurately, this - pseudo-header field MUST be omitted when translating from an HTTP/1.1 request - that has a request target in origin or asterisk form (see ). Clients that generate - HTTP/2 requests directly SHOULD use the :authority pseudo-header - field instead of the Host header field. An - intermediary that converts an HTTP/2 request to HTTP/1.1 MUST create a Host header field if one is not present in a request by - copying the value of the :authority pseudo-header - field. - - - - - The :path pseudo-header field includes the path and - query parts of the target URI (the path-absolute - production from and optionally a '?' character - followed by the query production, see and ). A request in asterisk form includes the value '*' for the - :path pseudo-header field. - - - This pseudo-header field MUST NOT be empty for http - or https URIs; http or - https URIs that do not contain a path component - MUST include a value of '/'. The exception to this rule is an OPTIONS request - for an http or https - URI that does not include a path component; these MUST include a :path pseudo-header field with a value of '*' (see ). - - - - - - All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields, unless it is a CONNECT request. An HTTP request that omits mandatory - pseudo-header fields is malformed. - - - HTTP/2 does not define a way to carry the version identifier that is included in the - HTTP/1.1 request line. - -
- -
- - For HTTP/2 responses, a single :status pseudo-header - field is defined that carries the HTTP status code field (see ). This pseudo-header field MUST be included in all - responses, otherwise the response is malformed. - - - HTTP/2 does not define a way to carry the version or reason phrase that is included in - an HTTP/1.1 status line. - -
- -
- - The Cookie header field can carry a significant amount of - redundant data. - - - The Cookie header field uses a semi-colon (";") to delimit cookie-pairs (or "crumbs"). - This header field doesn't follow the list construction rules in HTTP (see ), which prevents cookie-pairs from - being separated into different name-value pairs. This can significantly reduce - compression efficiency as individual cookie-pairs are updated. - - - To allow for better compression efficiency, the Cookie header field MAY be split into - separate header fields, each with one or more cookie-pairs. If there are multiple - Cookie header fields after decompression, these MUST be concatenated into a single - octet string using the two octet delimiter of 0x3B, 0x20 (the ASCII string "; ") - before being passed into a non-HTTP/2 context, such as an HTTP/1.1 connection, or a - generic HTTP server application. - -
- - Therefore, the following two lists of Cookie header fields are semantically - equivalent. - - -
-
- -
- - A malformed request or response is one that is an otherwise valid sequence of HTTP/2 - frames, but is otherwise invalid due to the presence of extraneous frames, prohibited - header fields, the absence of mandatory header fields, or the inclusion of uppercase - header field names. - - - A request or response that includes an entity body can include a content-length header field. A request or response is also - malformed if the value of a content-length header field - does not equal the sum of the DATA frame payload lengths that form the - body. A response that is defined to have no payload, as described in , can have a non-zero - content-length header field, even though no content is - included in DATA frames. - - - Intermediaries that process HTTP requests or responses (i.e., any intermediary not - acting as a tunnel) MUST NOT forward a malformed request or response. Malformed - requests or responses that are detected MUST be treated as a stream error of type PROTOCOL_ERROR. - - - For malformed requests, a server MAY send an HTTP response prior to closing or - resetting the stream. Clients MUST NOT accept a malformed response. Note that these - requirements are intended to protect against several types of common attacks against - HTTP; they are deliberately strict, because being permissive can expose - implementations to these vulnerabilities. - -
-
- -
- - This section shows HTTP/1.1 requests and responses, with illustrations of equivalent - HTTP/2 requests and responses. - - - An HTTP GET request includes request header fields and no body and is therefore - transmitted as a single HEADERS frame, followed by zero or more - CONTINUATION frames containing the serialized block of request header - fields. The HEADERS frame in the following has both the END_HEADERS and - END_STREAM flags set; no CONTINUATION frames are sent: - - -
- + END_STREAM - Accept: image/jpeg + END_HEADERS - :method = GET - :scheme = https - :path = /resource - host = example.org - accept = image/jpeg -]]> -
- - - Similarly, a response that includes only response header fields is transmitted as a - HEADERS frame (again, followed by zero or more - CONTINUATION frames) containing the serialized block of response header - fields. - - -
- + END_STREAM - Expires: Thu, 23 Jan ... + END_HEADERS - :status = 304 - etag = "xyzzy" - expires = Thu, 23 Jan ... -]]> -
- - - An HTTP POST request that includes request header fields and payload data is transmitted - as one HEADERS frame, followed by zero or more - CONTINUATION frames containing the request header fields, followed by one - or more DATA frames, with the last CONTINUATION (or - HEADERS) frame having the END_HEADERS flag set and the final - DATA frame having the END_STREAM flag set: - - -
- - END_STREAM - Content-Type: image/jpeg - END_HEADERS - Content-Length: 123 :method = POST - :path = /resource - {binary data} :scheme = https - - CONTINUATION - + END_HEADERS - content-type = image/jpeg - host = example.org - content-length = 123 - - DATA - + END_STREAM - {binary data} -]]> - - Note that data contributing to any given header field could be spread between header - block fragments. The allocation of header fields to frames in this example is - illustrative only. - -
- - - A response that includes header fields and payload data is transmitted as a - HEADERS frame, followed by zero or more CONTINUATION - frames, followed by one or more DATA frames, with the last - DATA frame in the sequence having the END_STREAM flag set: - - -
- - END_STREAM - Content-Length: 123 + END_HEADERS - :status = 200 - {binary data} content-type = image/jpeg - content-length = 123 - - DATA - + END_STREAM - {binary data} -]]> -
- - - Trailing header fields are sent as a header block after both the request or response - header block and all the DATA frames have been sent. The - HEADERS frame starting the trailers header block has the END_STREAM flag - set. - - -
- - END_STREAM - Transfer-Encoding: chunked + END_HEADERS - Trailer: Foo :status = 200 - content-length = 123 - 123 content-type = image/jpeg - {binary data} trailer = Foo - 0 - Foo: bar DATA - - END_STREAM - {binary data} - - HEADERS - + END_STREAM - + END_HEADERS - foo = bar -]]> -
- - -
- - An informational response using a 1xx status code other than 101 is transmitted as a - HEADERS frame, followed by zero or more CONTINUATION - frames: - - - END_STREAM - + END_HEADERS - :status = 103 - extension-field = bar -]]> -
-
- -
- - In HTTP/1.1, an HTTP client is unable to retry a non-idempotent request when an error - occurs, because there is no means to determine the nature of the error. It is possible - that some server processing occurred prior to the error, which could result in - undesirable effects if the request were reattempted. - - - HTTP/2 provides two mechanisms for providing a guarantee to a client that a request has - not been processed: - - - The GOAWAY frame indicates the highest stream number that might have - been processed. Requests on streams with higher numbers are therefore guaranteed to - be safe to retry. - - - The REFUSED_STREAM error code can be included in a - RST_STREAM frame to indicate that the stream is being closed prior to - any processing having occurred. Any request that was sent on the reset stream can - be safely retried. - - - - - Requests that have not been processed have not failed; clients MAY automatically retry - them, even those with non-idempotent methods. - - - A server MUST NOT indicate that a stream has not been processed unless it can guarantee - that fact. If frames that are on a stream are passed to the application layer for any - stream, then REFUSED_STREAM MUST NOT be used for that stream, and a - GOAWAY frame MUST include a stream identifier that is greater than or - equal to the given stream identifier. - - - In addition to these mechanisms, the PING frame provides a way for a - client to easily test a connection. Connections that remain idle can become broken as - some middleboxes (for instance, network address translators, or load balancers) silently - discard connection bindings. The PING frame allows a client to safely - test whether a connection is still active without sending a request. - -
-
- -
- - HTTP/2 allows a server to pre-emptively send (or "push") responses (along with - corresponding "promised" requests) to a client in association with a previous - client-initiated request. This can be useful when the server knows the client will need - to have those responses available in order to fully process the response to the original - request. - - - - Pushing additional message exchanges in this fashion is optional, and is negotiated - between individual endpoints. The SETTINGS_ENABLE_PUSH setting can be set - to 0 to indicate that server push is disabled. - - - Promised requests MUST be cacheable (see ), MUST be safe (see ) and MUST NOT include a request body. Clients that receive a - promised request that is not cacheable, unsafe or that includes a request body MUST - reset the stream with a stream error of type - PROTOCOL_ERROR. - - - Pushed responses that are cacheable (see ) can be stored by the client, if it implements a HTTP - cache. Pushed responses are considered successfully validated on the origin server (e.g., - if the "no-cache" cache response directive is present) while the stream identified by the - promised stream ID is still open. - - - Pushed responses that are not cacheable MUST NOT be stored by any HTTP cache. They MAY - be made available to the application separately. - - - An intermediary can receive pushes from the server and choose not to forward them on to - the client. In other words, how to make use of the pushed information is up to that - intermediary. Equally, the intermediary might choose to make additional pushes to the - client, without any action taken by the server. - - - A client cannot push. Thus, servers MUST treat the receipt of a - PUSH_PROMISE frame as a connection - error of type PROTOCOL_ERROR. Clients MUST reject any attempt to - change the SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating - the message as a connection error of type - PROTOCOL_ERROR. - - -
- - Server push is semantically equivalent to a server responding to a request; however, in - this case that request is also sent by the server, as a PUSH_PROMISE - frame. - - - The PUSH_PROMISE frame includes a header block that contains a complete - set of request header fields that the server attributes to the request. It is not - possible to push a response to a request that includes a request body. - - - - Pushed responses are always associated with an explicit request from the client. The - PUSH_PROMISE frames sent by the server are sent on that explicit - request's stream. The PUSH_PROMISE frame also includes a promised stream - identifier, chosen from the stream identifiers available to the server (see ). - - - - The header fields in PUSH_PROMISE and any subsequent - CONTINUATION frames MUST be a valid and complete set of request header fields. The server MUST include a method in - the :method header field that is safe and cacheable. If a - client receives a PUSH_PROMISE that does not include a complete and valid - set of header fields, or the :method header field identifies - a method that is not safe, it MUST respond with a stream error of type PROTOCOL_ERROR. - - - - The server SHOULD send PUSH_PROMISE () - frames prior to sending any frames that reference the promised responses. This avoids a - race where clients issue requests prior to receiving any PUSH_PROMISE - frames. - - - For example, if the server receives a request for a document containing embedded links - to multiple image files, and the server chooses to push those additional images to the - client, sending push promises before the DATA frames that contain the - image links ensures that the client is able to see the promises before discovering - embedded links. Similarly, if the server pushes responses referenced by the header block - (for instance, in Link header fields), sending the push promises before sending the - header block ensures that clients do not request them. - - - - PUSH_PROMISE frames MUST NOT be sent by the client. - - - PUSH_PROMISE frames can be sent by the server in response to any - client-initiated stream, but the stream MUST be in either the "open" or "half closed - (remote)" state with respect to the server. PUSH_PROMISE frames are - interspersed with the frames that comprise a response, though they cannot be - interspersed with HEADERS and CONTINUATION frames that - comprise a single header block. - - - Sending a PUSH_PROMISE frame creates a new stream and puts the stream - into the “reserved (local)” state for the server and the “reserved (remote)” state for - the client. - -
- -
- - After sending the PUSH_PROMISE frame, the server can begin delivering the - pushed response as a response on a server-initiated - stream that uses the promised stream identifier. The server uses this stream to - transmit an HTTP response, using the same sequence of frames as defined in . This stream becomes "half closed" - to the client after the initial HEADERS frame is sent. - - - - Once a client receives a PUSH_PROMISE frame and chooses to accept the - pushed response, the client SHOULD NOT issue any requests for the promised response - until after the promised stream has closed. - - - - If the client determines, for any reason, that it does not wish to receive the pushed - response from the server, or if the server takes too long to begin sending the promised - response, the client can send an RST_STREAM frame, using either the - CANCEL or REFUSED_STREAM codes, and referencing the pushed - stream's identifier. - - - A client can use the SETTINGS_MAX_CONCURRENT_STREAMS setting to limit the - number of responses that can be concurrently pushed by a server. Advertising a - SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables server push by - preventing the server from creating the necessary streams. This does not prohibit a - server from sending PUSH_PROMISE frames; clients need to reset any - promised streams that are not wanted. - - - - Clients receiving a pushed response MUST validate that either the server is - authoritative (see ), or the proxy that provided the pushed - response is configured for the corresponding request. For example, a server that offers - a certificate for only the example.com DNS-ID or Common Name - is not permitted to push a response for https://www.example.org/doc. - - - The response for a PUSH_PROMISE stream begins with a - HEADERS frame, which immediately puts the stream into the “half closed - (remote)” state for the server and “half closed (local)” state for the client, and ends - with a frame bearing END_STREAM, which places the stream in the "closed" state. - - - The client never sends a frame with the END_STREAM flag for a server push. - - - -
- -
- -
- - In HTTP/1.x, the pseudo-method CONNECT () is used to convert an HTTP connection into a tunnel to a remote host. - CONNECT is primarily used with HTTP proxies to establish a TLS session with an origin - server for the purposes of interacting with https resources. - - - In HTTP/2, the CONNECT method is used to establish a tunnel over a single HTTP/2 stream to - a remote host, for similar purposes. The HTTP header field mapping works as defined in - Request Header Fields, with a few - differences. Specifically: - - - The :method header field is set to CONNECT. - - - The :scheme and :path header - fields MUST be omitted. - - - The :authority header field contains the host and port to - connect to (equivalent to the authority-form of the request-target of CONNECT - requests, see ). - - - - - A proxy that supports CONNECT establishes a TCP connection to - the server identified in the :authority header field. Once - this connection is successfully established, the proxy sends a HEADERS - frame containing a 2xx series status code to the client, as defined in . - - - After the initial HEADERS frame sent by each peer, all subsequent - DATA frames correspond to data sent on the TCP connection. The payload of - any DATA frames sent by the client is transmitted by the proxy to the TCP - server; data received from the TCP server is assembled into DATA frames by - the proxy. Frame types other than DATA or stream management frames - (RST_STREAM, WINDOW_UPDATE, and PRIORITY) - MUST NOT be sent on a connected stream, and MUST be treated as a stream error if received. - - - The TCP connection can be closed by either peer. The END_STREAM flag on a - DATA frame is treated as being equivalent to the TCP FIN bit. A client is - expected to send a DATA frame with the END_STREAM flag set after receiving - a frame bearing the END_STREAM flag. A proxy that receives a DATA frame - with the END_STREAM flag set sends the attached data with the FIN bit set on the last TCP - segment. A proxy that receives a TCP segment with the FIN bit set sends a - DATA frame with the END_STREAM flag set. Note that the final TCP segment - or DATA frame could be empty. - - - A TCP connection error is signaled with RST_STREAM. A proxy treats any - error in the TCP connection, which includes receiving a TCP segment with the RST bit set, - as a stream error of type - CONNECT_ERROR. Correspondingly, a proxy MUST send a TCP segment with the - RST bit set if it detects an error with the stream or the HTTP/2 connection. - -
-
- -
- - This section outlines attributes of the HTTP protocol that improve interoperability, reduce - exposure to known security vulnerabilities, or reduce the potential for implementation - variation. - - -
- - HTTP/2 connections are persistent. For best performance, it is expected clients will not - close connections until it is determined that no further communication with a server is - necessary (for example, when a user navigates away from a particular web page), or until - the server closes the connection. - - - Clients SHOULD NOT open more than one HTTP/2 connection to a given host and port pair, - where host is derived from a URI, a selected alternative - service, or a configured proxy. - - - A client can create additional connections as replacements, either to replace connections - that are near to exhausting the available stream - identifier space, to refresh the keying material for a TLS connection, or to - replace connections that have encountered errors. - - - A client MAY open multiple connections to the same IP address and TCP port using different - Server Name Indication values or to provide different TLS - client certificates, but SHOULD avoid creating multiple connections with the same - configuration. - - - Servers are encouraged to maintain open connections for as long as possible, but are - permitted to terminate idle connections if necessary. When either endpoint chooses to - close the transport-layer TCP connection, the terminating endpoint SHOULD first send a - GOAWAY () frame so that both endpoints can reliably - determine whether previously sent frames have been processed and gracefully complete or - terminate any necessary remaining tasks. - - -
- - Connections that are made to an origin servers, either directly or through a tunnel - created using the CONNECT method MAY be reused for - requests with multiple different URI authority components. A connection can be reused - as long as the origin server is authoritative. For - http resources, this depends on the host having resolved to - the same IP address. - - - For https resources, connection reuse additionally depends - on having a certificate that is valid for the host in the URI. An origin server might - offer a certificate with multiple subjectAltName attributes, - or names with wildcards, one of which is valid for the authority in the URI. For - example, a certificate with a subjectAltName of *.example.com might permit the use of the same connection for - requests to URIs starting with https://a.example.com/ and - https://b.example.com/. - - - In some deployments, reusing a connection for multiple origins can result in requests - being directed to the wrong origin server. For example, TLS termination might be - performed by a middlebox that uses the TLS Server Name Indication - (SNI) extension to select an origin server. This means that it is possible - for clients to send confidential information to servers that might not be the intended - target for the request, even though the server is otherwise authoritative. - - - A server that does not wish clients to reuse connections can indicate that it is not - authoritative for a request by sending a 421 (Misdirected Request) status code in response - to the request (see ). - - - A client that is configured to use a proxy over HTTP/2 directs requests to that proxy - through a single connection. That is, all requests sent via a proxy reuse the - connection to the proxy. - -
- -
- - The 421 (Misdirected Request) status code indicates that the request was directed at a - server that is not able to produce a response. This can be sent by a server that is not - configured to produce responses for the combination of scheme and authority that are - included in the request URI. - - - Clients receiving a 421 (Misdirected Request) response from a server MAY retry the - request - whether the request method is idempotent or not - over a different connection. - This is possible if a connection is reused () or if an alternative - service is selected (). - - - This status code MUST NOT be generated by proxies. - - - A 421 response is cacheable by default; i.e., unless otherwise indicated by the method - definition or explicit cache controls (see ). - -
-
- -
- - Implementations of HTTP/2 MUST support TLS 1.2 for HTTP/2 over - TLS. The general TLS usage guidance in SHOULD be followed, with - some additional restrictions that are specific to HTTP/2. - - - - An implementation of HTTP/2 over TLS MUST use TLS 1.2 or higher with the restrictions on - feature set and cipher suite described in this section. Due to implementation - limitations, it might not be possible to fail TLS negotiation. An endpoint MUST - immediately terminate an HTTP/2 connection that does not meet these minimum requirements - with a connection error of type - INADEQUATE_SECURITY. - - -
- - The TLS implementation MUST support the Server Name Indication - (SNI) extension to TLS. HTTP/2 clients MUST indicate the target domain name when - negotiating TLS. - - - The TLS implementation MUST disable compression. TLS compression can lead to the - exposure of information that would not otherwise be revealed . - Generic compression is unnecessary since HTTP/2 provides compression features that are - more aware of context and therefore likely to be more appropriate for use for - performance, security or other reasons. - - - The TLS implementation MUST disable renegotiation. An endpoint MUST treat a TLS - renegotiation as a connection error of type - PROTOCOL_ERROR. Note that disabling renegotiation can result in - long-lived connections becoming unusable due to limits on the number of messages the - underlying cipher suite can encipher. - - - A client MAY use renegotiation to provide confidentiality protection for client - credentials offered in the handshake, but any renegotiation MUST occur prior to sending - the connection preface. A server SHOULD request a client certificate if it sees a - renegotiation request immediately after establishing a connection. - - - This effectively prevents the use of renegotiation in response to a request for a - specific protected resource. A future specification might provide a way to support this - use case. - -
- -
- - The set of TLS cipher suites that are permitted in HTTP/2 is restricted. HTTP/2 MUST - only be used with cipher suites that have ephemeral key exchange, such as the ephemeral Diffie-Hellman (DHE) or the elliptic curve variant (ECDHE). Ephemeral key exchange MUST - have a minimum size of 2048 bits for DHE or security level of 128 bits for ECDHE. - Clients MUST accept DHE sizes of up to 4096 bits. HTTP MUST NOT be used with cipher - suites that use stream or block ciphers. Authenticated Encryption with Additional Data - (AEAD) modes, such as the Galois Counter Model (GCM) mode for - AES are acceptable. - - - The effect of these restrictions is that TLS 1.2 implementations could have - non-intersecting sets of available cipher suites, since these prevent the use of the - cipher suite that TLS 1.2 makes mandatory. To avoid this problem, implementations of - HTTP/2 that use TLS 1.2 MUST support TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 with P256 . - - - Clients MAY advertise support of cipher suites that are prohibited by the above - restrictions in order to allow for connection to servers that do not support HTTP/2. - This enables a fallback to protocols without these constraints without the additional - latency imposed by using a separate connection for fallback. - -
-
-
- -
-
- - HTTP/2 relies on the HTTP/1.1 definition of authority for determining whether a server is - authoritative in providing a given response, see . This relies on local name resolution for the "http" - URI scheme, and the authenticated server identity for the "https" scheme (see ). - -
- -
- - In a cross-protocol attack, an attacker causes a client to initiate a transaction in one - protocol toward a server that understands a different protocol. An attacker might be able - to cause the transaction to appear as valid transaction in the second protocol. In - combination with the capabilities of the web context, this can be used to interact with - poorly protected servers in private networks. - - - Completing a TLS handshake with an ALPN identifier for HTTP/2 can be considered sufficient - protection against cross protocol attacks. ALPN provides a positive indication that a - server is willing to proceed with HTTP/2, which prevents attacks on other TLS-based - protocols. - - - The encryption in TLS makes it difficult for attackers to control the data which could be - used in a cross-protocol attack on a cleartext protocol. - - - The cleartext version of HTTP/2 has minimal protection against cross-protocol attacks. - The connection preface contains a string that is - designed to confuse HTTP/1.1 servers, but no special protection is offered for other - protocols. A server that is willing to ignore parts of an HTTP/1.1 request containing an - Upgrade header field in addition to the client connection preface could be exposed to a - cross-protocol attack. - -
- -
- - HTTP/2 header field names and values are encoded as sequences of octets with a length - prefix. This enables HTTP/2 to carry any string of octets as the name or value of a - header field. An intermediary that translates HTTP/2 requests or responses into HTTP/1.1 - directly could permit the creation of corrupted HTTP/1.1 messages. An attacker might - exploit this behavior to cause the intermediary to create HTTP/1.1 messages with illegal - header fields, extra header fields, or even new messages that are entirely falsified. - - - Header field names or values that contain characters not permitted by HTTP/1.1, including - carriage return (ASCII 0xd) or line feed (ASCII 0xa) MUST NOT be translated verbatim by an - intermediary, as stipulated in . - - - Translation from HTTP/1.x to HTTP/2 does not produce the same opportunity to an attacker. - Intermediaries that perform translation to HTTP/2 MUST remove any instances of the obs-fold production from header field values. - -
- -
- - Pushed responses do not have an explicit request from the client; the request - is provided by the server in the PUSH_PROMISE frame. - - - Caching responses that are pushed is possible based on the guidance provided by the origin - server in the Cache-Control header field. However, this can cause issues if a single - server hosts more than one tenant. For example, a server might offer multiple users each - a small portion of its URI space. - - - Where multiple tenants share space on the same server, that server MUST ensure that - tenants are not able to push representations of resources that they do not have authority - over. Failure to enforce this would allow a tenant to provide a representation that would - be served out of cache, overriding the actual representation that the authoritative tenant - provides. - - - Pushed responses for which an origin server is not authoritative (see - ) are never cached or used. - -
- -
- - An HTTP/2 connection can demand a greater commitment of resources to operate than a - HTTP/1.1 connection. The use of header compression and flow control depend on a - commitment of resources for storing a greater amount of state. Settings for these - features ensure that memory commitments for these features are strictly bounded. - - - The number of PUSH_PROMISE frames is not constrained in the same fashion. - A client that accepts server push SHOULD limit the number of streams it allows to be in - the "reserved (remote)" state. Excessive number of server push streams can be treated as - a stream error of type - ENHANCE_YOUR_CALM. - - - Processing capacity cannot be guarded as effectively as state capacity. - - - The SETTINGS frame can be abused to cause a peer to expend additional - processing time. This might be done by pointlessly changing SETTINGS parameters, setting - multiple undefined parameters, or changing the same setting multiple times in the same - frame. WINDOW_UPDATE or PRIORITY frames can be abused to - cause an unnecessary waste of resources. - - - Large numbers of small or empty frames can be abused to cause a peer to expend time - processing frame headers. Note however that some uses are entirely legitimate, such as - the sending of an empty DATA frame to end a stream. - - - Header compression also offers some opportunities to waste processing resources; see for more details on potential abuses. - - - Limits in SETTINGS parameters cannot be reduced instantaneously, which - leaves an endpoint exposed to behavior from a peer that could exceed the new limits. In - particular, immediately after establishing a connection, limits set by a server are not - known to clients and could be exceeded without being an obvious protocol violation. - - - All these features - i.e., SETTINGS changes, small frames, header - compression - have legitimate uses. These features become a burden only when they are - used unnecessarily or to excess. - - - An endpoint that doesn't monitor this behavior exposes itself to a risk of denial of - service attack. Implementations SHOULD track the use of these features and set limits on - their use. An endpoint MAY treat activity that is suspicious as a connection error of type - ENHANCE_YOUR_CALM. - - -
- - A large header block can cause an implementation to - commit a large amount of state. Header fields that are critical for routing can appear - toward the end of a header block, which prevents streaming of header fields to their - ultimate destination. For this an other reasons, such as ensuring cache correctness, - means that an endpoint might need to buffer the entire header block. Since there is no - hard limit to the size of a header block, some endpoints could be forced commit a large - amount of available memory for header fields. - - - An endpoint can use the SETTINGS_MAX_HEADER_LIST_SIZE to advise peers of - limits that might apply on the size of header blocks. This setting is only advisory, so - endpoints MAY choose to send header blocks that exceed this limit and risk having the - request or response being treated as malformed. This setting specific to a connection, - so any request or response could encounter a hop with a lower, unknown limit. An - intermediary can attempt to avoid this problem by passing on values presented by - different peers, but they are not obligated to do so. - - - A server that receives a larger header block than it is willing to handle can send an - HTTP 431 (Request Header Fields Too Large) status code . A - client can discard responses that it cannot process. The header block MUST be processed - to ensure a consistent connection state, unless the connection is closed. - -
-
- -
- - HTTP/2 enables greater use of compression for both header fields () and entity bodies. Compression can allow an attacker to recover - secret data when it is compressed in the same context as data under attacker control. - - - There are demonstrable attacks on compression that exploit the characteristics of the web - (e.g., ). The attacker induces multiple requests containing - varying plaintext, observing the length of the resulting ciphertext in each, which - reveals a shorter length when a guess about the secret is correct. - - - Implementations communicating on a secure channel MUST NOT compress content that includes - both confidential and attacker-controlled data unless separate compression dictionaries - are used for each source of data. Compression MUST NOT be used if the source of data - cannot be reliably determined. Generic stream compression, such as that provided by TLS - MUST NOT be used with HTTP/2 (). - - - Further considerations regarding the compression of header fields are described in . - -
- -
- - Padding within HTTP/2 is not intended as a replacement for general purpose padding, such - as might be provided by TLS. Redundant padding could even be - counterproductive. Correct application can depend on having specific knowledge of the - data that is being padded. - - - To mitigate attacks that rely on compression, disabling or limiting compression might be - preferable to padding as a countermeasure. - - - Padding can be used to obscure the exact size of frame content, and is provided to - mitigate specific attacks within HTTP. For example, attacks where compressed content - includes both attacker-controlled plaintext and secret data (see for example, ). - - - Use of padding can result in less protection than might seem immediately obvious. At - best, padding only makes it more difficult for an attacker to infer length information by - increasing the number of frames an attacker has to observe. Incorrectly implemented - padding schemes can be easily defeated. In particular, randomized padding with a - predictable distribution provides very little protection; similarly, padding payloads to a - fixed size exposes information as payload sizes cross the fixed size boundary, which could - be possible if an attacker can control plaintext. - - - Intermediaries SHOULD retain padding for DATA frames, but MAY drop padding - for HEADERS and PUSH_PROMISE frames. A valid reason for an - intermediary to change the amount of padding of frames is to improve the protections that - padding provides. - -
- -
- - Several characteristics of HTTP/2 provide an observer an opportunity to correlate actions - of a single client or server over time. This includes the value of settings, the manner - in which flow control windows are managed, the way priorities are allocated to streams, - timing of reactions to stimulus, and handling of any optional features. - - - As far as this creates observable differences in behavior, they could be used as a basis - for fingerprinting a specific client, as defined in . - -
-
- -
- - A string for identifying HTTP/2 is entered into the "Application Layer Protocol Negotiation - (ALPN) Protocol IDs" registry established in . - - - This document establishes a registry for frame types, settings, and error codes. These new - registries are entered into a new "Hypertext Transfer Protocol (HTTP) 2 Parameters" section. - - - This document registers the HTTP2-Settings header field for - use in HTTP; and the 421 (Misdirected Request) status code. - - - This document registers the PRI method for use in HTTP, to avoid - collisions with the connection preface. - - -
- - This document creates two registrations for the identification of HTTP/2 in the - "Application Layer Protocol Negotiation (ALPN) Protocol IDs" registry established in . - - - The "h2" string identifies HTTP/2 when used over TLS: - - HTTP/2 over TLS - 0x68 0x32 ("h2") - This document - - - - The "h2c" string identifies HTTP/2 when used over cleartext TCP: - - HTTP/2 over TCP - 0x68 0x32 0x63 ("h2c") - This document - - -
- -
- - This document establishes a registry for HTTP/2 frame type codes. The "HTTP/2 Frame - Type" registry manages an 8-bit space. The "HTTP/2 Frame Type" registry operates under - either of the "IETF Review" or "IESG Approval" policies for - values between 0x00 and 0xef, with values between 0xf0 and 0xff being reserved for - experimental use. - - - New entries in this registry require the following information: - - - A name or label for the frame type. - - - The 8-bit code assigned to the frame type. - - - A reference to a specification that includes a description of the frame layout, - it's semantics and flags that the frame type uses, including any parts of the frame - that are conditionally present based on the value of flags. - - - - - The entries in the following table are registered by this document. - - - Frame Type - Code - Section - DATA0x0 - HEADERS0x1 - PRIORITY0x2 - RST_STREAM0x3 - SETTINGS0x4 - PUSH_PROMISE0x5 - PING0x6 - GOAWAY0x7 - WINDOW_UPDATE0x8 - CONTINUATION0x9 - -
- -
- - This document establishes a registry for HTTP/2 settings. The "HTTP/2 Settings" registry - manages a 16-bit space. The "HTTP/2 Settings" registry operates under the "Expert Review" policy for values in the range from 0x0000 to - 0xefff, with values between and 0xf000 and 0xffff being reserved for experimental use. - - - New registrations are advised to provide the following information: - - - A symbolic name for the setting. Specifying a setting name is optional. - - - The 16-bit code assigned to the setting. - - - An initial value for the setting. - - - An optional reference to a specification that describes the use of the setting. - - - - - An initial set of setting registrations can be found in . - - - Name - Code - Initial Value - Specification - HEADER_TABLE_SIZE - 0x14096 - ENABLE_PUSH - 0x21 - MAX_CONCURRENT_STREAMS - 0x3(infinite) - INITIAL_WINDOW_SIZE - 0x465535 - MAX_FRAME_SIZE - 0x516384 - MAX_HEADER_LIST_SIZE - 0x6(infinite) - - -
- -
- - This document establishes a registry for HTTP/2 error codes. The "HTTP/2 Error Code" - registry manages a 32-bit space. The "HTTP/2 Error Code" registry operates under the - "Expert Review" policy. - - - Registrations for error codes are required to include a description of the error code. An - expert reviewer is advised to examine new registrations for possible duplication with - existing error codes. Use of existing registrations is to be encouraged, but not - mandated. - - - New registrations are advised to provide the following information: - - - A name for the error code. Specifying an error code name is optional. - - - The 32-bit error code value. - - - A brief description of the error code semantics, longer if no detailed specification - is provided. - - - An optional reference for a specification that defines the error code. - - - - - The entries in the following table are registered by this document. - - - Name - Code - Description - Specification - NO_ERROR0x0 - Graceful shutdown - - PROTOCOL_ERROR0x1 - Protocol error detected - - INTERNAL_ERROR0x2 - Implementation fault - - FLOW_CONTROL_ERROR0x3 - Flow control limits exceeded - - SETTINGS_TIMEOUT0x4 - Settings not acknowledged - - STREAM_CLOSED0x5 - Frame received for closed stream - - FRAME_SIZE_ERROR0x6 - Frame size incorrect - - REFUSED_STREAM0x7 - Stream not processed - - CANCEL0x8 - Stream cancelled - - COMPRESSION_ERROR0x9 - Compression state not updated - - CONNECT_ERROR0xa - TCP connection error for CONNECT method - - ENHANCE_YOUR_CALM0xb - Processing capacity exceeded - - INADEQUATE_SECURITY0xc - Negotiated TLS parameters not acceptable - - - -
- -
- - This section registers the HTTP2-Settings header field in the - Permanent Message Header Field Registry. - - - HTTP2-Settings - - - http - - - standard - - - IETF - - - of this document - - - This header field is only used by an HTTP/2 client for Upgrade-based negotiation. - - - -
- -
- - This section registers the PRI method in the HTTP Method - Registry (). - - - PRI - - - No - - - No - - - of this document - - - This method is never used by an actual client. This method will appear to be used - when an HTTP/1.1 server or intermediary attempts to parse an HTTP/2 connection - preface. - - - -
- -
- - This document registers the 421 (Misdirected Request) HTTP Status code in the Hypertext - Transfer Protocol (HTTP) Status Code Registry (). - - - - - 421 - - - Misdirected Request - - - of this document - - - -
- -
- -
- - This document includes substantial input from the following individuals: - - - Adam Langley, Wan-Teh Chang, Jim Morrison, Mark Nottingham, Alyssa Wilk, Costin - Manolache, William Chan, Vitaliy Lvin, Joe Chan, Adam Barth, Ryan Hamilton, Gavin - Peters, Kent Alstad, Kevin Lindsay, Paul Amer, Fan Yang, Jonathan Leighton (SPDY - contributors). - - - Gabriel Montenegro and Willy Tarreau (Upgrade mechanism). - - - William Chan, Salvatore Loreto, Osama Mazahir, Gabriel Montenegro, Jitu Padhye, Roberto - Peon, Rob Trace (Flow control). - - - Mike Bishop (Extensibility). - - - Mark Nottingham, Julian Reschke, James Snell, Jeff Pinner, Mike Bishop, Herve Ruellan - (Substantial editorial contributions). - - - Kari Hurtta, Tatsuhiro Tsujikawa, Greg Wilkins, Poul-Henning Kamp. - - - Alexey Melnikov was an editor of this document during 2013. - - - A substantial proportion of Martin's contribution was supported by Microsoft during his - employment there. - - - -
-
- - - - - - HPACK - Header Compression for HTTP/2 - - - - - - - - - - - - Transmission Control Protocol - - - University of Southern California (USC)/Information Sciences - Institute - - - - - - - - - - - Key words for use in RFCs to Indicate Requirement Levels - - - Harvard University -
sob@harvard.edu
-
- -
- - -
- - - - - HTTP Over TLS - - - - - - - - - - Uniform Resource Identifier (URI): Generic - Syntax - - - - - - - - - - - - The Base16, Base32, and Base64 Data Encodings - - - - - - - - - Guidelines for Writing an IANA Considerations Section in RFCs - - - - - - - - - - - Augmented BNF for Syntax Specifications: ABNF - - - - - - - - - - - The Transport Layer Security (TLS) Protocol Version 1.2 - - - - - - - - - - - Transport Layer Security (TLS) Extensions: Extension Definitions - - - - - - - - - - Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension - - - - - - - - - - - - - TLS Elliptic Curve Cipher Suites with SHA-256/384 and AES Galois - Counter Mode (GCM) - - - - - - - - - - - Digital Signature Standard (DSS) - - NIST - - - - - - - - - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing - - Adobe Systems Incorporated -
fielding@gbiv.com
-
- - greenbytes GmbH -
julian.reschke@greenbytes.de
-
- -
- - -
- - - - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content - - Adobe Systems Incorporated -
fielding@gbiv.com
-
- - greenbytes GmbH -
julian.reschke@greenbytes.de
-
- -
- - -
- - - Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests - - Adobe Systems Incorporated -
fielding@gbiv.com
-
- - greenbytes GmbH -
julian.reschke@greenbytes.de
-
- -
- -
- - - Hypertext Transfer Protocol (HTTP/1.1): Range Requests - - Adobe Systems Incorporated -
fielding@gbiv.com
-
- - World Wide Web Consortium -
ylafon@w3.org
-
- - greenbytes GmbH -
julian.reschke@greenbytes.de
-
- -
- -
- - - Hypertext Transfer Protocol (HTTP/1.1): Caching - - Adobe Systems Incorporated -
fielding@gbiv.com
-
- - Akamai -
mnot@mnot.net
-
- - greenbytes GmbH -
julian.reschke@greenbytes.de
-
- -
- - -
- - - Hypertext Transfer Protocol (HTTP/1.1): Authentication - - Adobe Systems Incorporated -
fielding@gbiv.com
-
- - greenbytes GmbH -
julian.reschke@greenbytes.de
-
- -
- - -
- - - - HTTP State Management Mechanism - - - - - -
- - - - - - TCP Extensions for High Performance - - - - - - - - - - - - Transport Layer Security Protocol Compression Methods - - - - - - - - - Additional HTTP Status Codes - - - - - - - - - - - Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS) - - - - - - - - - - - - - - - AES Galois Counter Mode (GCM) Cipher Suites for TLS - - - - - - - - - - - - HTML5 - - - - - - - - - - - Latest version available at - . - - - - - - - Talking to Yourself for Fun and Profit - - - - - - - - - - - - - - BREACH: Reviving the CRIME Attack - - - - - - - - - - - Registration Procedures for Message Header Fields - - Nine by Nine -
GK-IETF@ninebynine.org
-
- - BEA Systems -
mnot@pobox.com
-
- - HP Labs -
JeffMogul@acm.org
-
- -
- - -
- - - - Recommendations for Secure Use of TLS and DTLS - - - - - - - - - - - - - - - - - - HTTP Alternative Services - - - Akamai - - - Mozilla - - - greenbytes - - - - - - -
- -
- - This section is to be removed by RFC Editor before publication. - - -
- - Renamed Not Authoritative status code to Misdirected Request. - -
- -
- - Pseudo-header fields are now required to appear strictly before regular ones. - - - Restored 1xx series status codes, except 101. - - - Changed frame length field 24-bits. Expanded frame header to 9 octets. Added a setting - to limit the damage. - - - Added a setting to advise peers of header set size limits. - - - Removed segments. - - - Made non-semantic-bearing HEADERS frames illegal in the HTTP mapping. - -
- -
- - Restored extensibility options. - - - Restricting TLS cipher suites to AEAD only. - - - Removing Content-Encoding requirements. - - - Permitting the use of PRIORITY after stream close. - - - Removed ALTSVC frame. - - - Removed BLOCKED frame. - - - Reducing the maximum padding size to 256 octets; removing padding from - CONTINUATION frames. - - - Removed per-frame GZIP compression. - -
- -
- - Added BLOCKED frame (at risk). - - - Simplified priority scheme. - - - Added DATA per-frame GZIP compression. - -
- -
- - Changed "connection header" to "connection preface" to avoid confusion. - - - Added dependency-based stream prioritization. - - - Added "h2c" identifier to distinguish between cleartext and secured HTTP/2. - - - Adding missing padding to PUSH_PROMISE. - - - Integrate ALTSVC frame and supporting text. - - - Dropping requirement on "deflate" Content-Encoding. - - - Improving security considerations around use of compression. - -
- -
- - Adding padding for data frames. - - - Renumbering frame types, error codes, and settings. - - - Adding INADEQUATE_SECURITY error code. - - - Updating TLS usage requirements to 1.2; forbidding TLS compression. - - - Removing extensibility for frames and settings. - - - Changing setting identifier size. - - - Removing the ability to disable flow control. - - - Changing the protocol identification token to "h2". - - - Changing the use of :authority to make it optional and to allow userinfo in non-HTTP - cases. - - - Allowing split on 0x0 for Cookie. - - - Reserved PRI method in HTTP/1.1 to avoid possible future collisions. - -
- -
- - Added cookie crumbling for more efficient header compression. - - - Added header field ordering with the value-concatenation mechanism. - -
- -
- - Marked draft for implementation. - -
- -
- - Adding definition for CONNECT method. - - - Constraining the use of push to safe, cacheable methods with no request body. - - - Changing from :host to :authority to remove any potential confusion. - - - Adding setting for header compression table size. - - - Adding settings acknowledgement. - - - Removing unnecessary and potentially problematic flags from CONTINUATION. - - - Added denial of service considerations. - -
-
- - Marking the draft ready for implementation. - - - Renumbering END_PUSH_PROMISE flag. - - - Editorial clarifications and changes. - -
- -
- - Added CONTINUATION frame for HEADERS and PUSH_PROMISE. - - - PUSH_PROMISE is no longer implicitly prohibited if SETTINGS_MAX_CONCURRENT_STREAMS is - zero. - - - Push expanded to allow all safe methods without a request body. - - - Clarified the use of HTTP header fields in requests and responses. Prohibited HTTP/1.1 - hop-by-hop header fields. - - - Requiring that intermediaries not forward requests with missing or illegal routing - :-headers. - - - Clarified requirements around handling different frames after stream close, stream reset - and GOAWAY. - - - Added more specific prohibitions for sending of different frame types in various stream - states. - - - Making the last received setting value the effective value. - - - Clarified requirements on TLS version, extension and ciphers. - -
- -
- - Committed major restructuring atrocities. - - - Added reference to first header compression draft. - - - Added more formal description of frame lifecycle. - - - Moved END_STREAM (renamed from FINAL) back to HEADERS/DATA. - - - Removed HEADERS+PRIORITY, added optional priority to HEADERS frame. - - - Added PRIORITY frame. - -
- -
- - Added continuations to frames carrying header blocks. - - - Replaced use of "session" with "connection" to avoid confusion with other HTTP stateful - concepts, like cookies. - - - Removed "message". - - - Switched to TLS ALPN from NPN. - - - Editorial changes. - -
- -
- - Added IANA considerations section for frame types, error codes and settings. - - - Removed data frame compression. - - - Added PUSH_PROMISE. - - - Added globally applicable flags to framing. - - - Removed zlib-based header compression mechanism. - - - Updated references. - - - Clarified stream identifier reuse. - - - Removed CREDENTIALS frame and associated mechanisms. - - - Added advice against naive implementation of flow control. - - - Added session header section. - - - Restructured frame header. Removed distinction between data and control frames. - - - Altered flow control properties to include session-level limits. - - - Added note on cacheability of pushed resources and multiple tenant servers. - - - Changed protocol label form based on discussions. - -
- -
- - Changed title throughout. - - - Removed section on Incompatibilities with SPDY draft#2. - - - Changed INTERNAL_ERROR on GOAWAY to have a value of 2 . - - - Replaced abstract and introduction. - - - Added section on starting HTTP/2.0, including upgrade mechanism. - - - Removed unused references. - - - Added flow control principles based on . - -
- -
- - Adopted as base for draft-ietf-httpbis-http2. - - - Updated authors/editors list. - - - Added status note. - -
-
- -
-
- diff --git a/http2/testsync.go b/http2/testsync.go deleted file mode 100644 index 61075bd16d..0000000000 --- a/http2/testsync.go +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -package http2 - -import ( - "context" - "sync" - "time" -) - -// testSyncHooks coordinates goroutines in tests. -// -// For example, a call to ClientConn.RoundTrip involves several goroutines, including: -// - the goroutine running RoundTrip; -// - the clientStream.doRequest goroutine, which writes the request; and -// - the clientStream.readLoop goroutine, which reads the response. -// -// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines -// are blocked waiting for some condition such as reading the Request.Body or waiting for -// flow control to become available. -// -// The testSyncHooks also manage timers and synthetic time in tests. -// This permits us to, for example, start a request and cause it to time out waiting for -// response headers without resorting to time.Sleep calls. -type testSyncHooks struct { - // active/inactive act as a mutex and condition variable. - // - // - neither chan contains a value: testSyncHooks is locked. - // - active contains a value: unlocked, and at least one goroutine is not blocked - // - inactive contains a value: unlocked, and all goroutines are blocked - active chan struct{} - inactive chan struct{} - - // goroutine counts - total int // total goroutines - condwait map[*sync.Cond]int // blocked in sync.Cond.Wait - blocked []*testBlockedGoroutine // otherwise blocked - - // fake time - now time.Time - timers []*fakeTimer - - // Transport testing: Report various events. - newclientconn func(*ClientConn) - newstream func(*clientStream) -} - -// testBlockedGoroutine is a blocked goroutine. -type testBlockedGoroutine struct { - f func() bool // blocked until f returns true - ch chan struct{} // closed when unblocked -} - -func newTestSyncHooks() *testSyncHooks { - h := &testSyncHooks{ - active: make(chan struct{}, 1), - inactive: make(chan struct{}, 1), - condwait: map[*sync.Cond]int{}, - } - h.inactive <- struct{}{} - h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) - return h -} - -// lock acquires the testSyncHooks mutex. -func (h *testSyncHooks) lock() { - select { - case <-h.active: - case <-h.inactive: - } -} - -// waitInactive waits for all goroutines to become inactive. -func (h *testSyncHooks) waitInactive() { - for { - <-h.inactive - if !h.unlock() { - break - } - } -} - -// unlock releases the testSyncHooks mutex. -// It reports whether any goroutines are active. -func (h *testSyncHooks) unlock() (active bool) { - // Look for a blocked goroutine which can be unblocked. - blocked := h.blocked[:0] - unblocked := false - for _, b := range h.blocked { - if !unblocked && b.f() { - unblocked = true - close(b.ch) - } else { - blocked = append(blocked, b) - } - } - h.blocked = blocked - - // Count goroutines blocked on condition variables. - condwait := 0 - for _, count := range h.condwait { - condwait += count - } - - if h.total > condwait+len(blocked) { - h.active <- struct{}{} - return true - } else { - h.inactive <- struct{}{} - return false - } -} - -// goRun starts a new goroutine. -func (h *testSyncHooks) goRun(f func()) { - h.lock() - h.total++ - h.unlock() - go func() { - defer func() { - h.lock() - h.total-- - h.unlock() - }() - f() - }() -} - -// blockUntil indicates that a goroutine is blocked waiting for some condition to become true. -// It waits until f returns true before proceeding. -// -// Example usage: -// -// h.blockUntil(func() bool { -// // Is the context done yet? -// select { -// case <-ctx.Done(): -// default: -// return false -// } -// return true -// }) -// // Wait for the context to become done. -// <-ctx.Done() -// -// The function f passed to blockUntil must be non-blocking and idempotent. -func (h *testSyncHooks) blockUntil(f func() bool) { - if f() { - return - } - ch := make(chan struct{}) - h.lock() - h.blocked = append(h.blocked, &testBlockedGoroutine{ - f: f, - ch: ch, - }) - h.unlock() - <-ch -} - -// broadcast is sync.Cond.Broadcast. -func (h *testSyncHooks) condBroadcast(cond *sync.Cond) { - h.lock() - delete(h.condwait, cond) - h.unlock() - cond.Broadcast() -} - -// broadcast is sync.Cond.Wait. -func (h *testSyncHooks) condWait(cond *sync.Cond) { - h.lock() - h.condwait[cond]++ - h.unlock() -} - -// newTimer creates a new fake timer. -func (h *testSyncHooks) newTimer(d time.Duration) timer { - h.lock() - defer h.unlock() - t := &fakeTimer{ - hooks: h, - when: h.now.Add(d), - c: make(chan time.Time), - } - h.timers = append(h.timers, t) - return t -} - -// afterFunc creates a new fake AfterFunc timer. -func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer { - h.lock() - defer h.unlock() - t := &fakeTimer{ - hooks: h, - when: h.now.Add(d), - f: f, - } - h.timers = append(h.timers, t) - return t -} - -func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(ctx) - t := h.afterFunc(d, cancel) - return ctx, func() { - t.Stop() - cancel() - } -} - -func (h *testSyncHooks) timeUntilEvent() time.Duration { - h.lock() - defer h.unlock() - var next time.Time - for _, t := range h.timers { - if next.IsZero() || t.when.Before(next) { - next = t.when - } - } - if d := next.Sub(h.now); d > 0 { - return d - } - return 0 -} - -// advance advances time and causes synthetic timers to fire. -func (h *testSyncHooks) advance(d time.Duration) { - h.lock() - defer h.unlock() - h.now = h.now.Add(d) - timers := h.timers[:0] - for _, t := range h.timers { - t := t // remove after go.mod depends on go1.22 - t.mu.Lock() - switch { - case t.when.After(h.now): - timers = append(timers, t) - case t.when.IsZero(): - // stopped timer - default: - t.when = time.Time{} - if t.c != nil { - close(t.c) - } - if t.f != nil { - h.total++ - go func() { - defer func() { - h.lock() - h.total-- - h.unlock() - }() - t.f() - }() - } - } - t.mu.Unlock() - } - h.timers = timers -} - -// A timer wraps a time.Timer, or a synthetic equivalent in tests. -// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires. -type timer interface { - C() <-chan time.Time - Stop() bool - Reset(d time.Duration) bool -} - -// timeTimer implements timer using real time. -type timeTimer struct { - t *time.Timer - c chan time.Time -} - -// newTimeTimer creates a new timer using real time. -func newTimeTimer(d time.Duration) timer { - ch := make(chan time.Time) - t := time.AfterFunc(d, func() { - close(ch) - }) - return &timeTimer{t, ch} -} - -// newTimeAfterFunc creates an AfterFunc timer using real time. -func newTimeAfterFunc(d time.Duration, f func()) timer { - return &timeTimer{ - t: time.AfterFunc(d, f), - } -} - -func (t timeTimer) C() <-chan time.Time { return t.c } -func (t timeTimer) Stop() bool { return t.t.Stop() } -func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) } - -// fakeTimer implements timer using fake time. -type fakeTimer struct { - hooks *testSyncHooks - - mu sync.Mutex - when time.Time // when the timer will fire - c chan time.Time // closed when the timer fires; mutually exclusive with f - f func() // called when the timer fires; mutually exclusive with c -} - -func (t *fakeTimer) C() <-chan time.Time { return t.c } - -func (t *fakeTimer) Stop() bool { - t.mu.Lock() - defer t.mu.Unlock() - stopped := t.when.IsZero() - t.when = time.Time{} - return stopped -} - -func (t *fakeTimer) Reset(d time.Duration) bool { - if t.c != nil || t.f == nil { - panic("fakeTimer only supports Reset on AfterFunc timers") - } - t.mu.Lock() - defer t.mu.Unlock() - t.hooks.lock() - defer t.hooks.unlock() - active := !t.when.IsZero() - t.when = t.hooks.now.Add(d) - if !active { - t.hooks.timers = append(t.hooks.timers, t) - } - return active -} diff --git a/http2/timer.go b/http2/timer.go new file mode 100644 index 0000000000..0b1c17b812 --- /dev/null +++ b/http2/timer.go @@ -0,0 +1,20 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package http2 + +import "time" + +// A timer is a time.Timer, as an interface which can be replaced in tests. +type timer = interface { + C() <-chan time.Time + Reset(d time.Duration) bool + Stop() bool +} + +// timeTimer adapts a time.Timer to the timer interface. +type timeTimer struct { + *time.Timer +} + +func (t timeTimer) C() <-chan time.Time { return t.Timer.C } diff --git a/http2/transport.go b/http2/transport.go index 2fa49490c9..98a49c6b6e 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -185,7 +185,45 @@ type Transport struct { connPoolOnce sync.Once connPoolOrDef ClientConnPool // non-nil version of ConnPool - syncHooks *testSyncHooks + *transportTestHooks +} + +// Hook points used for testing. +// Outside of tests, t.transportTestHooks is nil and these all have minimal implementations. +// Inside tests, see the testSyncHooks function docs. + +type transportTestHooks struct { + newclientconn func(*ClientConn) + group synctestGroupInterface +} + +func (t *Transport) markNewGoroutine() { + if t != nil && t.transportTestHooks != nil { + t.transportTestHooks.group.Join() + } +} + +// newTimer creates a new time.Timer, or a synthetic timer in tests. +func (t *Transport) newTimer(d time.Duration) timer { + if t.transportTestHooks != nil { + return t.transportTestHooks.group.NewTimer(d) + } + return timeTimer{time.NewTimer(d)} +} + +// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests. +func (t *Transport) afterFunc(d time.Duration, f func()) timer { + if t.transportTestHooks != nil { + return t.transportTestHooks.group.AfterFunc(d, f) + } + return timeTimer{time.AfterFunc(d, f)} +} + +func (t *Transport) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) { + if t.transportTestHooks != nil { + return t.transportTestHooks.group.ContextWithTimeout(ctx, d) + } + return context.WithTimeout(ctx, d) } func (t *Transport) maxHeaderListSize() uint32 { @@ -352,60 +390,6 @@ type ClientConn struct { werr error // first write error that has occurred hbuf bytes.Buffer // HPACK encoder writes into this henc *hpack.Encoder - - syncHooks *testSyncHooks // can be nil -} - -// Hook points used for testing. -// Outside of tests, cc.syncHooks is nil and these all have minimal implementations. -// Inside tests, see the testSyncHooks function docs. - -// goRun starts a new goroutine. -func (cc *ClientConn) goRun(f func()) { - if cc.syncHooks != nil { - cc.syncHooks.goRun(f) - return - } - go f() -} - -// condBroadcast is cc.cond.Broadcast. -func (cc *ClientConn) condBroadcast() { - if cc.syncHooks != nil { - cc.syncHooks.condBroadcast(cc.cond) - } - cc.cond.Broadcast() -} - -// condWait is cc.cond.Wait. -func (cc *ClientConn) condWait() { - if cc.syncHooks != nil { - cc.syncHooks.condWait(cc.cond) - } - cc.cond.Wait() -} - -// newTimer creates a new time.Timer, or a synthetic timer in tests. -func (cc *ClientConn) newTimer(d time.Duration) timer { - if cc.syncHooks != nil { - return cc.syncHooks.newTimer(d) - } - return newTimeTimer(d) -} - -// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests. -func (cc *ClientConn) afterFunc(d time.Duration, f func()) timer { - if cc.syncHooks != nil { - return cc.syncHooks.afterFunc(d, f) - } - return newTimeAfterFunc(d, f) -} - -func (cc *ClientConn) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) { - if cc.syncHooks != nil { - return cc.syncHooks.contextWithTimeout(ctx, d) - } - return context.WithTimeout(ctx, d) } // clientStream is the state for a single HTTP/2 stream. One of these @@ -487,7 +471,7 @@ func (cs *clientStream) abortStreamLocked(err error) { // TODO(dneil): Clean up tests where cs.cc.cond is nil. if cs.cc.cond != nil { // Wake up writeRequestBody if it is waiting on flow control. - cs.cc.condBroadcast() + cs.cc.cond.Broadcast() } } @@ -497,7 +481,7 @@ func (cs *clientStream) abortRequestBodyWrite() { defer cc.mu.Unlock() if cs.reqBody != nil && cs.reqBodyClosed == nil { cs.closeReqBodyLocked() - cc.condBroadcast() + cc.cond.Broadcast() } } @@ -507,10 +491,11 @@ func (cs *clientStream) closeReqBodyLocked() { } cs.reqBodyClosed = make(chan struct{}) reqBodyClosed := cs.reqBodyClosed - cs.cc.goRun(func() { + go func() { + cs.cc.t.markNewGoroutine() cs.reqBody.Close() close(reqBodyClosed) - }) + }() } type stickyErrWriter struct { @@ -626,21 +611,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res backoff := float64(uint(1) << (uint(retry) - 1)) backoff += backoff * (0.1 * mathrand.Float64()) d := time.Second * time.Duration(backoff) - var tm timer - if t.syncHooks != nil { - tm = t.syncHooks.newTimer(d) - t.syncHooks.blockUntil(func() bool { - select { - case <-tm.C(): - case <-req.Context().Done(): - default: - return false - } - return true - }) - } else { - tm = newTimeTimer(d) - } + tm := t.newTimer(d) select { case <-tm.C(): t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) @@ -725,8 +696,8 @@ func canRetryError(err error) bool { } func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) { - if t.syncHooks != nil { - return t.newClientConn(nil, singleUse, t.syncHooks) + if t.transportTestHooks != nil { + return t.newClientConn(nil, singleUse) } host, _, err := net.SplitHostPort(addr) if err != nil { @@ -736,7 +707,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b if err != nil { return nil, err } - return t.newClientConn(tconn, singleUse, nil) + return t.newClientConn(tconn, singleUse) } func (t *Transport) newTLSConfig(host string) *tls.Config { @@ -802,10 +773,10 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 { } func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { - return t.newClientConn(c, t.disableKeepAlives(), nil) + return t.newClientConn(c, t.disableKeepAlives()) } -func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) { +func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) { cc := &ClientConn{ t: t, tconn: c, @@ -820,16 +791,12 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHoo wantSettingsAck: true, pings: make(map[[8]byte]chan struct{}), reqHeaderMu: make(chan struct{}, 1), - syncHooks: hooks, } - if hooks != nil { - hooks.newclientconn(cc) + if t.transportTestHooks != nil { + t.markNewGoroutine() + t.transportTestHooks.newclientconn(cc) c = cc.tconn } - if d := t.idleConnTimeout(); d != 0 { - cc.idleTimeout = d - cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout) - } if VerboseLogs { t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr()) } @@ -893,7 +860,13 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHoo return nil, cc.werr } - cc.goRun(cc.readLoop) + // Start the idle timer after the connection is fully initialized. + if d := t.idleConnTimeout(); d != 0 { + cc.idleTimeout = d + cc.idleTimer = t.afterFunc(d, cc.onIdleTimeout) + } + + go cc.readLoop() return cc, nil } @@ -901,7 +874,7 @@ func (cc *ClientConn) healthCheck() { pingTimeout := cc.t.pingTimeout() // We don't need to periodically ping in the health check, because the readLoop of ClientConn will // trigger the healthCheck again if there is no frame received. - ctx, cancel := cc.contextWithTimeout(context.Background(), pingTimeout) + ctx, cancel := cc.t.contextWithTimeout(context.Background(), pingTimeout) defer cancel() cc.vlogf("http2: Transport sending health check") err := cc.Ping(ctx) @@ -1144,7 +1117,8 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error { // Wait for all in-flight streams to complete or connection to close done := make(chan struct{}) cancelled := false // guarded by cc.mu - cc.goRun(func() { + go func() { + cc.t.markNewGoroutine() cc.mu.Lock() defer cc.mu.Unlock() for { @@ -1156,9 +1130,9 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error { if cancelled { break } - cc.condWait() + cc.cond.Wait() } - }) + }() shutdownEnterWaitStateHook() select { case <-done: @@ -1168,7 +1142,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error { cc.mu.Lock() // Free the goroutine above cancelled = true - cc.condBroadcast() + cc.cond.Broadcast() cc.mu.Unlock() return ctx.Err() } @@ -1206,7 +1180,7 @@ func (cc *ClientConn) closeForError(err error) { for _, cs := range cc.streams { cs.abortStreamLocked(err) } - cc.condBroadcast() + cc.cond.Broadcast() cc.mu.Unlock() cc.closeConn() } @@ -1321,23 +1295,30 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) respHeaderRecv: make(chan struct{}), donec: make(chan struct{}), } - cc.goRun(func() { - cs.doRequest(req) - }) + + // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? + if !cc.t.disableCompression() && + req.Header.Get("Accept-Encoding") == "" && + req.Header.Get("Range") == "" && + !cs.isHead { + // Request gzip only, not deflate. Deflate is ambiguous and + // not as universally supported anyway. + // See: https://zlib.net/zlib_faq.html#faq39 + // + // Note that we don't request this for HEAD requests, + // due to a bug in nginx: + // http://trac.nginx.org/nginx/ticket/358 + // https://golang.org/issue/5522 + // + // We don't request gzip if the request is for a range, since + // auto-decoding a portion of a gzipped document will just fail + // anyway. See https://golang.org/issue/8923 + cs.requestedGzip = true + } + + go cs.doRequest(req, streamf) waitDone := func() error { - if cc.syncHooks != nil { - cc.syncHooks.blockUntil(func() bool { - select { - case <-cs.donec: - case <-ctx.Done(): - case <-cs.reqCancel: - default: - return false - } - return true - }) - } select { case <-cs.donec: return nil @@ -1398,24 +1379,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) return err } - if streamf != nil { - streamf(cs) - } - for { - if cc.syncHooks != nil { - cc.syncHooks.blockUntil(func() bool { - select { - case <-cs.respHeaderRecv: - case <-cs.abort: - case <-ctx.Done(): - case <-cs.reqCancel: - default: - return false - } - return true - }) - } select { case <-cs.respHeaderRecv: return handleResponseHeaders() @@ -1445,8 +1409,9 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) // doRequest runs for the duration of the request lifetime. // // It sends the request and performs post-request cleanup (closing Request.Body, etc.). -func (cs *clientStream) doRequest(req *http.Request) { - err := cs.writeRequest(req) +func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream)) { + cs.cc.t.markNewGoroutine() + err := cs.writeRequest(req, streamf) cs.cleanupWriteRequest(err) } @@ -1457,7 +1422,7 @@ func (cs *clientStream) doRequest(req *http.Request) { // // It returns non-nil if the request ends otherwise. // If the returned error is StreamError, the error Code may be used in resetting the stream. -func (cs *clientStream) writeRequest(req *http.Request) (err error) { +func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStream)) (err error) { cc := cs.cc ctx := cs.ctx @@ -1471,21 +1436,6 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) { if cc.reqHeaderMu == nil { panic("RoundTrip on uninitialized ClientConn") // for tests } - var newStreamHook func(*clientStream) - if cc.syncHooks != nil { - newStreamHook = cc.syncHooks.newstream - cc.syncHooks.blockUntil(func() bool { - select { - case cc.reqHeaderMu <- struct{}{}: - <-cc.reqHeaderMu - case <-cs.reqCancel: - case <-ctx.Done(): - default: - return false - } - return true - }) - } select { case cc.reqHeaderMu <- struct{}{}: case <-cs.reqCancel: @@ -1510,28 +1460,8 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) { } cc.mu.Unlock() - if newStreamHook != nil { - newStreamHook(cs) - } - - // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? - if !cc.t.disableCompression() && - req.Header.Get("Accept-Encoding") == "" && - req.Header.Get("Range") == "" && - !cs.isHead { - // Request gzip only, not deflate. Deflate is ambiguous and - // not as universally supported anyway. - // See: https://zlib.net/zlib_faq.html#faq39 - // - // Note that we don't request this for HEAD requests, - // due to a bug in nginx: - // http://trac.nginx.org/nginx/ticket/358 - // https://golang.org/issue/5522 - // - // We don't request gzip if the request is for a range, since - // auto-decoding a portion of a gzipped document will just fail - // anyway. See https://golang.org/issue/8923 - cs.requestedGzip = true + if streamf != nil { + streamf(cs) } continueTimeout := cc.t.expectContinueTimeout() @@ -1594,7 +1524,7 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) { var respHeaderTimer <-chan time.Time var respHeaderRecv chan struct{} if d := cc.responseHeaderTimeout(); d != 0 { - timer := cc.newTimer(d) + timer := cc.t.newTimer(d) defer timer.Stop() respHeaderTimer = timer.C() respHeaderRecv = cs.respHeaderRecv @@ -1603,21 +1533,6 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) { // or until the request is aborted (via context, error, or otherwise), // whichever comes first. for { - if cc.syncHooks != nil { - cc.syncHooks.blockUntil(func() bool { - select { - case <-cs.peerClosed: - case <-respHeaderTimer: - case <-respHeaderRecv: - case <-cs.abort: - case <-ctx.Done(): - case <-cs.reqCancel: - default: - return false - } - return true - }) - } select { case <-cs.peerClosed: return nil @@ -1766,7 +1681,7 @@ func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error { return nil } cc.pendingRequests++ - cc.condWait() + cc.cond.Wait() cc.pendingRequests-- select { case <-cs.abort: @@ -2028,7 +1943,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) cs.flow.take(take) return take, nil } - cc.condWait() + cc.cond.Wait() } } @@ -2311,7 +2226,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) { } // Wake up writeRequestBody via clientStream.awaitFlowControl and // wake up RoundTrip if there is a pending request. - cc.condBroadcast() + cc.cond.Broadcast() closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 { @@ -2333,6 +2248,7 @@ type clientConnReadLoop struct { // readLoop runs in its own goroutine and reads and dispatches frames. func (cc *ClientConn) readLoop() { + cc.t.markNewGoroutine() rl := &clientConnReadLoop{cc: cc} defer rl.cleanup() cc.readerErr = rl.run() @@ -2399,7 +2315,7 @@ func (rl *clientConnReadLoop) cleanup() { cs.abortStreamLocked(err) } } - cc.condBroadcast() + cc.cond.Broadcast() cc.mu.Unlock() } @@ -2436,7 +2352,7 @@ func (rl *clientConnReadLoop) run() error { readIdleTimeout := cc.t.ReadIdleTimeout var t timer if readIdleTimeout != 0 { - t = cc.afterFunc(readIdleTimeout, cc.healthCheck) + t = cc.t.afterFunc(readIdleTimeout, cc.healthCheck) } for { f, err := cc.fr.ReadFrame() @@ -3034,7 +2950,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { for _, cs := range cc.streams { cs.flow.add(delta) } - cc.condBroadcast() + cc.cond.Broadcast() cc.initialWindowSize = s.Val case SettingHeaderTableSize: @@ -3089,7 +3005,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error { return ConnectionError(ErrCodeFlowControl) } - cc.condBroadcast() + cc.cond.Broadcast() return nil } @@ -3133,7 +3049,8 @@ func (cc *ClientConn) Ping(ctx context.Context) error { } var pingError error errc := make(chan struct{}) - cc.goRun(func() { + go func() { + cc.t.markNewGoroutine() cc.wmu.Lock() defer cc.wmu.Unlock() if pingError = cc.fr.WritePing(false, p); pingError != nil { @@ -3144,20 +3061,7 @@ func (cc *ClientConn) Ping(ctx context.Context) error { close(errc) return } - }) - if cc.syncHooks != nil { - cc.syncHooks.blockUntil(func() bool { - select { - case <-c: - case <-errc: - case <-ctx.Done(): - case <-cc.readerDone: - default: - return false - } - return true - }) - } + }() select { case <-c: return nil diff --git a/http2/transport_test.go b/http2/transport_test.go index 3e4297f28e..ddeaf6137f 100644 --- a/http2/transport_test.go +++ b/http2/transport_test.go @@ -16,7 +16,6 @@ import ( "fmt" "io" "io/fs" - "io/ioutil" "log" "math/rand" "net" @@ -152,7 +151,7 @@ func TestIdleConnTimeout(t *testing.T) { } // Respond to the client's request. - hf := testClientConnReadFrame[*MetaHeadersFrame](tc) + hf := readFrame[*HeadersFrame](t, tc) tc.writeHeaders(HeadersFrameParam{ StreamID: hf.StreamID, EndHeaders: true, @@ -169,7 +168,7 @@ func TestIdleConnTimeout(t *testing.T) { } tt.advance(test.wait) - if got, want := tc.netConnClosed, test.wantNewConn; got != want { + if got, want := tc.isClosed(), test.wantNewConn; got != want { t.Fatalf("after waiting %v, conn closed=%v; want %v", test.wait, got, want) } } @@ -206,7 +205,7 @@ func TestTransportH2c(t *testing.T) { if res.ProtoMajor != 2 { t.Fatal("proto not h2c") } - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } @@ -220,15 +219,14 @@ func TestTransportH2c(t *testing.T) { func TestTransport(t *testing.T) { const body = "sup" - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, body) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - u, err := url.Parse(st.ts.URL) + u, err := url.Parse(ts.URL) if err != nil { t.Fatal(err) } @@ -264,7 +262,7 @@ func TestTransport(t *testing.T) { if res.TLS == nil { t.Errorf("%d: Response.TLS = nil; want non-nil", i) } - slurp, err := ioutil.ReadAll(res.Body) + slurp, err := io.ReadAll(res.Body) if err != nil { t.Errorf("%d: Body read: %v", i, err) } else if string(slurp) != body { @@ -275,26 +273,27 @@ func TestTransport(t *testing.T) { } func testTransportReusesConns(t *testing.T, useClient, wantSame bool, modReq func(*http.Request)) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, r.RemoteAddr) - }, optOnlyServer, func(c net.Conn, st http.ConnState) { - t.Logf("conn %v is now state %v", c.RemoteAddr(), st) + }, func(ts *httptest.Server) { + ts.Config.ConnState = func(c net.Conn, st http.ConnState) { + t.Logf("conn %v is now state %v", c.RemoteAddr(), st) + } }) - defer st.Close() tr := &Transport{TLSClientConfig: tlsConfigInsecure} if useClient { tr.ConnPool = noDialClientConnPool{new(clientConnPool)} } defer tr.CloseIdleConnections() get := func() string { - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Fatal(err) } modReq(req) var res *http.Response if useClient { - c := st.ts.Client() + c := ts.Client() ConfigureTransports(c.Transport.(*http.Transport)) res, err = c.Do(req) } else { @@ -304,7 +303,7 @@ func testTransportReusesConns(t *testing.T, useClient, wantSame bool, modReq fun t.Fatal(err) } defer res.Body.Close() - slurp, err := ioutil.ReadAll(res.Body) + slurp, err := io.ReadAll(res.Body) if err != nil { t.Fatalf("Body read: %v", err) } @@ -358,15 +357,12 @@ func TestTransportGetGotConnHooks_HTTP2Transport(t *testing.T) { func TestTransportGetGotConnHooks_Client(t *testing.T) { testTransportGetGotConnHooks(t, true) } func testTransportGetGotConnHooks(t *testing.T, useClient bool) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, r.RemoteAddr) - }, func(s *httptest.Server) { - s.EnableHTTP2 = true - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} - client := st.ts.Client() + client := ts.Client() ConfigureTransports(client.Transport.(*http.Transport)) var ( @@ -389,7 +385,7 @@ func testTransportGetGotConnHooks(t *testing.T, useClient bool) { } }, } - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Fatal(err) } @@ -432,9 +428,8 @@ func (c *testNetConn) Close() error { // Tests that the Transport only keeps one pending dial open per destination address. // https://golang.org/issue/13397 func TestTransportGroupsPendingDials(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { - }, optOnlyServer) - defer st.Close() + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + }) var ( mu sync.Mutex dialCount int @@ -463,7 +458,7 @@ func TestTransportGroupsPendingDials(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Error(err) return @@ -486,35 +481,21 @@ func TestTransportGroupsPendingDials(t *testing.T) { } } -func retry(tries int, delay time.Duration, fn func() error) error { - var err error - for i := 0; i < tries; i++ { - err = fn() - if err == nil { - return nil - } - time.Sleep(delay) - } - return err -} - func TestTransportAbortClosesPipes(t *testing.T) { shutdown := make(chan struct{}) - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() <-shutdown }, - optOnlyServer, ) - defer st.Close() defer close(shutdown) // we must shutdown before st.Close() to avoid hanging errCh := make(chan error) go func() { defer close(errCh) tr := &Transport{TLSClientConfig: tlsConfigInsecure} - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { errCh <- err return @@ -525,8 +506,8 @@ func TestTransportAbortClosesPipes(t *testing.T) { return } defer res.Body.Close() - st.closeConn() - _, err = ioutil.ReadAll(res.Body) + ts.CloseClientConnections() + _, err = io.ReadAll(res.Body) if err == nil { errCh <- errors.New("expected error from res.Body.Read") return @@ -548,13 +529,11 @@ func TestTransportAbortClosesPipes(t *testing.T) { // could be a table-driven test with extra goodies. func TestTransportPath(t *testing.T) { gotc := make(chan *url.URL, 1) - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { gotc <- r.URL }, - optOnlyServer, ) - defer st.Close() tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() @@ -562,7 +541,7 @@ func TestTransportPath(t *testing.T) { path = "/testpath" query = "q=1" ) - surl := st.ts.URL + path + "?" + query + surl := ts.URL + path + "?" + query req, err := http.NewRequest("POST", surl, nil) if err != nil { t.Fatal(err) @@ -656,18 +635,16 @@ func TestTransportBody(t *testing.T) { err error } gotc := make(chan reqInfo, 1) - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { - slurp, err := ioutil.ReadAll(r.Body) + slurp, err := io.ReadAll(r.Body) if err != nil { gotc <- reqInfo{err: err} } else { gotc <- reqInfo{req: r, slurp: slurp} } }, - optOnlyServer, ) - defer st.Close() for i, tt := range bodyTests { tr := &Transport{TLSClientConfig: tlsConfigInsecure} @@ -677,7 +654,7 @@ func TestTransportBody(t *testing.T) { if tt.noContentLen { body = struct{ io.Reader }{body} // just a Reader, hiding concrete type and other methods } - req, err := http.NewRequest("POST", st.ts.URL, body) + req, err := http.NewRequest("POST", ts.URL, body) if err != nil { t.Fatalf("#%d: %v", i, err) } @@ -717,15 +694,13 @@ func TestTransportDialTLS(t *testing.T) { var mu sync.Mutex // guards following var gotReq, didDial bool - ts := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { mu.Lock() gotReq = true mu.Unlock() }, - optOnlyServer, ) - defer ts.Close() tr := &Transport{ DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { mu.Lock() @@ -741,7 +716,7 @@ func TestTransportDialTLS(t *testing.T) { } defer tr.CloseIdleConnections() client := &http.Client{Transport: tr} - res, err := client.Get(ts.ts.URL) + res, err := client.Get(ts.URL) if err != nil { t.Fatal(err) } @@ -776,18 +751,17 @@ func TestConfigureTransport(t *testing.T) { } // And does it work? - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, r.Proto) - }, optOnlyServer) - defer st.Close() + }) t1.TLSClientConfig.InsecureSkipVerify = true c := &http.Client{Transport: t1} - res, err := c.Get(st.ts.URL) + res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } - slurp, err := ioutil.ReadAll(res.Body) + slurp, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } @@ -838,7 +812,7 @@ func TestTransportReqBodyAfterResponse_200(t *testing.T) { testTransportReqBodyA func TestTransportReqBodyAfterResponse_403(t *testing.T) { testTransportReqBodyAfterResponse(t, 403) } func testTransportReqBodyAfterResponse(t *testing.T, status int) { - const bodySize = 10 << 20 + const bodySize = 1 << 10 tc := newTestClientConn(t) tc.greet() @@ -891,6 +865,7 @@ func testTransportReqBodyAfterResponse(t *testing.T, status int) { streamID: rt.streamID(), endStream: true, size: bodySize / 2, + multiple: true, }) } else { // After a 403 response, client gives up and resets the stream. @@ -902,20 +877,19 @@ func testTransportReqBodyAfterResponse(t *testing.T, status int) { // See golang.org/issue/13444 func TestTransportFullDuplex(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) // redundant but for clarity w.(http.Flusher).Flush() io.Copy(flushWriter{w}, capitalizeReader{r.Body}) fmt.Fprintf(w, "bye.\n") - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} pr, pw := io.Pipe() - req, err := http.NewRequest("PUT", st.ts.URL, ioutil.NopCloser(pr)) + req, err := http.NewRequest("PUT", ts.URL, io.NopCloser(pr)) if err != nil { t.Fatal(err) } @@ -953,12 +927,11 @@ func TestTransportFullDuplex(t *testing.T) { func TestTransportConnectRequest(t *testing.T) { gotc := make(chan *http.Request, 1) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { gotc <- r - }, optOnlyServer) - defer st.Close() + }) - u, err := url.Parse(st.ts.URL) + u, err := url.Parse(ts.URL) if err != nil { t.Fatal(err) } @@ -1413,24 +1386,22 @@ func TestPadHeaders(t *testing.T) { } func TestTransportChecksRequestHeaderListSize(t *testing.T) { - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { // Consume body & force client to send // trailers before writing response. - // ioutil.ReadAll returns non-nil err for + // io.ReadAll returns non-nil err for // requests that attempt to send greater than // maxHeaderListSize bytes of trailers, since // those requests generate a stream reset. - ioutil.ReadAll(r.Body) + io.ReadAll(r.Body) r.Body.Close() }, func(ts *httptest.Server) { ts.Config.MaxHeaderBytes = 16 << 10 }, - optOnlyServer, optQuiet, ) - defer st.Close() tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() @@ -1438,7 +1409,7 @@ func TestTransportChecksRequestHeaderListSize(t *testing.T) { checkRoundTrip := func(req *http.Request, wantErr error, desc string) { // Make an arbitrary request to ensure we get the server's // settings frame and initialize peerMaxHeaderListSize. - req0, err := http.NewRequest("GET", st.ts.URL, nil) + req0, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Fatalf("newRequest: NewRequest: %v", err) } @@ -1501,13 +1472,29 @@ func TestTransportChecksRequestHeaderListSize(t *testing.T) { newRequest := func() *http.Request { // Body must be non-nil to enable writing trailers. body := strings.NewReader("hello") - req, err := http.NewRequest("POST", st.ts.URL, body) + req, err := http.NewRequest("POST", ts.URL, body) if err != nil { t.Fatalf("newRequest: NewRequest: %v", err) } return req } + var ( + scMu sync.Mutex + sc *serverConn + ) + testHookGetServerConn = func(v *serverConn) { + scMu.Lock() + defer scMu.Unlock() + if sc != nil { + panic("testHookGetServerConn called multiple times") + } + sc = v + } + defer func() { + testHookGetServerConn = nil + }() + // Validate peerMaxHeaderListSize. req := newRequest() checkRoundTrip(req, nil, "Initial request") @@ -1519,16 +1506,16 @@ func TestTransportChecksRequestHeaderListSize(t *testing.T) { cc.mu.Lock() peerSize := cc.peerMaxHeaderListSize cc.mu.Unlock() - st.scMu.Lock() - wantSize := uint64(st.sc.maxHeaderListSize()) - st.scMu.Unlock() + scMu.Lock() + wantSize := uint64(sc.maxHeaderListSize()) + scMu.Unlock() if peerSize != wantSize { t.Errorf("peerMaxHeaderListSize = %v; want %v", peerSize, wantSize) } // Sanity check peerSize. (*serverConn) maxHeaderListSize adds // 320 bytes of padding. - wantHeaderBytes := uint64(st.ts.Config.MaxHeaderBytes) + 320 + wantHeaderBytes := uint64(ts.Config.MaxHeaderBytes) + 320 if peerSize != wantHeaderBytes { t.Errorf("peerMaxHeaderListSize = %v; want %v.", peerSize, wantHeaderBytes) } @@ -1653,22 +1640,20 @@ func TestTransportCookieHeaderSplit(t *testing.T) { // a stream error, but others like cancel should be similar) func TestTransportBodyReadErrorType(t *testing.T) { doPanic := make(chan bool, 1) - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() // force headers out <-doPanic panic("boom") }, - optOnlyServer, optQuiet, ) - defer st.Close() tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} - res, err := c.Get(st.ts.URL) + res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } @@ -1692,7 +1677,7 @@ func TestTransportDoubleCloseOnWriteError(t *testing.T) { conn net.Conn // to close if set ) - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() @@ -1700,9 +1685,7 @@ func TestTransportDoubleCloseOnWriteError(t *testing.T) { conn.Close() } }, - optOnlyServer, ) - defer st.Close() tr := &Transport{ TLSClientConfig: tlsConfigInsecure, @@ -1719,20 +1702,18 @@ func TestTransportDoubleCloseOnWriteError(t *testing.T) { } defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} - c.Get(st.ts.URL) + c.Get(ts.URL) } // Test that the http1 Transport.DisableKeepAlives option is respected // and connections are closed as soon as idle. // See golang.org/issue/14008 func TestTransportDisableKeepAlives(t *testing.T) { - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hi") }, - optOnlyServer, ) - defer st.Close() connClosed := make(chan struct{}) // closed on tls.Conn.Close tr := &Transport{ @@ -1749,11 +1730,11 @@ func TestTransportDisableKeepAlives(t *testing.T) { }, } c := &http.Client{Transport: tr} - res, err := c.Get(st.ts.URL) + res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } - if _, err := ioutil.ReadAll(res.Body); err != nil { + if _, err := io.ReadAll(res.Body); err != nil { t.Fatal(err) } defer res.Body.Close() @@ -1770,14 +1751,12 @@ func TestTransportDisableKeepAlives(t *testing.T) { // but when things are totally idle, it still needs to close. func TestTransportDisableKeepAlives_Concurrency(t *testing.T) { const D = 25 * time.Millisecond - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { time.Sleep(D) io.WriteString(w, "hi") }, - optOnlyServer, ) - defer st.Close() var dials int32 var conns sync.WaitGroup @@ -1812,12 +1791,12 @@ func TestTransportDisableKeepAlives_Concurrency(t *testing.T) { } go func() { defer reqs.Done() - res, err := c.Get(st.ts.URL) + res, err := c.Get(ts.URL) if err != nil { t.Error(err) return } - if _, err := ioutil.ReadAll(res.Body); err != nil { + if _, err := io.ReadAll(res.Body); err != nil { t.Error(err) return } @@ -1892,6 +1871,7 @@ func testTransportResponseHeaderTimeout(t *testing.T, body bool) { tc.wantData(wantData{ endStream: true, size: bodySize, + multiple: true, }) } @@ -1908,15 +1888,14 @@ func testTransportResponseHeaderTimeout(t *testing.T, body bool) { func TestTransportDisableCompression(t *testing.T) { const body = "sup" - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { want := http.Header{ "User-Agent": []string{"Go-http-client/2.0"}, } if !reflect.DeepEqual(r.Header, want) { t.Errorf("request headers = %v; want %v", r.Header, want) } - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{ TLSClientConfig: tlsConfigInsecure, @@ -1926,7 +1905,7 @@ func TestTransportDisableCompression(t *testing.T) { } defer tr.CloseIdleConnections() - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Fatal(err) } @@ -1939,15 +1918,14 @@ func TestTransportDisableCompression(t *testing.T) { // RFC 7540 section 8.1.2.2 func TestTransportRejectsConnHeaders(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { var got []string for k := range r.Header { got = append(got, k) } sort.Strings(got) w.Header().Set("Got-Header", strings.Join(got, ",")) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() @@ -2035,7 +2013,7 @@ func TestTransportRejectsConnHeaders(t *testing.T) { } for _, tt := range tests { - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) req.Header[tt.key] = tt.value res, err := tr.RoundTrip(req) var got string @@ -2089,14 +2067,13 @@ func TestTransportRejectsContentLengthWithSign(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", tt.cl[0]) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, _ := http.NewRequest("HEAD", st.ts.URL, nil) + req, _ := http.NewRequest("HEAD", ts.URL, nil) res, err := tr.RoundTrip(req) var got string @@ -2117,15 +2094,14 @@ func TestTransportRejectsContentLengthWithSign(t *testing.T) { // golang.org/issue/14048 // golang.org/issue/64766 func TestTransportFailsOnInvalidHeadersAndTrailers(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { var got []string for k := range r.Header { got = append(got, k) } sort.Strings(got) w.Header().Set("Got-Header", strings.Join(got, ",")) - }, optOnlyServer) - defer st.Close() + }) tests := [...]struct { h http.Header @@ -2162,7 +2138,7 @@ func TestTransportFailsOnInvalidHeadersAndTrailers(t *testing.T) { defer tr.CloseIdleConnections() for i, tt := range tests { - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) req.Header = tt.h req.Trailer = tt.t res, err := tr.RoundTrip(req) @@ -2191,7 +2167,7 @@ func TestTransportFailsOnInvalidHeadersAndTrailers(t *testing.T) { // the first Read call's gzip.NewReader returning an error. func TestGzipReader_DoubleReadCrash(t *testing.T) { gz := &gzipReader{ - body: ioutil.NopCloser(strings.NewReader("0123456789")), + body: io.NopCloser(strings.NewReader("0123456789")), } var buf [1]byte n, err1 := gz.Read(buf[:]) @@ -2210,7 +2186,7 @@ func TestGzipReader_ReadAfterClose(t *testing.T) { w.Write([]byte("012345679")) w.Close() gz := &gzipReader{ - body: ioutil.NopCloser(&body), + body: io.NopCloser(&body), } var buf [1]byte n, err := gz.Read(buf[:]) @@ -2372,11 +2348,10 @@ func (b neverEnding) Read(p []byte) (int, error) { // runs out of flow control tokens) func TestTransportHandlerBodyClose(t *testing.T) { const bodySize = 10 << 20 - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { r.Body.Close() io.Copy(w, io.LimitReader(neverEnding('A'), bodySize)) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() @@ -2385,7 +2360,7 @@ func TestTransportHandlerBodyClose(t *testing.T) { const numReq = 10 for i := 0; i < numReq; i++ { - req, err := http.NewRequest("POST", st.ts.URL, struct{ io.Reader }{io.LimitReader(neverEnding('A'), bodySize)}) + req, err := http.NewRequest("POST", ts.URL, struct{ io.Reader }{io.LimitReader(neverEnding('A'), bodySize)}) if err != nil { t.Fatal(err) } @@ -2393,7 +2368,7 @@ func TestTransportHandlerBodyClose(t *testing.T) { if err != nil { t.Fatal(err) } - n, err := io.Copy(ioutil.Discard, res.Body) + n, err := io.Copy(io.Discard, res.Body) res.Body.Close() if n != bodySize || err != nil { t.Fatalf("req#%d: Copy = %d, %v; want %d, nil", i, n, err, bodySize) @@ -2418,7 +2393,7 @@ func TestTransportFlowControl(t *testing.T) { } var wrote int64 // updated atomically - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { b := make([]byte, bufLen) for wrote < total { n, err := w.Write(b) @@ -2429,11 +2404,11 @@ func TestTransportFlowControl(t *testing.T) { } w.(http.Flusher).Flush() } - }, optOnlyServer) + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Fatal("NewRequest error:", err) } @@ -2506,7 +2481,7 @@ func testTransportUsesGoAwayDebugError(t *testing.T, failMidBody bool) { // the interesting parts of both. tc.writeGoAway(5, ErrCodeNo, []byte(goAwayDebugData)) tc.writeGoAway(5, goAwayErrCode, nil) - tc.closeWrite(io.EOF) + tc.closeWrite() res, err := rt.result() whence := "RoundTrip" @@ -2631,7 +2606,7 @@ func TestTransportAdjustsFlowControl(t *testing.T) { gotBytes := int64(0) for { - f := testClientConnReadFrame[*DataFrame](tc) + f := readFrame[*DataFrame](t, tc) gotBytes += int64(len(f.Data())) // After we've got half the client's initial flow control window's worth // of request body data, give it just enough flow control to finish. @@ -2727,7 +2702,7 @@ func TestTransportReturnsErrorOnBadResponseHeaders(t *testing.T) { t.Fatalf("RoundTrip error = %#v; want %#v", err, want) } - fr := testClientConnReadFrame[*RSTStreamFrame](tc) + fr := readFrame[*RSTStreamFrame](t, tc) if fr.StreamID != 1 || fr.ErrCode != ErrCodeProtocol { t.Errorf("Frame = %v; want RST_STREAM for stream 1 with ErrCodeProtocol", summarizeFrame(fr)) } @@ -2755,16 +2730,15 @@ func (b byteAndEOFReader) Read(p []byte) (n int, err error) { // which returns (non-0, io.EOF) and also needs to set the ContentLength // explicitly. func TestTransportBodyDoubleEndStream(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { // Nothing. - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() for i := 0; i < 2; i++ { - req, _ := http.NewRequest("POST", st.ts.URL, byteAndEOFReader('a')) + req, _ := http.NewRequest("POST", ts.URL, byteAndEOFReader('a')) req.ContentLength = 1 res, err := tr.RoundTrip(req) if err != nil { @@ -2907,16 +2881,17 @@ func TestTransportRequestPathPseudo(t *testing.T) { // before we've determined that the ClientConn is usable. func TestRoundTripDoesntConsumeRequestBodyEarly(t *testing.T) { const body = "foo" - req, _ := http.NewRequest("POST", "http://foo.com/", ioutil.NopCloser(strings.NewReader(body))) + req, _ := http.NewRequest("POST", "http://foo.com/", io.NopCloser(strings.NewReader(body))) cc := &ClientConn{ closed: true, reqHeaderMu: make(chan struct{}, 1), + t: &Transport{}, } _, err := cc.RoundTrip(req) if err != errClientConnUnusable { t.Fatalf("RoundTrip = %v; want errClientConnUnusable", err) } - slurp, err := ioutil.ReadAll(req.Body) + slurp, err := io.ReadAll(req.Body) if err != nil { t.Errorf("ReadAll = %v", err) } @@ -2926,12 +2901,11 @@ func TestRoundTripDoesntConsumeRequestBodyEarly(t *testing.T) { } func TestClientConnPing(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, optOnlyServer) - defer st.Close() + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() ctx := context.Background() - cc, err := tr.dialClientConn(ctx, st.ts.Listener.Addr().String(), false) + cc, err := tr.dialClientConn(ctx, ts.Listener.Addr().String(), false) if err != nil { t.Fatal(err) } @@ -2949,7 +2923,7 @@ func TestTransportCancelDataResponseRace(t *testing.T) { clientGotResponse := make(chan bool, 1) const msg = "Hello." - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.Path, "/hello") { time.Sleep(50 * time.Millisecond) io.WriteString(w, msg) @@ -2964,29 +2938,28 @@ func TestTransportCancelDataResponseRace(t *testing.T) { } time.Sleep(10 * time.Millisecond) } - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) req.Cancel = cancel res, err := c.Do(req) clientGotResponse <- true if err != nil { t.Fatal(err) } - if _, err = io.Copy(ioutil.Discard, res.Body); err == nil { + if _, err = io.Copy(io.Discard, res.Body); err == nil { t.Fatal("unexpected success") } - res, err = c.Get(st.ts.URL + "/hello") + res, err = c.Get(ts.URL + "/hello") if err != nil { t.Fatal(err) } - slurp, err := ioutil.ReadAll(res.Body) + slurp, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } @@ -2998,21 +2971,20 @@ func TestTransportCancelDataResponseRace(t *testing.T) { // Issue 21316: It should be safe to reuse an http.Request after the // request has completed. func TestTransportNoRaceOnRequestObjectAfterRequestComplete(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) io.WriteString(w, "body") - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) resp, err := tr.RoundTrip(req) if err != nil { t.Fatal(err) } - if _, err = io.Copy(ioutil.Discard, resp.Body); err != nil { + if _, err = io.Copy(io.Discard, resp.Body); err != nil { t.Fatalf("error reading response body: %v", err) } if err := resp.Body.Close(); err != nil { @@ -3045,11 +3017,9 @@ func TestTransportCloseAfterLostPing(t *testing.T) { } func TestTransportPingWriteBlocks(t *testing.T) { - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}, - optOnlyServer, ) - defer st.Close() tr := &Transport{ TLSClientConfig: tlsConfigInsecure, DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { @@ -3068,7 +3038,7 @@ func TestTransportPingWriteBlocks(t *testing.T) { } defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} - _, err := c.Get(st.ts.URL) + _, err := c.Get(ts.URL) if err == nil { t.Fatalf("Get = nil, want error") } @@ -3103,7 +3073,7 @@ func TestTransportPingWhenReadingMultiplePings(t *testing.T) { // ...ping now. tc.advance(1 * time.Millisecond) - f := testClientConnReadFrame[*PingFrame](tc) + f := readFrame[*PingFrame](t, tc) tc.writePing(true, f.Data) } @@ -3339,8 +3309,8 @@ func TestTransportRetryHasLimit(t *testing.T) { } tc.writeRSTStream(streamID, ErrCodeRefusedStream) - d := tt.tr.syncHooks.timeUntilEvent() - if d == 0 { + d, scheduled := tt.group.TimeUntilEvent() + if !scheduled { if streamID == 1 { continue } @@ -3402,26 +3372,27 @@ func TestTransportMaxFrameReadSize(t *testing.T) { maxReadFrameSize: 1024, want: minMaxFrameSize, }} { - tc := newTestClientConn(t, func(tr *Transport) { - tr.MaxReadFrameSize = test.maxReadFrameSize - }) + t.Run(fmt.Sprint(test.maxReadFrameSize), func(t *testing.T) { + tc := newTestClientConn(t, func(tr *Transport) { + tr.MaxReadFrameSize = test.maxReadFrameSize + }) - fr := testClientConnReadFrame[*SettingsFrame](tc) - got, ok := fr.Value(SettingMaxFrameSize) - if !ok { - t.Errorf("Transport.MaxReadFrameSize = %v; server got no setting, want %v", test.maxReadFrameSize, test.want) - } else if got != test.want { - t.Errorf("Transport.MaxReadFrameSize = %v; server got %v, want %v", test.maxReadFrameSize, got, test.want) - } + fr := readFrame[*SettingsFrame](t, tc) + got, ok := fr.Value(SettingMaxFrameSize) + if !ok { + t.Errorf("Transport.MaxReadFrameSize = %v; server got no setting, want %v", test.maxReadFrameSize, test.want) + } else if got != test.want { + t.Errorf("Transport.MaxReadFrameSize = %v; server got %v, want %v", test.maxReadFrameSize, got, test.want) + } + }) } } func TestTransportRequestsLowServerLimit(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { - }, optOnlyServer, func(s *Server) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + }, func(s *Server) { s.MaxConcurrentStreams = 1 }) - defer st.Close() var ( connCountMu sync.Mutex @@ -3440,7 +3411,7 @@ func TestTransportRequestsLowServerLimit(t *testing.T) { const reqCount = 3 for i := 0; i < reqCount; i++ { - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Fatal(err) } @@ -3549,7 +3520,7 @@ func TestTransportMaxDecoderHeaderTableSize(t *testing.T) { tr.MaxDecoderHeaderTableSize = reqSize }) - fr := testClientConnReadFrame[*SettingsFrame](tc) + fr := readFrame[*SettingsFrame](t, tc) if v, ok := fr.Value(SettingHeaderTableSize); !ok { t.Fatalf("missing SETTINGS_HEADER_TABLE_SIZE setting") } else if v != reqSize { @@ -3557,6 +3528,8 @@ func TestTransportMaxDecoderHeaderTableSize(t *testing.T) { } tc.writeSettings(Setting{SettingHeaderTableSize, resSize}) + tc.cc.mu.Lock() + defer tc.cc.mu.Unlock() if got, want := tc.cc.peerMaxHeaderTableSize, resSize; got != want { t.Fatalf("peerHeaderTableSize = %d, want %d", got, want) } @@ -3605,7 +3578,7 @@ func TestTransportAllocationsAfterResponseBodyClose(t *testing.T) { writeErr := make(chan error, 1) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() var sum int64 for i := 0; i < 100; i++ { @@ -3618,13 +3591,12 @@ func TestTransportAllocationsAfterResponseBodyClose(t *testing.T) { } t.Logf("wrote all %d bytes", sum) writeErr <- nil - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} - res, err := c.Get(st.ts.URL) + res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } @@ -3676,24 +3648,22 @@ func TestTransportNoBodyMeansNoDATA(t *testing.T) { } func benchSimpleRoundTrip(b *testing.B, nReqHeaders, nResHeader int) { - defer disableGoroutineTracking()() + disableGoroutineTracking(b) b.ReportAllocs() - st := newServerTester(b, + ts := newTestServer(b, func(w http.ResponseWriter, r *http.Request) { for i := 0; i < nResHeader; i++ { name := fmt.Sprint("A-", i) w.Header().Set(name, "*") } }, - optOnlyServer, optQuiet, ) - defer st.Close() tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { b.Fatal(err) } @@ -3729,16 +3699,15 @@ func (r infiniteReader) Read(b []byte) (int, error) { // Issue 20521: it is not an error to receive a response and end stream // from the server without the body being consumed. func TestTransportResponseAndResetWithoutConsumingBodyRace(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() // The request body needs to be big enough to trigger flow control. - req, _ := http.NewRequest("PUT", st.ts.URL, infiniteReader{}) + req, _ := http.NewRequest("PUT", ts.URL, infiniteReader{}) res, err := tr.RoundTrip(req) if err != nil { t.Fatal(err) @@ -3791,10 +3760,10 @@ func BenchmarkDownloadFrameSize(b *testing.B) { b.Run("512k Frame", func(b *testing.B) { benchLargeDownloadRoundTrip(b, 512*1024) }) } func benchLargeDownloadRoundTrip(b *testing.B, frameSize uint32) { - defer disableGoroutineTracking()() + disableGoroutineTracking(b) const transferSize = 1024 * 1024 * 1024 // must be multiple of 1M b.ReportAllocs() - st := newServerTester(b, + ts := newTestServer(b, func(w http.ResponseWriter, r *http.Request) { // test 1GB transfer w.Header().Set("Content-Length", strconv.Itoa(transferSize)) @@ -3805,12 +3774,11 @@ func benchLargeDownloadRoundTrip(b *testing.B, frameSize uint32) { } }, optQuiet, ) - defer st.Close() tr := &Transport{TLSClientConfig: tlsConfigInsecure, MaxReadFrameSize: frameSize} defer tr.CloseIdleConnections() - req, err := http.NewRequest("GET", st.ts.URL, nil) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { b.Fatal(err) } @@ -3869,7 +3837,7 @@ func testClientConnClose(t *testing.T, closeMode closeMode) { closeDone := make(chan struct{}) beforeHeader := func() {} bodyWrite := func(w http.ResponseWriter) {} - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { defer close(handlerDone) beforeHeader() w.WriteHeader(http.StatusOK) @@ -3886,13 +3854,12 @@ func testClientConnClose(t *testing.T, closeMode closeMode) { t.Error("expected connection closed by client") } } - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() ctx := context.Background() - cc, err := tr.dialClientConn(ctx, st.ts.Listener.Addr().String(), false) - req, err := http.NewRequest("GET", st.ts.URL, nil) + cc, err := tr.dialClientConn(ctx, ts.Listener.Addr().String(), false) + req, err := http.NewRequest("GET", ts.URL, nil) if err != nil { t.Fatal(err) } @@ -3992,7 +3959,7 @@ func testClientConnClose(t *testing.T, closeMode closeMode) { case closeAtHeaders, closeAtBody: if closeMode == closeAtBody { go close(sendBody) - if _, err := io.Copy(ioutil.Discard, res.Body); err == nil { + if _, err := io.Copy(io.Discard, res.Body); err == nil { t.Error("expected a Copy error, got nil") } } @@ -4043,7 +4010,7 @@ func TestClientConnShutdownCancel(t *testing.T) { func TestTransportUsesGetBodyWhenPresent(t *testing.T) { calls := 0 someBody := func() io.ReadCloser { - return struct{ io.ReadCloser }{ioutil.NopCloser(bytes.NewReader(nil))} + return struct{ io.ReadCloser }{io.NopCloser(bytes.NewReader(nil))} } req := &http.Request{ Body: someBody(), @@ -4170,7 +4137,7 @@ func TestTransportBodyEagerEndStream(t *testing.T) { tc.roundTrip(req) tc.wantFrameType(FrameHeaders) - f := testClientConnReadFrame[*DataFrame](tc) + f := readFrame[*DataFrame](t, tc) if !f.StreamEnded() { t.Fatalf("data frame without END_STREAM %v", f) } @@ -4213,15 +4180,14 @@ func TestTransportBodyLargerThanSpecifiedContentLength_len2(t *testing.T) { } func testTransportBodyLargerThanSpecifiedContentLength(t *testing.T, body *chunkReader, contentLen int64) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { r.Body.Read(make([]byte, 6)) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() - req, _ := http.NewRequest("POST", st.ts.URL, body) + req, _ := http.NewRequest("POST", ts.URL, body) req.ContentLength = contentLen _, err := tr.RoundTrip(req) if err != errReqBodyTooLong { @@ -4301,13 +4267,12 @@ func TestTransportRoundtripCloseOnWriteError(t *testing.T) { if err != nil { t.Fatal(err) } - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, optOnlyServer) - defer st.Close() + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() ctx := context.Background() - cc, err := tr.dialClientConn(ctx, st.ts.Listener.Addr().String(), false) + cc, err := tr.dialClientConn(ctx, ts.Listener.Addr().String(), false) if err != nil { t.Fatal(err) } @@ -4334,12 +4299,11 @@ func TestTransportRoundtripCloseOnWriteError(t *testing.T) { // already. If the request body has started to be sent, one must wait until it // is completed. func TestTransportBodyRewindRace(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Connection", "close") w.WriteHeader(http.StatusOK) return - }, optOnlyServer) - defer st.Close() + }) tr := &http.Transport{ TLSClientConfig: tlsConfigInsecure, @@ -4358,7 +4322,7 @@ func TestTransportBodyRewindRace(t *testing.T) { var wg sync.WaitGroup wg.Add(clients) for i := 0; i < clients; i++ { - req, err := http.NewRequest("POST", st.ts.URL, bytes.NewBufferString("abcdef")) + req, err := http.NewRequest("POST", ts.URL, bytes.NewBufferString("abcdef")) if err != nil { t.Fatalf("unexpect new request error: %v", err) } @@ -4378,11 +4342,10 @@ func TestTransportBodyRewindRace(t *testing.T) { // Issue 42498: A request with a body will never be sent if the stream is // reset prior to sending any data. func TestTransportServerResetStreamAtHeaders(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) return - }, optOnlyServer) - defer st.Close() + }) tr := &http.Transport{ TLSClientConfig: tlsConfigInsecure, @@ -4398,7 +4361,7 @@ func TestTransportServerResetStreamAtHeaders(t *testing.T) { Transport: tr, } - req, err := http.NewRequest("POST", st.ts.URL, errorReader{io.EOF}) + req, err := http.NewRequest("POST", ts.URL, errorReader{io.EOF}) if err != nil { t.Fatalf("unexpect new request error: %v", err) } @@ -4426,15 +4389,14 @@ func (tr *trackingReader) WasRead() bool { } func TestTransportExpectContinue(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/reject": w.WriteHeader(403) default: io.Copy(io.Discard, r.Body) } - }, optOnlyServer) - defer st.Close() + }) tr := &http.Transport{ TLSClientConfig: tlsConfigInsecure, @@ -4477,7 +4439,7 @@ func TestTransportExpectContinue(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { startTime := time.Now() - req, err := http.NewRequest("POST", st.ts.URL+tc.Path, tc.Body) + req, err := http.NewRequest("POST", ts.URL+tc.Path, tc.Body) if err != nil { t.Fatal(err) } @@ -4589,11 +4551,11 @@ func (c *blockingWriteConn) Write(b []byte) (n int, err error) { func TestTransportFrameBufferReuse(t *testing.T) { filler := hex.EncodeToString([]byte(randString(2048))) - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if got, want := r.Header.Get("Big"), filler; got != want { t.Errorf(`r.Header.Get("Big") = %q, want %q`, got, want) } - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil { t.Errorf("error reading request body: %v", err) } @@ -4603,8 +4565,7 @@ func TestTransportFrameBufferReuse(t *testing.T) { if got, want := r.Trailer.Get("Big"), filler; got != want { t.Errorf(`r.Trailer.Get("Big") = %q, want %q`, got, want) } - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() @@ -4615,7 +4576,7 @@ func TestTransportFrameBufferReuse(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - req, err := http.NewRequest("POST", st.ts.URL, strings.NewReader(filler)) + req, err := http.NewRequest("POST", ts.URL, strings.NewReader(filler)) if err != nil { t.Error(err) return @@ -4681,7 +4642,7 @@ func TestTransportBlockingRequestWrite(t *testing.T) { }} { test := test t.Run(test.name, func(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if v := r.Header.Get("Big"); v != "" && v != filler { t.Errorf("request header mismatch") } @@ -4691,10 +4652,9 @@ func TestTransportBlockingRequestWrite(t *testing.T) { if v := r.Trailer.Get("Big"); v != "" && v != filler { t.Errorf("request trailer mismatch\ngot: %q\nwant: %q", string(v), filler) } - }, optOnlyServer, func(s *Server) { + }, func(s *Server) { s.MaxConcurrentStreams = 1 }) - defer st.Close() // This Transport creates connections that block on writes after 1024 bytes. connc := make(chan *blockingWriteConn, 1) @@ -4716,7 +4676,7 @@ func TestTransportBlockingRequestWrite(t *testing.T) { // Request 1: A small request to ensure we read the server MaxConcurrentStreams. { - req, err := http.NewRequest("POST", st.ts.URL, nil) + req, err := http.NewRequest("POST", ts.URL, nil) if err != nil { t.Fatal(err) } @@ -4736,7 +4696,7 @@ func TestTransportBlockingRequestWrite(t *testing.T) { reqc := make(chan struct{}) go func() { defer close(reqc) - req, err := test.req(st.ts.URL) + req, err := test.req(ts.URL) if err != nil { t.Error(err) return @@ -4752,7 +4712,7 @@ func TestTransportBlockingRequestWrite(t *testing.T) { // Request 3: A small request that is sent on a new connection, since request 2 // is hogging the only available stream on the previous connection. { - req, err := http.NewRequest("POST", st.ts.URL, nil) + req, err := http.NewRequest("POST", ts.URL, nil) if err != nil { t.Fatal(err) } @@ -4787,15 +4747,14 @@ func TestTransportBlockingRequestWrite(t *testing.T) { func TestTransportCloseRequestBody(t *testing.T) { var statusCode int - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(statusCode) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() ctx := context.Background() - cc, err := tr.dialClientConn(ctx, st.ts.Listener.Addr().String(), false) + cc, err := tr.dialClientConn(ctx, ts.Listener.Addr().String(), false) if err != nil { t.Fatal(err) } @@ -4899,33 +4858,36 @@ func TestTransportRetriesOnStreamProtocolError(t *testing.T) { } func TestClientConnReservations(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { - }, func(s *Server) { - s.MaxConcurrentStreams = initialMaxConcurrentStreams - }) - defer st.Close() - - tr := &Transport{TLSClientConfig: tlsConfigInsecure} - defer tr.CloseIdleConnections() + tc := newTestClientConn(t) + tc.greet( + Setting{ID: SettingMaxConcurrentStreams, Val: initialMaxConcurrentStreams}, + ) - cc, err := tr.newClientConn(st.cc, false, nil) - if err != nil { - t.Fatal(err) + doRoundTrip := func() { + req, _ := http.NewRequest("GET", "https://dummy.tld/", nil) + rt := tc.roundTrip(req) + tc.wantFrameType(FrameHeaders) + tc.writeHeaders(HeadersFrameParam{ + StreamID: rt.streamID(), + EndHeaders: true, + EndStream: true, + BlockFragment: tc.makeHeaderBlockFragment( + ":status", "200", + ), + }) + rt.wantStatus(200) } - req, _ := http.NewRequest("GET", st.ts.URL, nil) n := 0 - for n <= initialMaxConcurrentStreams && cc.ReserveNewRequest() { + for n <= initialMaxConcurrentStreams && tc.cc.ReserveNewRequest() { n++ } if n != initialMaxConcurrentStreams { t.Errorf("did %v reservations; want %v", n, initialMaxConcurrentStreams) } - if _, err := cc.RoundTrip(req); err != nil { - t.Fatalf("RoundTrip error = %v", err) - } + doRoundTrip() n2 := 0 - for n2 <= 5 && cc.ReserveNewRequest() { + for n2 <= 5 && tc.cc.ReserveNewRequest() { n2++ } if n2 != 1 { @@ -4934,11 +4896,11 @@ func TestClientConnReservations(t *testing.T) { // Use up all the reservations for i := 0; i < n; i++ { - cc.RoundTrip(req) + doRoundTrip() } n2 = 0 - for n2 <= initialMaxConcurrentStreams && cc.ReserveNewRequest() { + for n2 <= initialMaxConcurrentStreams && tc.cc.ReserveNewRequest() { n2++ } if n2 != n { @@ -4972,10 +4934,9 @@ func TestTransportTimeoutServerHangs(t *testing.T) { func TestTransportContentLengthWithoutBody(t *testing.T) { contentLength := "" - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", contentLength) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() @@ -5002,7 +4963,7 @@ func TestTransportContentLengthWithoutBody(t *testing.T) { t.Run(test.name, func(t *testing.T) { contentLength = test.contentLength - req, _ := http.NewRequest("GET", st.ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) res, err := tr.RoundTrip(req) if err != nil { t.Fatal(err) @@ -5024,18 +4985,17 @@ func TestTransportContentLengthWithoutBody(t *testing.T) { } func TestTransportCloseResponseBodyWhileRequestBodyHangs(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.(http.Flusher).Flush() io.Copy(io.Discard, r.Body) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() pr, pw := net.Pipe() - req, err := http.NewRequest("GET", st.ts.URL, pr) + req, err := http.NewRequest("GET", ts.URL, pr) if err != nil { t.Fatal(err) } @@ -5051,19 +5011,18 @@ func TestTransportCloseResponseBodyWhileRequestBodyHangs(t *testing.T) { func TestTransport300ResponseBody(t *testing.T) { reqc := make(chan struct{}) body := []byte("response body") - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(300) w.(http.Flusher).Flush() <-reqc w.Write(body) - }, optOnlyServer) - defer st.Close() + }) tr := &Transport{TLSClientConfig: tlsConfigInsecure} defer tr.CloseIdleConnections() pr, pw := net.Pipe() - req, err := http.NewRequest("GET", st.ts.URL, pr) + req, err := http.NewRequest("GET", ts.URL, pr) if err != nil { t.Fatal(err) } @@ -5084,11 +5043,9 @@ func TestTransport300ResponseBody(t *testing.T) { } func TestTransportWriteByteTimeout(t *testing.T) { - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}, - optOnlyServer, ) - defer st.Close() tr := &Transport{ TLSClientConfig: tlsConfigInsecure, DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { @@ -5100,7 +5057,7 @@ func TestTransportWriteByteTimeout(t *testing.T) { defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} - _, err := c.Get(st.ts.URL) + _, err := c.Get(ts.URL) if !errors.Is(err, os.ErrDeadlineExceeded) { t.Fatalf("Get on unresponsive connection: got %q; want ErrDeadlineExceeded", err) } @@ -5128,11 +5085,9 @@ func (c *slowWriteConn) Write(b []byte) (n int, err error) { } func TestTransportSlowWrites(t *testing.T) { - st := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}, - optOnlyServer, ) - defer st.Close() tr := &Transport{ TLSClientConfig: tlsConfigInsecure, DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { @@ -5146,7 +5101,7 @@ func TestTransportSlowWrites(t *testing.T) { c := &http.Client{Transport: tr} const bodySize = 1 << 20 - resp, err := c.Post(st.ts.URL, "text/foo", io.LimitReader(neverEnding('A'), bodySize)) + resp, err := c.Post(ts.URL, "text/foo", io.LimitReader(neverEnding('A'), bodySize)) if err != nil { t.Fatal(err) } @@ -5188,12 +5143,12 @@ func testTransportClosesConnAfterGoAway(t *testing.T, lastStream uint32) { }) } - tc.closeWrite(io.EOF) + tc.closeWrite() err := rt.err() if gotErr, wantErr := err != nil, lastStream == 0; gotErr != wantErr { t.Errorf("RoundTrip got error %v (want error: %v)", err, wantErr) } - if !tc.netConnClosed { + if !tc.isClosed() { t.Errorf("ClientConn did not close its net.Conn, expected it to") } } @@ -5214,11 +5169,10 @@ func (r *slowCloser) Close() error { } func TestTransportSlowClose(t *testing.T) { - st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { - }, optOnlyServer) - defer st.Close() + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + }) - client := st.ts.Client() + client := ts.Client() body := &slowCloser{ closing: make(chan struct{}), closed: make(chan struct{}), @@ -5227,7 +5181,7 @@ func TestTransportSlowClose(t *testing.T) { reqc := make(chan struct{}) go func() { defer close(reqc) - res, err := client.Post(st.ts.URL, "text/plain", body) + res, err := client.Post(ts.URL, "text/plain", body) if err != nil { t.Error(err) } @@ -5240,7 +5194,7 @@ func TestTransportSlowClose(t *testing.T) { <-body.closing // wait for POST request to call body.Close // This GET request should not be blocked by the in-progress POST. - res, err := client.Get(st.ts.URL) + res, err := client.Get(ts.URL) if err != nil { t.Fatal(err) } @@ -5256,12 +5210,10 @@ func TestTransportDialTLSContext(t *testing.T) { ClientAuth: tls.RequestClientCert, } } - ts := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}, - optOnlyServer, serverTLSConfigFunc, ) - defer ts.Close() tr := &Transport{ TLSClientConfig: &tls.Config{ GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) { @@ -5275,7 +5227,7 @@ func TestTransportDialTLSContext(t *testing.T) { }, } defer tr.CloseIdleConnections() - req, err := http.NewRequest(http.MethodGet, ts.ts.URL, nil) + req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatal(err) } @@ -5320,12 +5272,10 @@ func TestDialRaceResumesDial(t *testing.T) { ClientAuth: tls.RequestClientCert, } } - ts := newServerTester(t, + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}, - optOnlyServer, serverTLSConfigFunc, ) - defer ts.Close() tr := &Transport{ TLSClientConfig: &tls.Config{ GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) { @@ -5343,7 +5293,7 @@ func TestDialRaceResumesDial(t *testing.T) { }, } defer tr.CloseIdleConnections() - req, err := http.NewRequest(http.MethodGet, ts.ts.URL, nil) + req, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatal(err) } @@ -5426,3 +5376,28 @@ func TestTransportDataAfter1xxHeader(t *testing.T) { } tc.wantFrameType(FrameRSTStream) } + +func TestIssue66763Race(t *testing.T) { + tr := &Transport{ + IdleConnTimeout: 1 * time.Nanosecond, + AllowHTTP: true, // issue 66763 only occurs when AllowHTTP is true + } + defer tr.CloseIdleConnections() + + cli, srv := net.Pipe() + donec := make(chan struct{}) + go func() { + // Creating the client conn may succeed or fail, + // depending on when the idle timeout happens. + // Either way, the idle timeout will close the net.Conn. + tr.NewClientConn(cli) + close(donec) + }() + + // The client sends its preface and SETTINGS frame, + // and then closes its conn after the idle timeout. + io.ReadAll(srv) + srv.Close() + + <-donec +} diff --git a/http2/writesched_priority.go b/http2/writesched_priority.go index 0a242c669e..f6783339d1 100644 --- a/http2/writesched_priority.go +++ b/http2/writesched_priority.go @@ -443,8 +443,8 @@ func (ws *priorityWriteScheduler) addClosedOrIdleNode(list *[]*priorityNode, max } func (ws *priorityWriteScheduler) removeNode(n *priorityNode) { - for k := n.kids; k != nil; k = k.next { - k.setParent(n.parent) + for n.kids != nil { + n.kids.setParent(n.parent) } n.setParent(nil) delete(ws.nodes, n.id) diff --git a/http2/writesched_priority_test.go b/http2/writesched_priority_test.go index b579ef9879..5aad057bea 100644 --- a/http2/writesched_priority_test.go +++ b/http2/writesched_priority_test.go @@ -562,3 +562,37 @@ func TestPriorityRstStreamOnNonOpenStreams(t *testing.T) { t.Error(err) } } + +// https://go.dev/issue/66514 +func TestPriorityIssue66514(t *testing.T) { + addDep := func(ws *priorityWriteScheduler, child uint32, parent uint32) { + ws.AdjustStream(child, PriorityParam{ + StreamDep: parent, + Exclusive: false, + Weight: 16, + }) + } + + validateDepTree := func(ws *priorityWriteScheduler, id uint32, t *testing.T) { + for n := ws.nodes[id]; n != nil; n = n.parent { + if n.parent == nil { + if n.id != uint32(0) { + t.Errorf("detected nodes not parented to 0") + } + } + } + } + + ws := NewPriorityWriteScheduler(nil).(*priorityWriteScheduler) + + // Root entry + addDep(ws, uint32(1), uint32(0)) + addDep(ws, uint32(3), uint32(1)) + addDep(ws, uint32(5), uint32(1)) + + for id := uint32(7); id < uint32(100); id += uint32(4) { + addDep(ws, id, id-uint32(4)) + addDep(ws, id+uint32(2), id-uint32(4)) + validateDepTree(ws, id, t) + } +} diff --git a/http2/z_spec_test.go b/http2/z_spec_test.go deleted file mode 100644 index 610b2cdbc2..0000000000 --- a/http2/z_spec_test.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package http2 - -import ( - "bytes" - "encoding/xml" - "flag" - "fmt" - "io" - "os" - "reflect" - "regexp" - "sort" - "strconv" - "strings" - "sync" - "testing" -) - -var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests") - -// The global map of sentence coverage for the http2 spec. -var defaultSpecCoverage specCoverage - -var loadSpecOnce sync.Once - -func loadSpec() { - if f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml"); err != nil { - panic(err) - } else { - defaultSpecCoverage = readSpecCov(f) - f.Close() - } -} - -// covers marks all sentences for section sec in defaultSpecCoverage. Sentences not -// "covered" will be included in report outputted by TestSpecCoverage. -func covers(sec, sentences string) { - loadSpecOnce.Do(loadSpec) - defaultSpecCoverage.cover(sec, sentences) -} - -type specPart struct { - section string - sentence string -} - -func (ss specPart) Less(oo specPart) bool { - atoi := func(s string) int { - n, err := strconv.Atoi(s) - if err != nil { - panic(err) - } - return n - } - a := strings.Split(ss.section, ".") - b := strings.Split(oo.section, ".") - for len(a) > 0 { - if len(b) == 0 { - return false - } - x, y := atoi(a[0]), atoi(b[0]) - if x == y { - a, b = a[1:], b[1:] - continue - } - return x < y - } - if len(b) > 0 { - return true - } - return false -} - -type bySpecSection []specPart - -func (a bySpecSection) Len() int { return len(a) } -func (a bySpecSection) Less(i, j int) bool { return a[i].Less(a[j]) } -func (a bySpecSection) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type specCoverage struct { - coverage map[specPart]bool - d *xml.Decoder -} - -func joinSection(sec []int) string { - s := fmt.Sprintf("%d", sec[0]) - for _, n := range sec[1:] { - s = fmt.Sprintf("%s.%d", s, n) - } - return s -} - -func (sc specCoverage) readSection(sec []int) { - var ( - buf = new(bytes.Buffer) - sub = 0 - ) - for { - tk, err := sc.d.Token() - if err != nil { - if err == io.EOF { - return - } - panic(err) - } - switch v := tk.(type) { - case xml.StartElement: - if skipElement(v) { - if err := sc.d.Skip(); err != nil { - panic(err) - } - if v.Name.Local == "section" { - sub++ - } - break - } - switch v.Name.Local { - case "section": - sub++ - sc.readSection(append(sec, sub)) - case "xref": - buf.Write(sc.readXRef(v)) - } - case xml.CharData: - if len(sec) == 0 { - break - } - buf.Write(v) - case xml.EndElement: - if v.Name.Local == "section" { - sc.addSentences(joinSection(sec), buf.String()) - return - } - } - } -} - -func (sc specCoverage) readXRef(se xml.StartElement) []byte { - var b []byte - for { - tk, err := sc.d.Token() - if err != nil { - panic(err) - } - switch v := tk.(type) { - case xml.CharData: - if b != nil { - panic("unexpected CharData") - } - b = []byte(string(v)) - case xml.EndElement: - if v.Name.Local != "xref" { - panic("expected ") - } - if b != nil { - return b - } - sig := attrSig(se) - switch sig { - case "target": - return []byte(fmt.Sprintf("[%s]", attrValue(se, "target"))) - case "fmt-of,rel,target", "fmt-,,rel,target": - return []byte(fmt.Sprintf("[%s, %s]", attrValue(se, "target"), attrValue(se, "rel"))) - case "fmt-of,sec,target", "fmt-,,sec,target": - return []byte(fmt.Sprintf("[section %s of %s]", attrValue(se, "sec"), attrValue(se, "target"))) - case "fmt-of,rel,sec,target": - return []byte(fmt.Sprintf("[section %s of %s, %s]", attrValue(se, "sec"), attrValue(se, "target"), attrValue(se, "rel"))) - default: - panic(fmt.Sprintf("unknown attribute signature %q in %#v", sig, fmt.Sprintf("%#v", se))) - } - default: - panic(fmt.Sprintf("unexpected tag %q", v)) - } - } -} - -var skipAnchor = map[string]bool{ - "intro": true, - "Overview": true, -} - -var skipTitle = map[string]bool{ - "Acknowledgements": true, - "Change Log": true, - "Document Organization": true, - "Conventions and Terminology": true, -} - -func skipElement(s xml.StartElement) bool { - switch s.Name.Local { - case "artwork": - return true - case "section": - for _, attr := range s.Attr { - switch attr.Name.Local { - case "anchor": - if skipAnchor[attr.Value] || strings.HasPrefix(attr.Value, "changes.since.") { - return true - } - case "title": - if skipTitle[attr.Value] { - return true - } - } - } - } - return false -} - -func readSpecCov(r io.Reader) specCoverage { - sc := specCoverage{ - coverage: map[specPart]bool{}, - d: xml.NewDecoder(r)} - sc.readSection(nil) - return sc -} - -func (sc specCoverage) addSentences(sec string, sentence string) { - for _, s := range parseSentences(sentence) { - sc.coverage[specPart{sec, s}] = false - } -} - -func (sc specCoverage) cover(sec string, sentence string) { - for _, s := range parseSentences(sentence) { - p := specPart{sec, s} - if _, ok := sc.coverage[p]; !ok { - panic(fmt.Sprintf("Not found in spec: %q, %q", sec, s)) - } - sc.coverage[specPart{sec, s}] = true - } - -} - -var whitespaceRx = regexp.MustCompile(`\s+`) - -func parseSentences(sens string) []string { - sens = strings.TrimSpace(sens) - if sens == "" { - return nil - } - ss := strings.Split(whitespaceRx.ReplaceAllString(sens, " "), ". ") - for i, s := range ss { - s = strings.TrimSpace(s) - if !strings.HasSuffix(s, ".") { - s += "." - } - ss[i] = s - } - return ss -} - -func TestSpecParseSentences(t *testing.T) { - tests := []struct { - ss string - want []string - }{ - {"Sentence 1. Sentence 2.", - []string{ - "Sentence 1.", - "Sentence 2.", - }}, - {"Sentence 1. \nSentence 2.\tSentence 3.", - []string{ - "Sentence 1.", - "Sentence 2.", - "Sentence 3.", - }}, - } - - for i, tt := range tests { - got := parseSentences(tt.ss) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%d: got = %q, want %q", i, got, tt.want) - } - } -} - -func TestSpecCoverage(t *testing.T) { - if !*coverSpec { - t.Skip() - } - - loadSpecOnce.Do(loadSpec) - - var ( - list []specPart - cv = defaultSpecCoverage.coverage - total = len(cv) - complete = 0 - ) - - for sp, touched := range defaultSpecCoverage.coverage { - if touched { - complete++ - } else { - list = append(list, sp) - } - } - sort.Stable(bySpecSection(list)) - - if testing.Short() && len(list) > 5 { - list = list[:5] - } - - for _, p := range list { - t.Errorf("\tSECTION %s: %s", p.section, p.sentence) - } - - t.Logf("%d/%d (%d%%) sentences covered", complete, total, (complete/total)*100) -} - -func attrSig(se xml.StartElement) string { - var names []string - for _, attr := range se.Attr { - if attr.Name.Local == "fmt" { - names = append(names, "fmt-"+attr.Value) - } else { - names = append(names, attr.Name.Local) - } - } - sort.Strings(names) - return strings.Join(names, ",") -} - -func attrValue(se xml.StartElement, attr string) string { - for _, a := range se.Attr { - if a.Name.Local == attr { - return a.Value - } - } - panic("unknown attribute " + attr) -} - -func TestSpecPartLess(t *testing.T) { - tests := []struct { - sec1, sec2 string - want bool - }{ - {"6.2.1", "6.2", false}, - {"6.2", "6.2.1", true}, - {"6.10", "6.10.1", true}, - {"6.10", "6.1.1", false}, // 10, not 1 - {"6.1", "6.1", false}, // equal, so not less - } - for _, tt := range tests { - got := (specPart{tt.sec1, "foo"}).Less(specPart{tt.sec2, "foo"}) - if got != tt.want { - t.Errorf("Less(%q, %q) = %v; want %v", tt.sec1, tt.sec2, got, tt.want) - } - } -} diff --git a/internal/iana/gen.go b/internal/iana/gen.go index 0fe65d8998..b4470baa75 100644 --- a/internal/iana/gen.go +++ b/internal/iana/gen.go @@ -16,7 +16,6 @@ import ( "fmt" "go/format" "io" - "io/ioutil" "net/http" "os" "strconv" @@ -69,7 +68,7 @@ func main() { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - if err := ioutil.WriteFile("const.go", b, 0644); err != nil { + if err := os.WriteFile("const.go", b, 0644); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } diff --git a/internal/socket/socket_test.go b/internal/socket/socket_test.go index faba106063..44c196b014 100644 --- a/internal/socket/socket_test.go +++ b/internal/socket/socket_test.go @@ -9,7 +9,6 @@ package socket_test import ( "bytes" "fmt" - "io/ioutil" "net" "os" "os/exec" @@ -446,7 +445,7 @@ func main() { if runtime.Compiler == "gccgo" { t.Skip("skipping race test when built with gccgo") } - dir, err := ioutil.TempDir("", "testrace") + dir, err := os.MkdirTemp("", "testrace") if err != nil { t.Fatalf("failed to create temp directory: %v", err) } @@ -463,7 +462,7 @@ func main() { for i, test := range tests { t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { src := filepath.Join(dir, fmt.Sprintf("test%d.go", i)) - if err := ioutil.WriteFile(src, []byte(test), 0644); err != nil { + if err := os.WriteFile(src, []byte(test), 0644); err != nil { t.Fatalf("failed to write file: %v", err) } t.Logf("%s run -race %s", goBinary, src) diff --git a/ipv4/gen.go b/ipv4/gen.go index 121c7643e9..f0182be2da 100644 --- a/ipv4/gen.go +++ b/ipv4/gen.go @@ -17,7 +17,6 @@ import ( "fmt" "go/format" "io" - "io/ioutil" "net/http" "os" "os/exec" @@ -61,7 +60,7 @@ func genzsys() error { case "freebsd", "linux": zsys = "zsys_" + runtime.GOOS + "_" + runtime.GOARCH + ".go" } - if err := ioutil.WriteFile(zsys, b, 0644); err != nil { + if err := os.WriteFile(zsys, b, 0644); err != nil { return err } return nil @@ -100,7 +99,7 @@ func geniana() error { if err != nil { return err } - if err := ioutil.WriteFile("iana.go", b, 0644); err != nil { + if err := os.WriteFile("iana.go", b, 0644); err != nil { return err } return nil diff --git a/ipv6/gen.go b/ipv6/gen.go index 2973dff5ce..590568a113 100644 --- a/ipv6/gen.go +++ b/ipv6/gen.go @@ -17,7 +17,6 @@ import ( "fmt" "go/format" "io" - "io/ioutil" "net/http" "os" "os/exec" @@ -61,7 +60,7 @@ func genzsys() error { case "freebsd", "linux": zsys = "zsys_" + runtime.GOOS + "_" + runtime.GOARCH + ".go" } - if err := ioutil.WriteFile(zsys, b, 0644); err != nil { + if err := os.WriteFile(zsys, b, 0644); err != nil { return err } return nil @@ -100,7 +99,7 @@ func geniana() error { if err != nil { return err } - if err := ioutil.WriteFile("iana.go", b, 0644); err != nil { + if err := os.WriteFile("iana.go", b, 0644); err != nil { return err } return nil diff --git a/nettest/conntest.go b/nettest/conntest.go index 615f4980c5..4297d408c0 100644 --- a/nettest/conntest.go +++ b/nettest/conntest.go @@ -8,7 +8,6 @@ import ( "bytes" "encoding/binary" "io" - "io/ioutil" "math/rand" "net" "runtime" @@ -173,7 +172,7 @@ func testRacyRead(t *testing.T, c1, c2 net.Conn) { // testRacyWrite tests that it is safe to mutate the input Write buffer // immediately after cancelation has occurred. func testRacyWrite(t *testing.T, c1, c2 net.Conn) { - go chunkedCopy(ioutil.Discard, c2) + go chunkedCopy(io.Discard, c2) var wg sync.WaitGroup defer wg.Wait() @@ -200,7 +199,7 @@ func testRacyWrite(t *testing.T, c1, c2 net.Conn) { // testReadTimeout tests that Read timeouts do not affect Write. func testReadTimeout(t *testing.T, c1, c2 net.Conn) { - go chunkedCopy(ioutil.Discard, c2) + go chunkedCopy(io.Discard, c2) c1.SetReadDeadline(aLongTimeAgo) _, err := c1.Read(make([]byte, 1024)) diff --git a/nettest/nettest.go b/nettest/nettest.go index 3656c3c54b..37e6dcb1b4 100644 --- a/nettest/nettest.go +++ b/nettest/nettest.go @@ -8,7 +8,6 @@ package nettest import ( "errors" "fmt" - "io/ioutil" "net" "os" "os/exec" @@ -226,7 +225,7 @@ func LocalPath() (string, error) { if runtime.GOOS == "darwin" { dir = "/tmp" } - f, err := ioutil.TempFile(dir, "go-nettest") + f, err := os.CreateTemp(dir, "go-nettest") if err != nil { return "", err } diff --git a/proxy/per_host.go b/proxy/per_host.go index 573fe79e86..d7d4b8b6e3 100644 --- a/proxy/per_host.go +++ b/proxy/per_host.go @@ -137,9 +137,7 @@ func (p *PerHost) AddNetwork(net *net.IPNet) { // AddZone specifies a DNS suffix that will use the bypass proxy. A zone of // "example.com" matches "example.com" and all of its subdomains. func (p *PerHost) AddZone(zone string) { - if strings.HasSuffix(zone, ".") { - zone = zone[:len(zone)-1] - } + zone = strings.TrimSuffix(zone, ".") if !strings.HasPrefix(zone, ".") { zone = "." + zone } @@ -148,8 +146,6 @@ func (p *PerHost) AddZone(zone string) { // AddHost specifies a host name that will use the bypass proxy. func (p *PerHost) AddHost(host string) { - if strings.HasSuffix(host, ".") { - host = host[:len(host)-1] - } + host = strings.TrimSuffix(host, ".") p.bypassHosts = append(p.bypassHosts, host) } diff --git a/publicsuffix/gen.go b/publicsuffix/gen.go index 21c191415f..7f7d08dbc2 100644 --- a/publicsuffix/gen.go +++ b/publicsuffix/gen.go @@ -26,7 +26,6 @@ import ( "fmt" "go/format" "io" - "io/ioutil" "net/http" "os" "regexp" @@ -298,7 +297,7 @@ func generate(p func(io.Writer, *node) error, root *node, filename string) error if err != nil { return err } - return ioutil.WriteFile(filename, b, 0644) + return os.WriteFile(filename, b, 0644) } func gitCommit() (sha, date string, retErr error) { @@ -310,7 +309,7 @@ func gitCommit() (sha, date string, retErr error) { return "", "", fmt.Errorf("bad GET status for %s: %s", gitCommitURL, res.Status) } defer res.Body.Close() - b, err := ioutil.ReadAll(res.Body) + b, err := io.ReadAll(res.Body) if err != nil { return "", "", err } diff --git a/quic/endpoint_test.go b/quic/endpoint_test.go index d5f436e6d7..3cba1423e3 100644 --- a/quic/endpoint_test.go +++ b/quic/endpoint_test.go @@ -23,6 +23,12 @@ func TestConnect(t *testing.T) { newLocalConnPair(t, &Config{}, &Config{}) } +func TestConnectDefaultTLSConfig(t *testing.T) { + serverConfig := newTestTLSConfigWithMoreDefaults(serverSide) + clientConfig := newTestTLSConfigWithMoreDefaults(clientSide) + newLocalConnPair(t, &Config{TLSConfig: serverConfig}, &Config{TLSConfig: clientConfig}) +} + func TestStreamTransfer(t *testing.T) { ctx := context.Background() cli, srv := newLocalConnPair(t, &Config{}, &Config{}) diff --git a/quic/tlsconfig_test.go b/quic/tlsconfig_test.go index 47bfb05983..5ed9818d57 100644 --- a/quic/tlsconfig_test.go +++ b/quic/tlsconfig_test.go @@ -20,6 +20,13 @@ func newTestTLSConfig(side connSide) *tls.Config { tls.TLS_CHACHA20_POLY1305_SHA256, }, MinVersion: tls.VersionTLS13, + // Default key exchange mechanisms as of Go 1.23 minus X25519Kyber768Draft00, + // which bloats the client hello enough to spill into a second datagram. + // Tests were written with the assuption each flight in the handshake + // fits in one datagram, and it's simpler to keep that property. + CurvePreferences: []tls.CurveID{ + tls.X25519, tls.CurveP256, tls.CurveP384, tls.CurveP521, + }, } if side == serverSide { config.Certificates = []tls.Certificate{testCert} @@ -27,6 +34,18 @@ func newTestTLSConfig(side connSide) *tls.Config { return config } +// newTestTLSConfigWithMoreDefaults returns a *tls.Config for testing +// which behaves more like a default, empty config. +// +// In particular, it uses the default curve preferences, which can increase +// the size of the handshake. +func newTestTLSConfigWithMoreDefaults(side connSide) *tls.Config { + config := newTestTLSConfig(side) + config.CipherSuites = nil + config.CurvePreferences = nil + return config +} + var testCert = func() tls.Certificate { cert, err := tls.X509KeyPair(localhostCert, localhostKey) if err != nil { diff --git a/webdav/file_test.go b/webdav/file_test.go index e875c136ca..3af53fde31 100644 --- a/webdav/file_test.go +++ b/webdav/file_test.go @@ -9,7 +9,6 @@ import ( "encoding/xml" "fmt" "io" - "io/ioutil" "os" "path" "path/filepath" @@ -518,7 +517,7 @@ func TestDir(t *testing.T) { t.Skip("see golang.org/issue/11453") } - td, err := ioutil.TempDir("", "webdav-test") + td, err := os.MkdirTemp("", "webdav-test") if err != nil { t.Fatal(err) } @@ -758,7 +757,7 @@ func TestMemFile(t *testing.T) { if err != nil { t.Fatalf("test case #%d %q: OpenFile: %v", i, tc, err) } - gotBytes, err := ioutil.ReadAll(g) + gotBytes, err := io.ReadAll(g) if err != nil { t.Fatalf("test case #%d %q: ReadAll: %v", i, tc, err) } diff --git a/webdav/webdav.go b/webdav/webdav.go index add2bcd67c..8ff3d100f9 100644 --- a/webdav/webdav.go +++ b/webdav/webdav.go @@ -267,6 +267,9 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { + if os.IsNotExist(err) { + return http.StatusConflict, err + } return http.StatusNotFound, err } _, copyErr := io.Copy(f, r.Body) diff --git a/webdav/webdav_test.go b/webdav/webdav_test.go index 2baebe3c97..deb60fb885 100644 --- a/webdav/webdav_test.go +++ b/webdav/webdav_test.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/http/httptest" "net/url" @@ -256,7 +255,7 @@ func TestFilenameEscape(t *testing.T) { } defer res.Body.Close() - b, err := ioutil.ReadAll(res.Body) + b, err := io.ReadAll(res.Body) if err != nil { return "", "", err } @@ -347,3 +346,63 @@ func TestFilenameEscape(t *testing.T) { } } } + +func TestPutRequest(t *testing.T) { + h := &Handler{ + FileSystem: NewMemFS(), + LockSystem: NewMemLS(), + } + srv := httptest.NewServer(h) + defer srv.Close() + + do := func(method, urlStr string, body string) (*http.Response, error) { + bodyReader := strings.NewReader(body) + req, err := http.NewRequest(method, urlStr, bodyReader) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + return res, nil + } + + testCases := []struct { + name string + urlPrefix string + want int + }{{ + name: "put", + urlPrefix: "/res", + want: http.StatusCreated, + }, { + name: "put_utf8_segment", + urlPrefix: "/res-%e2%82%ac", + want: http.StatusCreated, + }, { + name: "put_empty_segment", + urlPrefix: "", + want: http.StatusNotFound, + }, { + name: "put_root_segment", + urlPrefix: "/", + want: http.StatusNotFound, + }, { + name: "put_no_parent [RFC4918:S9.7.1]", + urlPrefix: "/409me/noparent.txt", + want: http.StatusConflict, + }} + + for _, tc := range testCases { + urlStr := srv.URL + tc.urlPrefix + res, err := do("PUT", urlStr, "ABC\n") + if err != nil { + t.Errorf("name=%q: PUT: %v", tc.name, err) + continue + } + if res.StatusCode != tc.want { + t.Errorf("name=%q: got status code %d, want %d", tc.name, res.StatusCode, tc.want) + } + } +} diff --git a/websocket/hybi.go b/websocket/hybi.go index 48a069e190..dda7434666 100644 --- a/websocket/hybi.go +++ b/websocket/hybi.go @@ -16,7 +16,6 @@ import ( "encoding/binary" "fmt" "io" - "io/ioutil" "net/http" "net/url" "strings" @@ -279,7 +278,7 @@ func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, er } } if header := frame.HeaderReader(); header != nil { - io.Copy(ioutil.Discard, header) + io.Copy(io.Discard, header) } switch frame.PayloadType() { case ContinuationFrame: @@ -294,7 +293,7 @@ func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, er if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { return nil, err } - io.Copy(ioutil.Discard, frame) + io.Copy(io.Discard, frame) if frame.PayloadType() == PingFrame { if _, err := handler.WritePong(b[:n]); err != nil { return nil, err diff --git a/websocket/websocket.go b/websocket/websocket.go index 90a2257cd5..923a5780ec 100644 --- a/websocket/websocket.go +++ b/websocket/websocket.go @@ -17,7 +17,6 @@ import ( "encoding/json" "errors" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -208,7 +207,7 @@ again: n, err = ws.frameReader.Read(msg) if err == io.EOF { if trailer := ws.frameReader.TrailerReader(); trailer != nil { - io.Copy(ioutil.Discard, trailer) + io.Copy(io.Discard, trailer) } ws.frameReader = nil goto again @@ -330,7 +329,7 @@ func (cd Codec) Receive(ws *Conn, v interface{}) (err error) { ws.rio.Lock() defer ws.rio.Unlock() if ws.frameReader != nil { - _, err = io.Copy(ioutil.Discard, ws.frameReader) + _, err = io.Copy(io.Discard, ws.frameReader) if err != nil { return err } @@ -362,7 +361,7 @@ again: return ErrFrameTooLarge } payloadType := frame.PayloadType() - data, err := ioutil.ReadAll(frame) + data, err := io.ReadAll(frame) if err != nil { return err }