| // 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. | |
| //go:build !(go1.27 && !http2legacy) | |
| package http2_test | |
| import ( | |
| "bytes" | |
| "compress/gzip" | |
| "compress/zlib" | |
| "context" | |
| "crypto/tls" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "log" | |
| "math" | |
| "net" | |
| "net/http" | |
| "net/http/httptest" | |
| "os" | |
| "reflect" | |
| "runtime" | |
| "slices" | |
| "strconv" | |
| "strings" | |
| "sync" | |
| "testing" | |
| "testing/synctest" | |
| "time" | |
| . "golang.org/x/net/http2" | |
| "golang.org/x/net/http2/hpack" | |
| ) | |
| 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 | |
| } | |
| // 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 { | |
| ts := httptest.NewUnstartedServer(handler) | |
| t.Cleanup(ts.Close) | |
| tlsConfig := &tls.Config{ | |
| InsecureSkipVerify: true, | |
| NextProtos: []string{NextProtoTLS}, | |
| } | |
| var framerReuseFrames bool | |
| h2server := new(Server) | |
| for _, opt := range opts { | |
| switch v := opt.(type) { | |
| case func(*tls.Config): | |
| 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 optFramerReuseFrames: | |
| framerReuseFrames = true | |
| } | |
| case func(net.Conn, http.ConnState): | |
| ts.Config.ConnState = v | |
| default: | |
| t.Fatalf("unknown newServerTester option type %T", v) | |
| } | |
| } | |
| ConfigureServer(ts.Config, h2server) | |
| // 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 | |
| st := &serverTester{ | |
| t: t, | |
| } | |
| st.hpackEnc = hpack.NewEncoder(&st.headerBuf) | |
| ts.TLS = ts.Config.TLSConfig // the httptest.Server has its own copy of this TLS config | |
| if ts.Config.ErrorLog == nil { | |
| ts.Config.ErrorLog = log.New(io.MultiWriter(stderrv(), twriter{t: t, st: st}, &st.serverLogBuf), "", log.LstdFlags) | |
| } | |
| ts.StartTLS() | |
| if VerboseLogs { | |
| t.Logf("Running test server at: %s", ts.URL) | |
| } | |
| SetTestHookGetServerConn(t, func(v *ServerConn) { | |
| st.scMu.Lock() | |
| defer st.scMu.Unlock() | |
| st.sc = v | |
| }) | |
| log.SetOutput(io.MultiWriter(stderrv(), twriter{t: t, st: st})) | |
| 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.TestSetDebugReadLoggerf(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.TestSetDebugWriteLoggerf(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() | |
| }) | |
| } | |
| return st | |
| } | |
| func (st *serverTester) streamExists(id uint32) bool { | |
| if st.sc == nil { | |
| st.t.Fatalf("serverTester has no ServerConn") | |
| } | |
| return st.sc.TestStreamExists(id) | |
| } | |
| func (st *serverTester) streamState(id uint32) StreamState { | |
| if st.sc == nil { | |
| st.t.Fatalf("serverTester has no ServerConn") | |
| } | |
| return st.sc.TestStreamState(id) | |
| } | |
| func (st *serverTester) wantConnFlowControlConsumed(consumed int32) { | |
| if got, want := st.sc.TestFlowControlConsumed(), consumed; got != want { | |
| st.t.Errorf("connection flow control consumed: %v, want %v", got, want) | |
| } | |
| } | |
| func TestServer(t *testing.T) { synctestTest(t, testServer) } | |
| func testServer(t testing.TB) { | |
| gotReq := make(chan bool, 1) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Foo", "Bar") | |
| gotReq <- true | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, // no DATA frames | |
| EndHeaders: true, | |
| }) | |
| <-gotReq | |
| } | |
| func TestServer_Request_Get(t *testing.T) { synctestTest(t, testServer_Request_Get) } | |
| func testServer_Request_Get(t testing.TB) { | |
| testServerRequest(t, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader("foo-bar", "some-value"), | |
| EndStream: true, // no DATA frames | |
| EndHeaders: true, | |
| }) | |
| }, func(r *http.Request) { | |
| if r.Method != "GET" { | |
| t.Errorf("Method = %q; want GET", r.Method) | |
| } | |
| if r.URL.Path != "/" { | |
| t.Errorf("URL.Path = %q; want /", r.URL.Path) | |
| } | |
| if r.ContentLength != 0 { | |
| t.Errorf("ContentLength = %v; want 0", r.ContentLength) | |
| } | |
| if r.Close { | |
| t.Error("Close = true; want false") | |
| } | |
| if !strings.Contains(r.RemoteAddr, ":") { | |
| t.Errorf("RemoteAddr = %q; want something with a colon", r.RemoteAddr) | |
| } | |
| if r.Proto != "HTTP/2.0" || r.ProtoMajor != 2 || r.ProtoMinor != 0 { | |
| t.Errorf("Proto = %q Major=%v,Minor=%v; want HTTP/2.0", r.Proto, r.ProtoMajor, r.ProtoMinor) | |
| } | |
| wantHeader := http.Header{ | |
| "Foo-Bar": []string{"some-value"}, | |
| } | |
| if !reflect.DeepEqual(r.Header, wantHeader) { | |
| t.Errorf("Header = %#v; want %#v", r.Header, wantHeader) | |
| } | |
| if n, err := r.Body.Read([]byte(" ")); err != io.EOF || n != 0 { | |
| t.Errorf("Read = %d, %v; want 0, EOF", n, err) | |
| } | |
| }) | |
| } | |
| func TestServer_Request_Get_PathSlashes(t *testing.T) { | |
| synctestTest(t, testServer_Request_Get_PathSlashes) | |
| } | |
| func testServer_Request_Get_PathSlashes(t testing.TB) { | |
| testServerRequest(t, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":path", "/%2f/"), | |
| EndStream: true, // no DATA frames | |
| EndHeaders: true, | |
| }) | |
| }, func(r *http.Request) { | |
| if r.RequestURI != "/%2f/" { | |
| t.Errorf("RequestURI = %q; want /%%2f/", r.RequestURI) | |
| } | |
| if r.URL.Path != "///" { | |
| t.Errorf("URL.Path = %q; want ///", r.URL.Path) | |
| } | |
| }) | |
| } | |
| // TODO: add a test with EndStream=true on the HEADERS but setting a | |
| // Content-Length anyway. Should we just omit it and force it to | |
| // zero? | |
| func TestServer_Request_Post_NoContentLength_EndStream(t *testing.T) { | |
| synctestTest(t, testServer_Request_Post_NoContentLength_EndStream) | |
| } | |
| func testServer_Request_Post_NoContentLength_EndStream(t testing.TB) { | |
| testServerRequest(t, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }, func(r *http.Request) { | |
| if r.Method != "POST" { | |
| t.Errorf("Method = %q; want POST", r.Method) | |
| } | |
| if r.ContentLength != 0 { | |
| t.Errorf("ContentLength = %v; want 0", r.ContentLength) | |
| } | |
| if n, err := r.Body.Read([]byte(" ")); err != io.EOF || n != 0 { | |
| t.Errorf("Read = %d, %v; want 0, EOF", n, err) | |
| } | |
| }) | |
| } | |
| func TestServer_Request_Post_Body_ImmediateEOF(t *testing.T) { | |
| synctestTest(t, testServer_Request_Post_Body_ImmediateEOF) | |
| } | |
| func testServer_Request_Post_Body_ImmediateEOF(t testing.TB) { | |
| testBodyContents(t, -1, "", func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, true, nil) // just kidding. empty body. | |
| }) | |
| } | |
| func TestServer_Request_Post_Body_OneData(t *testing.T) { | |
| synctestTest(t, testServer_Request_Post_Body_OneData) | |
| } | |
| func testServer_Request_Post_Body_OneData(t testing.TB) { | |
| const content = "Some content" | |
| testBodyContents(t, -1, content, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, true, []byte(content)) | |
| }) | |
| } | |
| func TestServer_Request_Post_Body_TwoData(t *testing.T) { | |
| synctestTest(t, testServer_Request_Post_Body_TwoData) | |
| } | |
| func testServer_Request_Post_Body_TwoData(t testing.TB) { | |
| const content = "Some content" | |
| testBodyContents(t, -1, content, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, false, []byte(content[:5])) | |
| st.writeData(1, true, []byte(content[5:])) | |
| }) | |
| } | |
| func TestServer_Request_Post_Body_ContentLength_Correct(t *testing.T) { | |
| synctestTest(t, testServer_Request_Post_Body_ContentLength_Correct) | |
| } | |
| func testServer_Request_Post_Body_ContentLength_Correct(t testing.TB) { | |
| const content = "Some content" | |
| testBodyContents(t, int64(len(content)), content, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader( | |
| ":method", "POST", | |
| "content-length", strconv.Itoa(len(content)), | |
| ), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, true, []byte(content)) | |
| }) | |
| } | |
| func TestServer_Request_Post_Body_ContentLength_TooLarge(t *testing.T) { | |
| synctestTest(t, testServer_Request_Post_Body_ContentLength_TooLarge) | |
| } | |
| func testServer_Request_Post_Body_ContentLength_TooLarge(t testing.TB) { | |
| testBodyContentsFail(t, 3, "request declared a Content-Length of 3 but only wrote 2 bytes", | |
| func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader( | |
| ":method", "POST", | |
| "content-length", "3", | |
| ), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, true, []byte("12")) | |
| }) | |
| } | |
| func TestServer_Request_Post_Body_ContentLength_TooSmall(t *testing.T) { | |
| synctestTest(t, testServer_Request_Post_Body_ContentLength_TooSmall) | |
| } | |
| func testServer_Request_Post_Body_ContentLength_TooSmall(t testing.TB) { | |
| testBodyContentsFail(t, 4, "sender tried to send more than declared Content-Length of 4 bytes", | |
| func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader( | |
| ":method", "POST", | |
| "content-length", "4", | |
| ), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, true, []byte("12345")) | |
| // Return flow control bytes back, since the data handler closed | |
| // the stream. | |
| st.wantRSTStream(1, ErrCodeProtocol) | |
| st.wantConnFlowControlConsumed(0) | |
| }) | |
| } | |
| func testBodyContents(t testing.TB, wantContentLength int64, wantBody string, write func(st *serverTester)) { | |
| testServerRequest(t, write, func(r *http.Request) { | |
| if r.Method != "POST" { | |
| t.Errorf("Method = %q; want POST", r.Method) | |
| } | |
| if r.ContentLength != wantContentLength { | |
| t.Errorf("ContentLength = %v; want %d", r.ContentLength, wantContentLength) | |
| } | |
| all, err := io.ReadAll(r.Body) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if string(all) != wantBody { | |
| t.Errorf("Read = %q; want %q", all, wantBody) | |
| } | |
| if err := r.Body.Close(); err != nil { | |
| t.Fatalf("Close: %v", err) | |
| } | |
| }) | |
| } | |
| func testBodyContentsFail(t testing.TB, wantContentLength int64, wantReadError string, write func(st *serverTester)) { | |
| testServerRequest(t, write, func(r *http.Request) { | |
| if r.Method != "POST" { | |
| t.Errorf("Method = %q; want POST", r.Method) | |
| } | |
| if r.ContentLength != wantContentLength { | |
| t.Errorf("ContentLength = %v; want %d", r.ContentLength, wantContentLength) | |
| } | |
| 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) | |
| } | |
| if !strings.Contains(err.Error(), wantReadError) { | |
| t.Fatalf("Body.Read = %v; want substring %q", err, wantReadError) | |
| } | |
| if err := r.Body.Close(); err != nil { | |
| t.Fatalf("Close: %v", err) | |
| } | |
| }) | |
| } | |
| // Using a Host header, instead of :authority | |
| func TestServer_Request_Get_Host(t *testing.T) { synctestTest(t, testServer_Request_Get_Host) } | |
| func testServer_Request_Get_Host(t testing.TB) { | |
| const host = "example.com" | |
| testServerRequest(t, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":authority", "", "host", host), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }, func(r *http.Request) { | |
| if r.Host != host { | |
| t.Errorf("Host = %q; want %q", r.Host, host) | |
| } | |
| }) | |
| } | |
| // Using an :authority pseudo-header, instead of Host | |
| func TestServer_Request_Get_Authority(t *testing.T) { | |
| synctestTest(t, testServer_Request_Get_Authority) | |
| } | |
| func testServer_Request_Get_Authority(t testing.TB) { | |
| const host = "example.com" | |
| testServerRequest(t, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":authority", host), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }, func(r *http.Request) { | |
| if r.Host != host { | |
| t.Errorf("Host = %q; want %q", r.Host, host) | |
| } | |
| }) | |
| } | |
| func TestServer_Request_WithContinuation(t *testing.T) { | |
| synctestTest(t, testServer_Request_WithContinuation) | |
| } | |
| func testServer_Request_WithContinuation(t testing.TB) { | |
| wantHeader := http.Header{ | |
| "Foo-One": []string{"value-one"}, | |
| "Foo-Two": []string{"value-two"}, | |
| "Foo-Three": []string{"value-three"}, | |
| } | |
| testServerRequest(t, func(st *serverTester) { | |
| fullHeaders := st.encodeHeader( | |
| "foo-one", "value-one", | |
| "foo-two", "value-two", | |
| "foo-three", "value-three", | |
| ) | |
| remain := fullHeaders | |
| chunks := 0 | |
| for len(remain) > 0 { | |
| const maxChunkSize = 5 | |
| chunk := remain | |
| if len(chunk) > maxChunkSize { | |
| chunk = chunk[:maxChunkSize] | |
| } | |
| remain = remain[len(chunk):] | |
| if chunks == 0 { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: chunk, | |
| EndStream: true, // no DATA frames | |
| EndHeaders: false, // we'll have continuation frames | |
| }) | |
| } else { | |
| err := st.fr.WriteContinuation(1, len(remain) == 0, chunk) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| chunks++ | |
| } | |
| if chunks < 2 { | |
| t.Fatal("too few chunks") | |
| } | |
| }, func(r *http.Request) { | |
| if !reflect.DeepEqual(r.Header, wantHeader) { | |
| t.Errorf("Header = %#v; want %#v", r.Header, wantHeader) | |
| } | |
| }) | |
| } | |
| // Concatenated cookie headers. ("8.1.2.5 Compressing the Cookie Header Field") | |
| func TestServer_Request_CookieConcat(t *testing.T) { synctestTest(t, testServer_Request_CookieConcat) } | |
| func testServer_Request_CookieConcat(t testing.TB) { | |
| const host = "example.com" | |
| testServerRequest(t, func(st *serverTester) { | |
| st.bodylessReq1( | |
| ":authority", host, | |
| "cookie", "a=b", | |
| "cookie", "c=d", | |
| "cookie", "e=f", | |
| ) | |
| }, func(r *http.Request) { | |
| const want = "a=b; c=d; e=f" | |
| if got := r.Header.Get("Cookie"); got != want { | |
| t.Errorf("Cookie = %q; want %q", got, want) | |
| } | |
| }) | |
| } | |
| func TestServer_Request_Reject_CapitalHeader(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("UPPER", "v") }) | |
| } | |
| func TestServer_Request_Reject_HeaderFieldNameColon(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("has:colon", "v") }) | |
| } | |
| func TestServer_Request_Reject_HeaderFieldNameNULL(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("has\x00null", "v") }) | |
| } | |
| func TestServer_Request_Reject_HeaderFieldNameEmpty(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("", "v") }) | |
| } | |
| func TestServer_Request_Reject_HeaderFieldValueNewline(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("foo", "has\nnewline") }) | |
| } | |
| func TestServer_Request_Reject_HeaderFieldValueCR(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("foo", "has\rcarriage") }) | |
| } | |
| func TestServer_Request_Reject_HeaderFieldValueDEL(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("foo", "has\x7fdel") }) | |
| } | |
| func TestServer_Request_Reject_Pseudo_Missing_method(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":method", "") }) | |
| } | |
| func TestServer_Request_Reject_Pseudo_ExactlyOne(t *testing.T) { | |
| // 8.1.2.3 Request Pseudo-Header Fields | |
| // "All HTTP/2 requests MUST include exactly one valid value" ... | |
| testRejectRequest(t, func(st *serverTester) { | |
| st.addLogFilter("duplicate pseudo-header") | |
| st.bodylessReq1(":method", "GET", ":method", "POST") | |
| }) | |
| } | |
| func TestServer_Request_Reject_Pseudo_AfterRegular(t *testing.T) { | |
| // 8.1.2.3 Request Pseudo-Header Fields | |
| // "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 (Section 8.1.2.6)." | |
| testRejectRequest(t, func(st *serverTester) { | |
| st.addLogFilter("pseudo-header after regular header") | |
| var buf bytes.Buffer | |
| enc := hpack.NewEncoder(&buf) | |
| enc.WriteField(hpack.HeaderField{Name: ":method", Value: "GET"}) | |
| enc.WriteField(hpack.HeaderField{Name: "regular", Value: "foobar"}) | |
| enc.WriteField(hpack.HeaderField{Name: ":path", Value: "/"}) | |
| enc.WriteField(hpack.HeaderField{Name: ":scheme", Value: "https"}) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: buf.Bytes(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }) | |
| } | |
| func TestServer_Request_Reject_Pseudo_Missing_path(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":path", "") }) | |
| } | |
| func TestServer_Request_Reject_Pseudo_Missing_scheme(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":scheme", "") }) | |
| } | |
| func TestServer_Request_Reject_Pseudo_scheme_invalid(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":scheme", "bogus") }) | |
| } | |
| func TestServer_Request_Reject_Pseudo_Unknown(t *testing.T) { | |
| testRejectRequest(t, func(st *serverTester) { | |
| st.addLogFilter(`invalid pseudo-header ":unknown_thing"`) | |
| st.bodylessReq1(":unknown_thing", "") | |
| }) | |
| } | |
| func TestServer_Request_Reject_Authority_Userinfo(t *testing.T) { | |
| // "':authority' MUST NOT include the deprecated userinfo subcomponent | |
| // for "http" or "https" schemed URIs." | |
| // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8 | |
| testRejectRequest(t, func(st *serverTester) { | |
| var buf bytes.Buffer | |
| enc := hpack.NewEncoder(&buf) | |
| enc.WriteField(hpack.HeaderField{Name: ":authority", Value: "userinfo@example.tld"}) | |
| enc.WriteField(hpack.HeaderField{Name: ":method", Value: "GET"}) | |
| enc.WriteField(hpack.HeaderField{Name: ":path", Value: "/"}) | |
| enc.WriteField(hpack.HeaderField{Name: ":scheme", Value: "https"}) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: buf.Bytes(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }) | |
| } | |
| func testRejectRequest(t *testing.T, send func(*serverTester)) { | |
| synctestTest(t, func(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| t.Error("server request made it to handler; should've been rejected") | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| send(st) | |
| st.wantRSTStream(1, ErrCodeProtocol) | |
| }) | |
| } | |
| func newServerTesterForError(t testing.TB) *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) | |
| st.greet() | |
| 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) { | |
| synctestTest(t, testRejectFrameOnIdle_WindowUpdate) | |
| } | |
| func testRejectFrameOnIdle_WindowUpdate(t testing.TB) { | |
| st := newServerTesterForError(t) | |
| st.fr.WriteWindowUpdate(123, 456) | |
| st.wantGoAway(123, ErrCodeProtocol) | |
| } | |
| func TestRejectFrameOnIdle_Data(t *testing.T) { synctestTest(t, testRejectFrameOnIdle_Data) } | |
| func testRejectFrameOnIdle_Data(t testing.TB) { | |
| st := newServerTesterForError(t) | |
| st.fr.WriteData(123, true, nil) | |
| st.wantGoAway(123, ErrCodeProtocol) | |
| } | |
| func TestRejectFrameOnIdle_RSTStream(t *testing.T) { synctestTest(t, testRejectFrameOnIdle_RSTStream) } | |
| func testRejectFrameOnIdle_RSTStream(t testing.TB) { | |
| st := newServerTesterForError(t) | |
| st.fr.WriteRSTStream(123, ErrCodeCancel) | |
| st.wantGoAway(123, ErrCodeProtocol) | |
| } | |
| func TestServer_Request_Connect(t *testing.T) { synctestTest(t, testServer_Request_Connect) } | |
| func testServer_Request_Connect(t testing.TB) { | |
| testServerRequest(t, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeaderRaw( | |
| ":method", "CONNECT", | |
| ":authority", "example.com:123", | |
| ), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }, func(r *http.Request) { | |
| if g, w := r.Method, "CONNECT"; g != w { | |
| t.Errorf("Method = %q; want %q", g, w) | |
| } | |
| if g, w := r.RequestURI, "example.com:123"; g != w { | |
| t.Errorf("RequestURI = %q; want %q", g, w) | |
| } | |
| if g, w := r.URL.Host, "example.com:123"; g != w { | |
| t.Errorf("URL.Host = %q; want %q", g, w) | |
| } | |
| }) | |
| } | |
| func TestServer_Request_Connect_InvalidPath(t *testing.T) { | |
| synctestTest(t, testServer_Request_Connect_InvalidPath) | |
| } | |
| func testServer_Request_Connect_InvalidPath(t testing.TB) { | |
| testServerRejectsStream(t, ErrCodeProtocol, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeaderRaw( | |
| ":method", "CONNECT", | |
| ":authority", "example.com:123", | |
| ":path", "/bogus", | |
| ), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }) | |
| } | |
| func TestServer_Request_Connect_InvalidScheme(t *testing.T) { | |
| synctestTest(t, testServer_Request_Connect_InvalidScheme) | |
| } | |
| func testServer_Request_Connect_InvalidScheme(t testing.TB) { | |
| testServerRejectsStream(t, ErrCodeProtocol, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeaderRaw( | |
| ":method", "CONNECT", | |
| ":authority", "example.com:123", | |
| ":scheme", "https", | |
| ), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| }) | |
| } | |
| func TestServer_Ping(t *testing.T) { synctestTest(t, testServer_Ping) } | |
| func testServer_Ping(t testing.TB) { | |
| st := newServerTester(t, nil) | |
| defer st.Close() | |
| st.greet() | |
| // Server should ignore this one, since it has ACK set. | |
| ackPingData := [8]byte{1, 2, 4, 8, 16, 32, 64, 128} | |
| if err := st.fr.WritePing(true, ackPingData); err != nil { | |
| t.Fatal(err) | |
| } | |
| // But the server should reply to this one, since ACK is false. | |
| pingData := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} | |
| if err := st.fr.WritePing(false, pingData); err != nil { | |
| t.Fatal(err) | |
| } | |
| pf := readFrame[*PingFrame](t, st) | |
| if !pf.Flags.Has(FlagPingAck) { | |
| t.Error("response ping doesn't have ACK set") | |
| } | |
| if pf.Data != pingData { | |
| t.Errorf("response ping has data %q; want %q", pf.Data, pingData) | |
| } | |
| } | |
| type filterListener struct { | |
| net.Listener | |
| accept func(conn net.Conn) (net.Conn, error) | |
| } | |
| func (l *filterListener) Accept() (net.Conn, error) { | |
| c, err := l.Listener.Accept() | |
| if err != nil { | |
| return nil, err | |
| } | |
| return l.accept(c) | |
| } | |
| func TestServer_MaxQueuedControlFrames(t *testing.T) { | |
| synctestTest(t, testServer_MaxQueuedControlFrames) | |
| } | |
| func testServer_MaxQueuedControlFrames(t testing.TB) { | |
| // Goroutine debugging makes this test very slow. | |
| DisableGoroutineTracking(t) | |
| st := newServerTester(t, nil) | |
| st.greet() | |
| 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} | |
| st.fr.WritePing(false, pingData) | |
| } | |
| synctest.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 | |
| } | |
| } | |
| st.wantClosed() | |
| } | |
| func TestServer_RejectsLargeFrames(t *testing.T) { synctestTest(t, testServer_RejectsLargeFrames) } | |
| func testServer_RejectsLargeFrames(t testing.TB) { | |
| if runtime.GOOS == "windows" || runtime.GOOS == "plan9" || runtime.GOOS == "zos" { | |
| t.Skip("see golang.org/issue/13434, golang.org/issue/37321") | |
| } | |
| st := newServerTester(t, nil) | |
| defer st.Close() | |
| st.greet() | |
| // Write too large of a frame (too large by one byte) | |
| // We ignore the return value because it's expected that the server | |
| // will only read the first 9 bytes (the headre) and then disconnect. | |
| st.fr.WriteRawFrame(0xff, 0, 0, make([]byte, DefaultMaxReadFrameSize+1)) | |
| st.wantGoAway(0, ErrCodeFrameSize) | |
| st.advance(GoAwayTimeout) | |
| st.wantClosed() | |
| } | |
| func TestServer_Handler_Sends_WindowUpdate(t *testing.T) { | |
| synctestTest(t, testServer_Handler_Sends_WindowUpdate) | |
| } | |
| func testServer_Handler_Sends_WindowUpdate(t testing.TB) { | |
| // Need to set this to at least twice the initial window size, | |
| // or st.greet gets stuck waiting for a WINDOW_UPDATE. | |
| // | |
| // This also needs to be less than MAX_FRAME_SIZE. | |
| const windowSize = 65535 * 2 | |
| st := newServerTester(t, nil, func(s *Server) { | |
| s.MaxUploadBufferPerConnection = windowSize | |
| s.MaxUploadBufferPerStream = windowSize | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, // data coming | |
| EndHeaders: true, | |
| }) | |
| call := st.nextHandlerCall() | |
| // 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 | |
| // combine with a future update. | |
| data := make([]byte, windowSize) | |
| st.writeData(1, false, data[:1024]) | |
| call.do(readBodyHandler(t, string(data[:1024]))) | |
| // 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) | |
| // The handler consumes the data and the server returns credit. | |
| call.do(readBodyHandler(t, string(data[1024:]))) | |
| st.wantWindowUpdate(0, windowSize-1024) | |
| st.wantWindowUpdate(1, windowSize-1024) | |
| } | |
| // the version of the TestServer_Handler_Sends_WindowUpdate with padding. | |
| // See golang.org/issue/16556 | |
| func TestServer_Handler_Sends_WindowUpdate_Padding(t *testing.T) { | |
| synctestTest(t, testServer_Handler_Sends_WindowUpdate_Padding) | |
| } | |
| func testServer_Handler_Sends_WindowUpdate_Padding(t testing.TB) { | |
| const windowSize = 65535 * 2 | |
| st := newServerTester(t, nil, func(s *Server) { | |
| s.MaxUploadBufferPerConnection = windowSize | |
| s.MaxUploadBufferPerStream = windowSize | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| call := st.nextHandlerCall() | |
| // Write half a window of data, with some padding. | |
| // The server doesn't return the padding yet, buffering the 5 bytes to combine | |
| // with a future update. | |
| data := make([]byte, windowSize/2) | |
| pad := make([]byte, 4) | |
| st.writeDataPadded(1, false, data, pad) | |
| // The handler consumes the body. | |
| // The server returns flow control for the body and padding | |
| // (4 bytes of padding + 1 byte of length). | |
| call.do(readBodyHandler(t, string(data))) | |
| st.wantWindowUpdate(0, uint32(len(data)+1+len(pad))) | |
| st.wantWindowUpdate(1, uint32(len(data)+1+len(pad))) | |
| } | |
| func TestServer_Send_GoAway_After_Bogus_WindowUpdate(t *testing.T) { | |
| synctestTest(t, testServer_Send_GoAway_After_Bogus_WindowUpdate) | |
| } | |
| func testServer_Send_GoAway_After_Bogus_WindowUpdate(t testing.TB) { | |
| st := newServerTester(t, nil) | |
| defer st.Close() | |
| st.greet() | |
| if err := st.fr.WriteWindowUpdate(0, 1<<31-1); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantGoAway(0, ErrCodeFlowControl) | |
| } | |
| func TestServer_Send_RstStream_After_Bogus_WindowUpdate(t *testing.T) { | |
| synctestTest(t, testServer_Send_RstStream_After_Bogus_WindowUpdate) | |
| } | |
| func testServer_Send_RstStream_After_Bogus_WindowUpdate(t testing.TB) { | |
| inHandler := make(chan bool) | |
| blockHandler := make(chan bool) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| inHandler <- true | |
| <-blockHandler | |
| }) | |
| defer st.Close() | |
| defer close(blockHandler) | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, // keep it open | |
| EndHeaders: true, | |
| }) | |
| <-inHandler | |
| // Send a bogus window update: | |
| if err := st.fr.WriteWindowUpdate(1, 1<<31-1); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantRSTStream(1, ErrCodeFlowControl) | |
| } | |
| // testServerPostUnblock sends a hanging POST with unsent data to handler, | |
| // then runs fn once in the handler, and verifies that the error returned from | |
| // handler is acceptable. It fails if takes over 5 seconds for handler to exit. | |
| func testServerPostUnblock(t testing.TB, | |
| handler func(http.ResponseWriter, *http.Request) error, | |
| fn func(*serverTester), | |
| checkErr func(error), | |
| otherHeaders ...string) { | |
| inHandler := make(chan bool) | |
| errc := make(chan error, 1) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| inHandler <- true | |
| errc <- handler(w, r) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(append([]string{":method", "POST"}, otherHeaders...)...), | |
| EndStream: false, // keep it open | |
| EndHeaders: true, | |
| }) | |
| <-inHandler | |
| fn(st) | |
| err := <-errc | |
| if checkErr != nil { | |
| checkErr(err) | |
| } | |
| } | |
| func TestServer_RSTStream_Unblocks_Read(t *testing.T) { | |
| synctestTest(t, testServer_RSTStream_Unblocks_Read) | |
| } | |
| func testServer_RSTStream_Unblocks_Read(t testing.TB) { | |
| testServerPostUnblock(t, | |
| func(w http.ResponseWriter, r *http.Request) (err error) { | |
| _, err = r.Body.Read(make([]byte, 1)) | |
| return | |
| }, | |
| func(st *serverTester) { | |
| if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil { | |
| t.Fatal(err) | |
| } | |
| }, | |
| func(err error) { | |
| want := StreamError{StreamID: 0x1, Code: 0x8} | |
| if !reflect.DeepEqual(err, want) { | |
| t.Errorf("Read error = %v; want %v", err, want) | |
| } | |
| }, | |
| ) | |
| } | |
| func TestServer_RSTStream_Unblocks_Header_Write(t *testing.T) { | |
| // Run this test a bunch, because it doesn't always | |
| // deadlock. But with a bunch, it did. | |
| n := 50 | |
| if testing.Short() { | |
| n = 5 | |
| } | |
| for i := 0; i < n; i++ { | |
| synctestTest(t, testServer_RSTStream_Unblocks_Header_Write) | |
| } | |
| } | |
| func testServer_RSTStream_Unblocks_Header_Write(t testing.TB) { | |
| inHandler := make(chan bool, 1) | |
| unblockHandler := make(chan bool, 1) | |
| headerWritten := make(chan bool, 1) | |
| wroteRST := make(chan bool, 1) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| inHandler <- true | |
| <-wroteRST | |
| w.Header().Set("foo", "bar") | |
| w.WriteHeader(200) | |
| w.(http.Flusher).Flush() | |
| headerWritten <- true | |
| <-unblockHandler | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, // keep it open | |
| EndHeaders: true, | |
| }) | |
| <-inHandler | |
| if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil { | |
| t.Fatal(err) | |
| } | |
| wroteRST <- true | |
| synctest.Wait() | |
| <-headerWritten | |
| unblockHandler <- true | |
| } | |
| func TestServer_DeadConn_Unblocks_Read(t *testing.T) { | |
| synctestTest(t, testServer_DeadConn_Unblocks_Read) | |
| } | |
| func testServer_DeadConn_Unblocks_Read(t testing.TB) { | |
| testServerPostUnblock(t, | |
| func(w http.ResponseWriter, r *http.Request) (err error) { | |
| _, err = r.Body.Read(make([]byte, 1)) | |
| return | |
| }, | |
| func(st *serverTester) { st.cc.Close() }, | |
| func(err error) { | |
| if err == nil { | |
| t.Error("unexpected nil error from Request.Body.Read") | |
| } | |
| }, | |
| ) | |
| } | |
| var blockUntilClosed = func(w http.ResponseWriter, r *http.Request) error { | |
| <-w.(http.CloseNotifier).CloseNotify() | |
| return nil | |
| } | |
| func TestServer_CloseNotify_After_RSTStream(t *testing.T) { | |
| synctestTest(t, testServer_CloseNotify_After_RSTStream) | |
| } | |
| func testServer_CloseNotify_After_RSTStream(t testing.TB) { | |
| testServerPostUnblock(t, blockUntilClosed, func(st *serverTester) { | |
| if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil { | |
| t.Fatal(err) | |
| } | |
| }, nil) | |
| } | |
| func TestServer_CloseNotify_After_ConnClose(t *testing.T) { | |
| synctestTest(t, testServer_CloseNotify_After_ConnClose) | |
| } | |
| func testServer_CloseNotify_After_ConnClose(t testing.TB) { | |
| testServerPostUnblock(t, blockUntilClosed, func(st *serverTester) { st.cc.Close() }, nil) | |
| } | |
| // that CloseNotify unblocks after a stream error due to the client's | |
| // problem that's unrelated to them explicitly canceling it (which is | |
| // TestServer_CloseNotify_After_RSTStream above) | |
| func TestServer_CloseNotify_After_StreamError(t *testing.T) { | |
| synctestTest(t, testServer_CloseNotify_After_StreamError) | |
| } | |
| func testServer_CloseNotify_After_StreamError(t testing.TB) { | |
| testServerPostUnblock(t, blockUntilClosed, func(st *serverTester) { | |
| // data longer than declared Content-Length => stream error | |
| st.writeData(1, true, []byte("1234")) | |
| }, nil, "content-length", "3") | |
| } | |
| func TestServer_StateTransitions(t *testing.T) { synctestTest(t, testServer_StateTransitions) } | |
| func testServer_StateTransitions(t testing.TB) { | |
| var st *serverTester | |
| inHandler := make(chan bool) | |
| writeData := make(chan bool) | |
| leaveHandler := make(chan bool) | |
| st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| inHandler <- true | |
| if !st.streamExists(1) { | |
| t.Errorf("stream 1 does not exist in handler") | |
| } | |
| if got, want := st.streamState(1), StateOpen; got != want { | |
| t.Errorf("in handler, state is %v; want %v", got, want) | |
| } | |
| writeData <- true | |
| if n, err := r.Body.Read(make([]byte, 1)); n != 0 || err != io.EOF { | |
| t.Errorf("body read = %d, %v; want 0, EOF", n, err) | |
| } | |
| if got, want := st.streamState(1), StateHalfClosedRemote; got != want { | |
| t.Errorf("in handler, state is %v; want %v", got, want) | |
| } | |
| <-leaveHandler | |
| }) | |
| st.greet() | |
| if st.streamExists(1) { | |
| t.Fatal("stream 1 should be empty") | |
| } | |
| if got := st.streamState(1); got != StateIdle { | |
| t.Fatalf("stream 1 should be idle; got %v", got) | |
| } | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, // keep it open | |
| EndHeaders: true, | |
| }) | |
| <-inHandler | |
| <-writeData | |
| st.writeData(1, true, nil) | |
| leaveHandler <- true | |
| 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) | |
| } | |
| if st.streamExists(1) { | |
| t.Fatal("at end, stream 1 should be gone") | |
| } | |
| } | |
| // test HEADERS w/o EndHeaders + another HEADERS (should get rejected) | |
| func TestServer_Rejects_HeadersNoEnd_Then_Headers(t *testing.T) { | |
| synctestTest(t, testServer_Rejects_HeadersNoEnd_Then_Headers) | |
| } | |
| func testServer_Rejects_HeadersNoEnd_Then_Headers(t testing.TB) { | |
| 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) { | |
| synctestTest(t, testServer_Rejects_HeadersNoEnd_Then_Ping) | |
| } | |
| func testServer_Rejects_HeadersNoEnd_Then_Ping(t testing.TB) { | |
| 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) { | |
| synctestTest(t, testServer_Rejects_HeadersEnd_Then_Continuation) | |
| } | |
| func testServer_Rejects_HeadersEnd_Then_Continuation(t testing.TB) { | |
| 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, EncodeHeaderRaw(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) { | |
| synctestTest(t, testServer_Rejects_HeadersNoEnd_Then_ContinuationWrongStream) | |
| } | |
| func testServer_Rejects_HeadersNoEnd_Then_ContinuationWrongStream(t testing.TB) { | |
| st := newServerTesterForError(t) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: false, | |
| }) | |
| if err := st.fr.WriteContinuation(3, true, EncodeHeaderRaw(t, "foo", "bar")); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantGoAway(0, ErrCodeProtocol) | |
| } | |
| // No HEADERS on stream 0. | |
| func TestServer_Rejects_Headers0(t *testing.T) { synctestTest(t, testServer_Rejects_Headers0) } | |
| func testServer_Rejects_Headers0(t testing.TB) { | |
| 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) { | |
| synctestTest(t, testServer_Rejects_Continuation0) | |
| } | |
| func testServer_Rejects_Continuation0(t testing.TB) { | |
| 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) { synctestTest(t, testServer_Rejects_Priority0) } | |
| func testServer_Rejects_Priority0(t testing.TB) { | |
| st := newServerTesterForError(t) | |
| st.fr.AllowIllegalWrites = true | |
| st.writePriority(0, PriorityParam{StreamDep: 1}) | |
| st.wantGoAway(0, ErrCodeProtocol) | |
| } | |
| // PRIORITY_UPDATE only accepts non-zero ID for the prioritized stream ID in | |
| // its payload. | |
| func TestServer_Rejects_PriorityUpdate0(t *testing.T) { | |
| synctestTest(t, testServer_Rejects_PriorityUpdate0) | |
| } | |
| func testServer_Rejects_PriorityUpdate0(t testing.TB) { | |
| st := newServerTesterForError(t) | |
| st.fr.AllowIllegalWrites = true | |
| st.writePriorityUpdate(0, "") | |
| st.wantGoAway(0, ErrCodeProtocol) | |
| } | |
| // PRIORITY_UPDATE with unparsable priority parameters may be rejected. | |
| func TestServer_Rejects_PriorityUpdateUnparsable(t *testing.T) { | |
| synctestTest(t, testServer_Rejects_PriorityUnparsable) | |
| } | |
| func testServer_Rejects_PriorityUnparsable(t testing.TB) { | |
| st := newServerTester(t, nil, func(s *Server) { | |
| s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218 | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writePriorityUpdate(1, "Invalid dictionary: ((((") | |
| st.wantRSTStream(1, ErrCodeProtocol) | |
| } | |
| // No HEADERS frame with a self-dependence. | |
| func TestServer_Rejects_HeadersSelfDependence(t *testing.T) { | |
| synctestTest(t, testServer_Rejects_HeadersSelfDependence) | |
| } | |
| func testServer_Rejects_HeadersSelfDependence(t testing.TB) { | |
| testServerRejectsStream(t, ErrCodeProtocol, func(st *serverTester) { | |
| st.fr.AllowIllegalWrites = true | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| Priority: PriorityParam{StreamDep: 1}, | |
| }) | |
| }) | |
| } | |
| // No PRIORITY frame with a self-dependence. | |
| func TestServer_Rejects_PrioritySelfDependence(t *testing.T) { | |
| synctestTest(t, testServer_Rejects_PrioritySelfDependence) | |
| } | |
| func testServer_Rejects_PrioritySelfDependence(t testing.TB) { | |
| testServerRejectsStream(t, ErrCodeProtocol, func(st *serverTester) { | |
| st.fr.AllowIllegalWrites = true | |
| st.writePriority(1, PriorityParam{StreamDep: 1}) | |
| }) | |
| } | |
| func TestServer_Rejects_PushPromise(t *testing.T) { synctestTest(t, testServer_Rejects_PushPromise) } | |
| func testServer_Rejects_PushPromise(t testing.TB) { | |
| st := newServerTesterForError(t) | |
| pp := PushPromiseParam{ | |
| StreamID: 1, | |
| PromiseID: 3, | |
| } | |
| 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 | |
| // error code after a client sends a bogus request. | |
| func testServerRejectsStream(t testing.TB, code ErrCode, writeReq func(*serverTester)) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}) | |
| defer st.Close() | |
| st.greet() | |
| writeReq(st) | |
| st.wantRSTStream(1, code) | |
| } | |
| // testServerRequest sets up an idle HTTP/2 connection and lets you | |
| // write a single request with writeReq, and then verify that the | |
| // *http.Request is built correctly in checkReq. | |
| func testServerRequest(t testing.TB, writeReq func(*serverTester), checkReq func(*http.Request)) { | |
| gotReq := make(chan bool, 1) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| if r.Body == nil { | |
| t.Fatal("nil Body") | |
| } | |
| checkReq(r) | |
| gotReq <- true | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| writeReq(st) | |
| <-gotReq | |
| } | |
| func getSlash(st *serverTester) { st.bodylessReq1() } | |
| func TestServer_Response_NoData(t *testing.T) { synctestTest(t, testServer_Response_NoData) } | |
| func testServer_Response_NoData(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| // Nothing. | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| }) | |
| }) | |
| } | |
| func TestServer_Response_NoData_Header_FooBar(t *testing.T) { | |
| synctestTest(t, testServer_Response_NoData_Header_FooBar) | |
| } | |
| func testServer_Response_NoData_Header_FooBar(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("Foo-Bar", "some-value") | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| header: http.Header{ | |
| ":status": []string{"200"}, | |
| "foo-bar": []string{"some-value"}, | |
| "content-length": []string{"0"}, | |
| }, | |
| }) | |
| }) | |
| } | |
| // Reject content-length headers containing a sign. | |
| // See https://golang.org/issue/39017 | |
| func TestServerIgnoresContentLengthSignWhenWritingChunks(t *testing.T) { | |
| synctestTest(t, testServerIgnoresContentLengthSignWhenWritingChunks) | |
| } | |
| func testServerIgnoresContentLengthSignWhenWritingChunks(t testing.TB) { | |
| tests := []struct { | |
| name string | |
| cl string | |
| wantCL string | |
| }{ | |
| { | |
| name: "proper content-length", | |
| cl: "3", | |
| wantCL: "3", | |
| }, | |
| { | |
| name: "ignore cl with plus sign", | |
| cl: "+3", | |
| wantCL: "0", | |
| }, | |
| { | |
| name: "ignore cl with minus sign", | |
| cl: "-3", | |
| wantCL: "0", | |
| }, | |
| { | |
| name: "max int64, for safe uint64->int64 conversion", | |
| cl: "9223372036854775807", | |
| wantCL: "9223372036854775807", | |
| }, | |
| { | |
| name: "overflows int64, so ignored", | |
| cl: "9223372036854775808", | |
| wantCL: "0", | |
| }, | |
| } | |
| for _, tt := range tests { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("content-length", tt.cl) | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| header: http.Header{ | |
| ":status": []string{"200"}, | |
| "content-length": []string{tt.wantCL}, | |
| }, | |
| }) | |
| }) | |
| } | |
| } | |
| // Reject content-length headers containing a sign. | |
| // See https://golang.org/issue/39017 | |
| func TestServerRejectsContentLengthWithSignNewRequests(t *testing.T) { | |
| tests := []struct { | |
| name string | |
| cl string | |
| wantCL int64 | |
| }{ | |
| { | |
| name: "proper content-length", | |
| cl: "3", | |
| wantCL: 3, | |
| }, | |
| { | |
| name: "ignore cl with plus sign", | |
| cl: "+3", | |
| wantCL: 0, | |
| }, | |
| { | |
| name: "ignore cl with minus sign", | |
| cl: "-3", | |
| wantCL: 0, | |
| }, | |
| { | |
| name: "max int64, for safe uint64->int64 conversion", | |
| cl: "9223372036854775807", | |
| wantCL: 9223372036854775807, | |
| }, | |
| { | |
| name: "overflows int64, so ignored", | |
| cl: "9223372036854775808", | |
| wantCL: 0, | |
| }, | |
| } | |
| for _, tt := range tests { | |
| tt := tt | |
| synctestSubtest(t, tt.name, func(t testing.TB) { | |
| writeReq := func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader("content-length", tt.cl), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, false, []byte("")) | |
| } | |
| checkReq := func(r *http.Request) { | |
| if r.ContentLength != tt.wantCL { | |
| t.Fatalf("Got: %d\nWant: %d", r.ContentLength, tt.wantCL) | |
| } | |
| } | |
| testServerRequest(t, writeReq, checkReq) | |
| }) | |
| } | |
| } | |
| func TestServer_Response_Data_Sniff_DoesntOverride(t *testing.T) { | |
| synctestTest(t, testServer_Response_Data_Sniff_DoesntOverride) | |
| } | |
| func testServer_Response_Data_Sniff_DoesntOverride(t testing.TB) { | |
| const msg = "<html>this is HTML." | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("Content-Type", "foo/bar") | |
| io.WriteString(w, msg) | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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), | |
| }) | |
| }) | |
| } | |
| func TestServer_Response_TransferEncoding_chunked(t *testing.T) { | |
| synctestTest(t, testServer_Response_TransferEncoding_chunked) | |
| } | |
| func testServer_Response_TransferEncoding_chunked(t testing.TB) { | |
| const msg = "hi" | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("Transfer-Encoding", "chunked") // should be stripped | |
| io.WriteString(w, msg) | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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))}, | |
| }, | |
| }) | |
| }) | |
| } | |
| // Header accessed only after the initial write. | |
| func TestServer_Response_Data_IgnoreHeaderAfterWrite_After(t *testing.T) { | |
| synctestTest(t, testServer_Response_Data_IgnoreHeaderAfterWrite_After) | |
| } | |
| func testServer_Response_Data_IgnoreHeaderAfterWrite_After(t testing.TB) { | |
| const msg = "<html>this is HTML." | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| io.WriteString(w, msg) | |
| w.Header().Set("foo", "should be ignored") | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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))}, | |
| }, | |
| }) | |
| }) | |
| } | |
| // Header accessed before the initial write and later mutated. | |
| func TestServer_Response_Data_IgnoreHeaderAfterWrite_Overwrite(t *testing.T) { | |
| synctestTest(t, testServer_Response_Data_IgnoreHeaderAfterWrite_Overwrite) | |
| } | |
| func testServer_Response_Data_IgnoreHeaderAfterWrite_Overwrite(t testing.TB) { | |
| const msg = "<html>this is HTML." | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("foo", "proper value") | |
| io.WriteString(w, msg) | |
| w.Header().Set("foo", "should be ignored") | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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))}, | |
| }, | |
| }) | |
| }) | |
| } | |
| func TestServer_Response_Data_SniffLenType(t *testing.T) { | |
| synctestTest(t, testServer_Response_Data_SniffLenType) | |
| } | |
| func testServer_Response_Data_SniffLenType(t testing.TB) { | |
| const msg = "<html>this is HTML." | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| io.WriteString(w, msg) | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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), | |
| }) | |
| }) | |
| } | |
| func TestServer_Response_Header_Flush_MidWrite(t *testing.T) { | |
| synctestTest(t, testServer_Response_Header_Flush_MidWrite) | |
| } | |
| func testServer_Response_Header_Flush_MidWrite(t testing.TB) { | |
| const msg = "<html>this is HTML" | |
| const msg2 = ", and this is the next chunk" | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| io.WriteString(w, msg) | |
| w.(http.Flusher).Flush() | |
| io.WriteString(w, msg2) | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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), | |
| }) | |
| }) | |
| } | |
| func TestServer_Response_LargeWrite(t *testing.T) { synctestTest(t, testServer_Response_LargeWrite) } | |
| func testServer_Response_LargeWrite(t testing.TB) { | |
| const size = 1 << 20 | |
| const maxFrameSize = 16 << 10 | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| n, err := w.Write(bytes.Repeat([]byte("a"), size)) | |
| if err != nil { | |
| return fmt.Errorf("Write error: %v", err) | |
| } | |
| if n != size { | |
| return fmt.Errorf("wrong size %d from Write", n) | |
| } | |
| return nil | |
| }, func(st *serverTester) { | |
| if err := st.fr.WriteSettings( | |
| Setting{SettingInitialWindowSize, 0}, | |
| Setting{SettingMaxFrameSize, maxFrameSize}, | |
| ); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantSettingsAck() | |
| getSlash(st) // make the single request | |
| // Give the handler quota to write: | |
| if err := st.fr.WriteWindowUpdate(1, size); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Give the handler quota to write to connection-level | |
| // window as well | |
| if err := st.fr.WriteWindowUpdate(0, size); err != nil { | |
| t.Fatal(err) | |
| } | |
| 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 := readFrame[*DataFrame](t, st) | |
| bytes += len(df.Data()) | |
| frames++ | |
| for _, b := range df.Data() { | |
| if b != 'a' { | |
| t.Fatal("non-'a' byte seen in DATA") | |
| } | |
| } | |
| if df.StreamEnded() { | |
| break | |
| } | |
| } | |
| if bytes != size { | |
| t.Errorf("Got %d bytes; want %d", bytes, size) | |
| } | |
| if want := int(size / maxFrameSize); frames < want || frames > want*2 { | |
| t.Errorf("Got %d frames; want %d", frames, size) | |
| } | |
| }) | |
| } | |
| // Test that the handler can't write more than the client allows | |
| func TestServer_Response_LargeWrite_FlowControlled(t *testing.T) { | |
| synctestTest(t, testServer_Response_LargeWrite_FlowControlled) | |
| } | |
| func testServer_Response_LargeWrite_FlowControlled(t testing.TB) { | |
| // Make these reads. Before each read, the client adds exactly enough | |
| // flow-control to satisfy the read. Numbers chosen arbitrarily. | |
| reads := []int{123, 1, 13, 127} | |
| size := 0 | |
| for _, n := range reads { | |
| size += n | |
| } | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.(http.Flusher).Flush() | |
| n, err := w.Write(bytes.Repeat([]byte("a"), size)) | |
| if err != nil { | |
| return fmt.Errorf("Write error: %v", err) | |
| } | |
| if n != size { | |
| return fmt.Errorf("wrong size %d from Write", n) | |
| } | |
| return nil | |
| }, func(st *serverTester) { | |
| // Set the window size to something explicit for this test. | |
| // It's also how much initial data we expect. | |
| if err := st.fr.WriteSettings(Setting{SettingInitialWindowSize, uint32(reads[0])}); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantSettingsAck() | |
| getSlash(st) // make the single request | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| }) | |
| st.wantData(wantData{ | |
| streamID: 1, | |
| endStream: false, | |
| size: reads[0], | |
| }) | |
| for i, quota := range reads[1:] { | |
| if err := st.fr.WriteWindowUpdate(1, uint32(quota)); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantData(wantData{ | |
| streamID: 1, | |
| endStream: i == len(reads[1:])-1, | |
| size: quota, | |
| }) | |
| } | |
| }) | |
| } | |
| // Test that the handler blocked in a Write is unblocked if the server sends a RST_STREAM. | |
| func TestServer_Response_RST_Unblocks_LargeWrite(t *testing.T) { | |
| synctestTest(t, testServer_Response_RST_Unblocks_LargeWrite) | |
| } | |
| func testServer_Response_RST_Unblocks_LargeWrite(t testing.TB) { | |
| const size = 1 << 20 | |
| const maxFrameSize = 16 << 10 | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.(http.Flusher).Flush() | |
| _, err := w.Write(bytes.Repeat([]byte("a"), size)) | |
| if err == nil { | |
| return errors.New("unexpected nil error from Write in handler") | |
| } | |
| return nil | |
| }, func(st *serverTester) { | |
| if err := st.fr.WriteSettings( | |
| Setting{SettingInitialWindowSize, 0}, | |
| Setting{SettingMaxFrameSize, maxFrameSize}, | |
| ); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantSettingsAck() | |
| getSlash(st) // make the single request | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| }) | |
| if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil { | |
| t.Fatal(err) | |
| } | |
| }) | |
| } | |
| func TestServer_Response_Empty_Data_Not_FlowControlled(t *testing.T) { | |
| synctestTest(t, testServer_Response_Empty_Data_Not_FlowControlled) | |
| } | |
| func testServer_Response_Empty_Data_Not_FlowControlled(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.(http.Flusher).Flush() | |
| // Nothing; send empty DATA | |
| return nil | |
| }, func(st *serverTester) { | |
| // Handler gets no data quota: | |
| if err := st.fr.WriteSettings(Setting{SettingInitialWindowSize, 0}); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.wantSettingsAck() | |
| getSlash(st) // make the single request | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| }) | |
| st.wantData(wantData{ | |
| streamID: 1, | |
| endStream: true, | |
| size: 0, | |
| }) | |
| }) | |
| } | |
| func TestServer_Response_Automatic100Continue(t *testing.T) { | |
| synctestTest(t, testServer_Response_Automatic100Continue) | |
| } | |
| func testServer_Response_Automatic100Continue(t testing.TB) { | |
| const msg = "foo" | |
| const reply = "bar" | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| if v := r.Header.Get("Expect"); v != "" { | |
| t.Errorf("Expect header = %q; want empty", v) | |
| } | |
| buf := make([]byte, len(msg)) | |
| // This read should trigger the 100-continue being sent. | |
| if n, err := io.ReadFull(r.Body, buf); err != nil || n != len(msg) || string(buf) != msg { | |
| return fmt.Errorf("ReadFull = %q, %v; want %q, nil", buf[:n], err, msg) | |
| } | |
| _, err := io.WriteString(w, reply) | |
| return err | |
| }, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":method", "POST", "expect", "100-Continue"), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| 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)) | |
| 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), | |
| }) | |
| }) | |
| } | |
| func TestServer_HandlerWriteErrorOnDisconnect(t *testing.T) { | |
| synctestTest(t, testServer_HandlerWriteErrorOnDisconnect) | |
| } | |
| func testServer_HandlerWriteErrorOnDisconnect(t testing.TB) { | |
| errc := make(chan error, 1) | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| p := []byte("some data.\n") | |
| for { | |
| _, err := w.Write(p) | |
| if err != nil { | |
| errc <- err | |
| return nil | |
| } | |
| } | |
| }, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| }) | |
| // Close the connection and wait for the handler to (hopefully) notice. | |
| st.cc.Close() | |
| _ = <-errc | |
| }) | |
| } | |
| func TestServer_Rejects_Too_Many_Streams(t *testing.T) { | |
| synctestTest(t, testServer_Rejects_Too_Many_Streams) | |
| } | |
| func testServer_Rejects_Too_Many_Streams(t testing.TB) { | |
| st := newServerTester(t, nil) | |
| st.greet() | |
| nextStreamID := uint32(1) | |
| streamID := func() uint32 { | |
| defer func() { nextStreamID += 2 }() | |
| return nextStreamID | |
| } | |
| sendReq := func(id uint32) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: id, | |
| BlockFragment: st.encodeHeader( | |
| ":path", fmt.Sprintf("/%v", id), | |
| ), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| } | |
| var calls []*serverHandlerCall | |
| for i := 0; i < DefaultMaxStreams; i++ { | |
| sendReq(streamID()) | |
| calls = append(calls, st.nextHandlerCall()) | |
| } | |
| // And this one should cross the limit: | |
| // (It's also sent as a CONTINUATION, to verify we still track the decoder context, | |
| // even if we're rejecting it) | |
| rejectID := streamID() | |
| headerBlock := st.encodeHeader(":path", fmt.Sprintf("/%v", rejectID)) | |
| frag1, frag2 := headerBlock[:3], headerBlock[3:] | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: rejectID, | |
| BlockFragment: frag1, | |
| EndStream: true, | |
| EndHeaders: false, // CONTINUATION coming | |
| }) | |
| if err := st.fr.WriteContinuation(rejectID, true, frag2); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.sync() | |
| st.wantRSTStream(rejectID, ErrCodeProtocol) | |
| // But let a handler finish: | |
| calls[0].exit() | |
| st.sync() | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| }) | |
| // And now another stream should be able to start: | |
| goodID := streamID() | |
| sendReq(goodID) | |
| call := st.nextHandlerCall() | |
| if got, want := call.req.URL.Path, fmt.Sprintf("/%d", goodID); got != want { | |
| t.Errorf("Got request for %q, want %q", got, want) | |
| } | |
| } | |
| // So many response headers that the server needs to use CONTINUATION frames: | |
| func TestServer_Response_ManyHeaders_With_Continuation(t *testing.T) { | |
| synctestTest(t, testServer_Response_ManyHeaders_With_Continuation) | |
| } | |
| func testServer_Response_ManyHeaders_With_Continuation(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| h := w.Header() | |
| for i := 0; i < 5000; i++ { | |
| h.Set(fmt.Sprintf("x-header-%d", i), fmt.Sprintf("x-value-%d", i)) | |
| } | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| hf := readFrame[*HeadersFrame](t, st) | |
| if hf.HeadersEnded() { | |
| t.Fatal("got unwanted END_HEADERS flag") | |
| } | |
| n := 0 | |
| for { | |
| n++ | |
| cf := readFrame[*ContinuationFrame](t, st) | |
| if cf.HeadersEnded() { | |
| break | |
| } | |
| } | |
| if n < 5 { | |
| t.Errorf("Only got %d CONTINUATION frames; expected 5+ (currently 6)", n) | |
| } | |
| }) | |
| } | |
| // This previously crashed (reported by Mathieu Lonjaret as observed | |
| // while using Camlistore) because we got a DATA frame from the client | |
| // after the handler exited and our logic at the time was wrong, | |
| // keeping a stream in the map in stateClosed, which tickled an | |
| // invariant check later when we tried to remove that stream (via | |
| // defer sc.closeAllStreamsOnConnClose) when the serverConn serve loop | |
| // ended. | |
| func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) { | |
| synctestTest(t, testServer_NoCrash_HandlerClose_Then_ClientClose) | |
| } | |
| func testServer_NoCrash_HandlerClose_Then_ClientClose(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| // nothing | |
| return nil | |
| }, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: false, // DATA is coming | |
| EndHeaders: true, | |
| }) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| }) | |
| // Sent when the a Handler closes while a client has | |
| // indicated it's still sending DATA: | |
| st.wantRSTStream(1, ErrCodeNo) | |
| // Now the handler has ended, so it's ended its | |
| // stream, but the client hasn't closed its side | |
| // (stateClosedLocal). So send more data and verify | |
| // it doesn't crash with an internal invariant panic, like | |
| // it did before. | |
| st.writeData(1, true, []byte("foo")) | |
| // Sent after a peer sends data anyway (admittedly the | |
| // previous RST_STREAM might've still been in-flight), | |
| // but they'll get the more friendly 'cancel' code | |
| // first. | |
| st.wantRSTStream(1, ErrCodeStreamClosed) | |
| // We should have our flow control bytes back, | |
| // since the handler didn't get them. | |
| st.wantConnFlowControlConsumed(0) | |
| // Set up a bunch of machinery to record the panic we saw | |
| // previously. | |
| var ( | |
| panMu sync.Mutex | |
| panicVal interface{} | |
| ) | |
| SetTestHookOnPanic(t, func(sc *ServerConn, pv interface{}) bool { | |
| panMu.Lock() | |
| panicVal = pv | |
| panMu.Unlock() | |
| return true | |
| }) | |
| // Now force the serve loop to end, via closing the connection. | |
| st.cc.Close() | |
| synctest.Wait() | |
| panMu.Lock() | |
| got := panicVal | |
| panMu.Unlock() | |
| if got != nil { | |
| t.Errorf("Got panic: %v", got) | |
| } | |
| }) | |
| } | |
| 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, version uint16) { | |
| synctestTest(t, func(t testing.TB) { | |
| 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 version to 1.0 | |
| state.Version = version | |
| }) | |
| defer st.Close() | |
| st.wantGoAway(0, ErrCodeInadequateSecurity) | |
| }) | |
| } | |
| func TestServer_Rejects_TLSBadCipher(t *testing.T) { synctestTest(t, testServer_Rejects_TLSBadCipher) } | |
| func testServer_Rejects_TLSBadCipher(t testing.TB) { | |
| st := newServerTester(t, nil, func(state *tls.ConnectionState) { | |
| state.Version = tls.VersionTLS12 | |
| state.CipherSuite = tls.TLS_RSA_WITH_RC4_128_SHA | |
| }) | |
| defer st.Close() | |
| st.wantGoAway(0, ErrCodeInadequateSecurity) | |
| } | |
| func TestServer_Advertises_Common_Cipher(t *testing.T) { | |
| synctestTest(t, testServer_Advertises_Common_Cipher) | |
| } | |
| func testServer_Advertises_Common_Cipher(t testing.TB) { | |
| 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 | |
| }) | |
| // 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() | |
| req, err := http.NewRequest("GET", ts.URL, nil) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| res, err := tr.RoundTrip(req) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| res.Body.Close() | |
| } | |
| // testServerResponse sets up an idle HTTP/2 connection. The client function should | |
| // write a single request that must be handled by the handler. | |
| func testServerResponse(t testing.TB, | |
| handler func(http.ResponseWriter, *http.Request) error, | |
| client func(*serverTester), | |
| ) { | |
| errc := make(chan error, 1) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| if r.Body == nil { | |
| t.Fatal("nil Body") | |
| } | |
| err := handler(w, r) | |
| select { | |
| case errc <- err: | |
| default: | |
| t.Errorf("unexpected duplicate request") | |
| } | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| client(st) | |
| if err := <-errc; err != nil { | |
| t.Fatalf("Error in handler: %v", err) | |
| } | |
| } | |
| // readBodyHandler returns an http Handler func that reads len(want) | |
| // bytes from r.Body and fails t if the contents read were not | |
| // the value of want. | |
| func readBodyHandler(t testing.TB, want string) func(w http.ResponseWriter, r *http.Request) { | |
| return func(w http.ResponseWriter, r *http.Request) { | |
| buf := make([]byte, len(want)) | |
| _, err := io.ReadFull(r.Body, buf) | |
| if err != nil { | |
| t.Error(err) | |
| return | |
| } | |
| if string(buf) != want { | |
| t.Errorf("read %q; want %q", buf, want) | |
| } | |
| } | |
| } | |
| func TestServer_MaxDecoderHeaderTableSize(t *testing.T) { | |
| synctestTest(t, testServer_MaxDecoderHeaderTableSize) | |
| } | |
| func testServer_MaxDecoderHeaderTableSize(t testing.TB) { | |
| wantHeaderTableSize := uint32(InitialHeaderTableSize * 2) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) { | |
| s.MaxDecoderHeaderTableSize = wantHeaderTableSize | |
| }) | |
| defer st.Close() | |
| var advHeaderTableSize *uint32 | |
| st.greetAndCheckSettings(func(s Setting) error { | |
| switch s.ID { | |
| case SettingHeaderTableSize: | |
| advHeaderTableSize = &s.Val | |
| } | |
| return nil | |
| }) | |
| if advHeaderTableSize == nil { | |
| t.Errorf("server didn't advertise a header table size") | |
| } else if got, want := *advHeaderTableSize, wantHeaderTableSize; got != want { | |
| t.Errorf("server advertised a header table size of %d, want %d", got, want) | |
| } | |
| } | |
| func TestServer_MaxEncoderHeaderTableSize(t *testing.T) { | |
| synctestTest(t, testServer_MaxEncoderHeaderTableSize) | |
| } | |
| func testServer_MaxEncoderHeaderTableSize(t testing.TB) { | |
| wantHeaderTableSize := uint32(InitialHeaderTableSize / 2) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) { | |
| s.MaxEncoderHeaderTableSize = wantHeaderTableSize | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| if got, want := st.sc.TestHPACKEncoder().MaxDynamicTableSize(), wantHeaderTableSize; got != want { | |
| t.Errorf("server encoder is using a header table size of %d, want %d", got, want) | |
| } | |
| } | |
| // Issue 12843 | |
| func TestServerDoS_MaxHeaderListSize(t *testing.T) { synctestTest(t, testServerDoS_MaxHeaderListSize) } | |
| func testServerDoS_MaxHeaderListSize(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}) | |
| defer st.Close() | |
| // shake hands | |
| frameSize := DefaultMaxReadFrameSize | |
| var advHeaderListSize *uint32 | |
| st.greetAndCheckSettings(func(s Setting) error { | |
| switch s.ID { | |
| case SettingMaxFrameSize: | |
| if s.Val < MinMaxFrameSize { | |
| frameSize = MinMaxFrameSize | |
| } else if s.Val > MaxFrameSize { | |
| frameSize = MaxFrameSize | |
| } else { | |
| frameSize = int(s.Val) | |
| } | |
| case SettingMaxHeaderListSize: | |
| advHeaderListSize = &s.Val | |
| } | |
| return nil | |
| }) | |
| if advHeaderListSize == nil { | |
| t.Errorf("server didn't advertise a max header list size") | |
| } else if *advHeaderListSize == 0 { | |
| t.Errorf("server advertised a max header list size of 0") | |
| } | |
| st.encodeHeaderField(":method", "GET") | |
| st.encodeHeaderField(":path", "/") | |
| st.encodeHeaderField(":scheme", "https") | |
| cookie := strings.Repeat("*", 4058) | |
| st.encodeHeaderField("cookie", cookie) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.headerBuf.Bytes(), | |
| EndStream: true, | |
| EndHeaders: false, | |
| }) | |
| // Capture the short encoding of a duplicate ~4K cookie, now | |
| // that we've already sent it once. | |
| st.headerBuf.Reset() | |
| st.encodeHeaderField("cookie", cookie) | |
| // Now send 1MB of it. | |
| const size = 1 << 20 | |
| b := bytes.Repeat(st.headerBuf.Bytes(), size/st.headerBuf.Len()) | |
| for len(b) > 0 { | |
| chunk := b | |
| if len(chunk) > frameSize { | |
| chunk = chunk[:frameSize] | |
| } | |
| b = b[len(chunk):] | |
| st.fr.WriteContinuation(1, len(b) == 0, chunk) | |
| } | |
| 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) { | |
| synctestTest(t, testServer_Response_Stream_With_Missing_Trailer) | |
| } | |
| func testServer_Response_Stream_With_Missing_Trailer(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("Trailer", "test-trailer") | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| }) | |
| st.wantData(wantData{ | |
| streamID: 1, | |
| endStream: true, | |
| size: 0, | |
| }) | |
| }) | |
| } | |
| func TestCompressionErrorOnWrite(t *testing.T) { synctestTest(t, testCompressionErrorOnWrite) } | |
| func testCompressionErrorOnWrite(t testing.TB) { | |
| const maxStrLen = 8 << 10 | |
| var serverConfig *http.Server | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| // No response body. | |
| }, func(s *http.Server) { | |
| serverConfig = s | |
| serverConfig.MaxHeaderBytes = maxStrLen | |
| }) | |
| st.addLogFilter("connection error: COMPRESSION_ERROR") | |
| defer st.Close() | |
| st.greet() | |
| maxAllowed := st.sc.TestFramerMaxHeaderStringLen() | |
| // Crank this up, now that we have a conn connected with the | |
| // hpack.Decoder's max string length set has been initialized | |
| // from the earlier low ~8K value. We want this higher so don't | |
| // hit the max header list size. We only want to test hitting | |
| // the max string size. | |
| serverConfig.MaxHeaderBytes = 1 << 20 | |
| // First a request with a header that's exactly the max allowed size | |
| // for the hpack compression. It's still too long for the header list | |
| // size, so we'll get the 431 error, but that keeps the compression | |
| // context still valid. | |
| hbf := st.encodeHeader("foo", strings.Repeat("a", maxAllowed)) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: hbf, | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| 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()) | |
| } | |
| if !df.StreamEnded() { | |
| t.Fatalf("expect data stream end") | |
| } | |
| // And now send one that's just one byte too big. | |
| hbf = st.encodeHeader("bar", strings.Repeat("b", maxAllowed+1)) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 3, | |
| BlockFragment: hbf, | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.wantGoAway(3, ErrCodeCompression) | |
| } | |
| func TestCompressionErrorOnClose(t *testing.T) { synctestTest(t, testCompressionErrorOnClose) } | |
| func testCompressionErrorOnClose(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| // No response body. | |
| }) | |
| st.addLogFilter("connection error: COMPRESSION_ERROR") | |
| defer st.Close() | |
| st.greet() | |
| hbf := st.encodeHeader("foo", "bar") | |
| hbf = hbf[:len(hbf)-1] // truncate one byte from the end, so hpack.Decoder.Close fails. | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: hbf, | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.wantGoAway(1, ErrCodeCompression) | |
| } | |
| // test that a server handler can read trailers from a client | |
| func TestServerReadsTrailers(t *testing.T) { synctestTest(t, testServerReadsTrailers) } | |
| func testServerReadsTrailers(t testing.TB) { | |
| const testBody = "some test body" | |
| writeReq := func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader("trailer", "Foo, Bar", "trailer", "Baz"), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, false, []byte(testBody)) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeaderRaw( | |
| "foo", "foov", | |
| "bar", "barv", | |
| "baz", "bazv", | |
| "surprise", "wasn't declared; shouldn't show up", | |
| ), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| } | |
| checkReq := func(r *http.Request) { | |
| wantTrailer := http.Header{ | |
| "Foo": nil, | |
| "Bar": nil, | |
| "Baz": nil, | |
| } | |
| if !reflect.DeepEqual(r.Trailer, wantTrailer) { | |
| t.Errorf("initial Trailer = %v; want %v", r.Trailer, wantTrailer) | |
| } | |
| slurp, err := io.ReadAll(r.Body) | |
| if string(slurp) != testBody { | |
| t.Errorf("read body %q; want %q", slurp, testBody) | |
| } | |
| if err != nil { | |
| t.Fatalf("Body slurp: %v", err) | |
| } | |
| wantTrailerAfter := http.Header{ | |
| "Foo": {"foov"}, | |
| "Bar": {"barv"}, | |
| "Baz": {"bazv"}, | |
| } | |
| if !reflect.DeepEqual(r.Trailer, wantTrailerAfter) { | |
| t.Errorf("final Trailer = %v; want %v", r.Trailer, wantTrailerAfter) | |
| } | |
| } | |
| testServerRequest(t, writeReq, checkReq) | |
| } | |
| // test that a server handler can send trailers | |
| func TestServerWritesTrailers_WithFlush(t *testing.T) { | |
| synctestTest(t, func(t testing.TB) { | |
| testServerWritesTrailers(t, true) | |
| }) | |
| } | |
| func TestServerWritesTrailers_WithoutFlush(t *testing.T) { | |
| synctestTest(t, func(t testing.TB) { | |
| testServerWritesTrailers(t, false) | |
| }) | |
| } | |
| func testServerWritesTrailers(t testing.TB, withFlush bool) { | |
| // See https://httpwg.github.io/specs/rfc7540.html#rfc.section.8.1.3 | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B") | |
| w.Header().Add("Trailer", "Server-Trailer-C") | |
| w.Header().Add("Trailer", "Transfer-Encoding, Content-Length, Trailer") // filtered | |
| // Regular headers: | |
| w.Header().Set("Foo", "Bar") | |
| w.Header().Set("Content-Length", "5") // len("Hello") | |
| io.WriteString(w, "Hello") | |
| if withFlush { | |
| w.(http.Flusher).Flush() | |
| } | |
| w.Header().Set("Server-Trailer-A", "valuea") | |
| w.Header().Set("Server-Trailer-C", "valuec") // skipping B | |
| // After a flush, random keys like Server-Surprise shouldn't show up: | |
| w.Header().Set("Server-Surpise", "surprise! this isn't predeclared!") | |
| // But we do permit promoting keys to trailers after a | |
| // flush if they start with the magic | |
| // otherwise-invalid "Trailer:" prefix: | |
| w.Header().Set("Trailer:Post-Header-Trailer", "hi1") | |
| w.Header().Set("Trailer:post-header-trailer2", "hi2") | |
| w.Header().Set("Trailer:Range", "invalid") | |
| w.Header().Set("Trailer:Foo\x01Bogus", "invalid") | |
| w.Header().Set("Transfer-Encoding", "should not be included; Forbidden by RFC 7230 4.1.2") | |
| w.Header().Set("Content-Length", "should not be included; Forbidden by RFC 7230 4.1.2") | |
| w.Header().Set("Trailer", "should not be included; Forbidden by RFC 7230 4.1.2") | |
| return nil | |
| }, func(st *serverTester) { | |
| // Ignore errors from writing invalid trailers. | |
| st.h1server.ErrorLog = log.New(io.Discard, "", 0) | |
| getSlash(st) | |
| 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) { | |
| synctestTest(t, testServerWritesUndeclaredTrailers) | |
| } | |
| func testServerWritesUndeclaredTrailers(t testing.TB) { | |
| const trailer = "Trailer-Header" | |
| const value = "hi1" | |
| ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set(http.TrailerPrefix+trailer, value) | |
| }) | |
| tr := &Transport{TLSClientConfig: tlsConfigInsecure} | |
| defer tr.CloseIdleConnections() | |
| cl := &http.Client{Transport: tr} | |
| resp, err := cl.Get(ts.URL) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| io.Copy(io.Discard, resp.Body) | |
| resp.Body.Close() | |
| if got, want := resp.Trailer.Get(trailer), value; got != want { | |
| t.Errorf("trailer %v = %q, want %q", trailer, got, want) | |
| } | |
| } | |
| // validate transmitted header field names & values | |
| // golang.org/issue/14048 | |
| func TestServerDoesntWriteInvalidHeaders(t *testing.T) { | |
| synctestTest(t, testServerDoesntWriteInvalidHeaders) | |
| } | |
| func testServerDoesntWriteInvalidHeaders(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Add("OK1", "x") | |
| w.Header().Add("Bad:Colon", "x") // colon (non-token byte) in key | |
| w.Header().Add("Bad1\x00", "x") // null in key | |
| w.Header().Add("Bad2", "x\x00y") // null in value | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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) { | |
| DisableGoroutineTracking(b) | |
| b.ReportAllocs() | |
| const msg = "Hello, world" | |
| st := newServerTesterWithRealConn(b, func(w http.ResponseWriter, r *http.Request) { | |
| io.WriteString(w, msg) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| // Give the server quota to reply. (plus it has the 64KB) | |
| if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil { | |
| b.Fatal(err) | |
| } | |
| for i := 0; i < b.N; i++ { | |
| id := 1 + uint32(i)*2 | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: id, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.wantFrameType(FrameHeaders) | |
| if df := readFrame[*DataFrame](b, st); !df.StreamEnded() { | |
| b.Fatalf("DATA didn't have END_STREAM; got %v", df) | |
| } | |
| } | |
| } | |
| func BenchmarkServerPosts(b *testing.B) { | |
| DisableGoroutineTracking(b) | |
| b.ReportAllocs() | |
| const msg = "Hello, world" | |
| 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(io.Discard, r.Body); n != 0 || err != nil { | |
| b.Errorf("Copy error; got %v, %v; want 0, nil", n, err) | |
| } | |
| io.WriteString(w, msg) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| // Give the server quota to reply. (plus it has the 64KB) | |
| if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil { | |
| b.Fatal(err) | |
| } | |
| for i := 0; i < b.N; i++ { | |
| id := 1 + uint32(i)*2 | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: id, | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.writeData(id, true, nil) | |
| st.wantFrameType(FrameHeaders) | |
| if df := readFrame[*DataFrame](b, st); !df.StreamEnded() { | |
| b.Fatalf("DATA didn't have END_STREAM; got %v", df) | |
| } | |
| } | |
| } | |
| // Send a stream of messages from server to client in separate data frames. | |
| // Brings up performance issues seen in long streams. | |
| // Created to show problem in go issue #18502 | |
| func BenchmarkServerToClientStreamDefaultOptions(b *testing.B) { | |
| benchmarkServerToClientStream(b) | |
| } | |
| // Justification for Change-Id: Iad93420ef6c3918f54249d867098f1dadfa324d8 | |
| // Expect to see memory/alloc reduction by opting in to Frame reuse with the Framer. | |
| func BenchmarkServerToClientStreamReuseFrames(b *testing.B) { | |
| benchmarkServerToClientStream(b, optFramerReuseFrames) | |
| } | |
| func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { | |
| DisableGoroutineTracking(b) | |
| b.ReportAllocs() | |
| const msgLen = 1 | |
| // default window size | |
| const windowSize = 1<<16 - 1 | |
| // next message to send from the server and for the client to expect | |
| nextMsg := func(i int) []byte { | |
| msg := make([]byte, msgLen) | |
| msg[0] = byte(i) | |
| if len(msg) != msgLen { | |
| panic("invalid test setup msg length") | |
| } | |
| return msg | |
| } | |
| 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(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 { | |
| w.Write(nextMsg(i)) | |
| w.(http.Flusher).Flush() | |
| } | |
| }, newServerOpts...) | |
| defer st.Close() | |
| st.greet() | |
| const id = uint32(1) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: id, | |
| BlockFragment: st.encodeHeader(":method", "POST"), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.writeData(id, true, nil) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| }) | |
| var pendingWindowUpdate = uint32(0) | |
| for i := 0; i < b.N; i += 1 { | |
| expected := nextMsg(i) | |
| 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(expected)) | |
| if pendingWindowUpdate >= windowSize/2 { | |
| if err := st.fr.WriteWindowUpdate(0, pendingWindowUpdate); err != nil { | |
| b.Fatal(err) | |
| } | |
| if err := st.fr.WriteWindowUpdate(id, pendingWindowUpdate); err != nil { | |
| b.Fatal(err) | |
| } | |
| pendingWindowUpdate = 0 | |
| } | |
| } | |
| st.wantData(wantData{ | |
| streamID: 1, | |
| endStream: true, | |
| }) | |
| } | |
| // go-fuzz bug, originally reported at https://github.com/bradfitz/http2/issues/53 | |
| // Verify we don't hang. | |
| func TestIssue53(t *testing.T) { synctestTest(t, testIssue53) } | |
| func testIssue53(t testing.TB) { | |
| const data = "PRI * HTTP/2.0\r\n\r\nSM" + | |
| "\r\n\r\n\x00\x00\x00\x01\ainfinfin\ad" | |
| s := &http.Server{ | |
| ErrorLog: log.New(io.MultiWriter(stderrv(), twriter{t: t}), "", log.LstdFlags), | |
| Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | |
| w.Write([]byte("hello")) | |
| }), | |
| } | |
| s2 := &Server{ | |
| MaxReadFrameSize: 1 << 16, | |
| PermitProhibitedCipherSuites: true, | |
| } | |
| c := &issue53Conn{[]byte(data), false, false} | |
| s2.ServeConn(c, &ServeConnOpts{BaseConfig: s}) | |
| if !c.closed { | |
| t.Fatal("connection is not closed") | |
| } | |
| } | |
| type issue53Conn struct { | |
| data []byte | |
| closed bool | |
| written bool | |
| } | |
| func (c *issue53Conn) Read(b []byte) (n int, err error) { | |
| if len(c.data) == 0 { | |
| return 0, io.EOF | |
| } | |
| n = copy(b, c.data) | |
| c.data = c.data[n:] | |
| return | |
| } | |
| func (c *issue53Conn) Write(b []byte) (n int, err error) { | |
| c.written = true | |
| return len(b), nil | |
| } | |
| func (c *issue53Conn) Close() error { | |
| c.closed = true | |
| return nil | |
| } | |
| func (c *issue53Conn) LocalAddr() net.Addr { | |
| return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 49706} | |
| } | |
| func (c *issue53Conn) RemoteAddr() net.Addr { | |
| return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 49706} | |
| } | |
| func (c *issue53Conn) SetDeadline(t time.Time) error { return nil } | |
| func (c *issue53Conn) SetReadDeadline(t time.Time) error { return nil } | |
| func (c *issue53Conn) SetWriteDeadline(t time.Time) error { return nil } | |
| // TestServeConnNilOpts ensures that Server.ServeConn(conn, nil) works. | |
| // | |
| // golang.org/issue/33839 | |
| func TestServeConnNilOpts(t *testing.T) { synctestTest(t, testServeConnNilOpts) } | |
| func testServeConnNilOpts(t testing.TB) { | |
| // A nil ServeConnOpts uses http.DefaultServeMux as the handler. | |
| var gotRequest string | |
| var mux http.ServeMux | |
| mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | |
| gotRequest = r.URL.Path | |
| }) | |
| SetForTest(t, &http.DefaultServeMux, &mux) | |
| srvConn, cliConn := net.Pipe() | |
| defer srvConn.Close() | |
| defer cliConn.Close() | |
| s2 := &Server{} | |
| go s2.ServeConn(srvConn, nil) | |
| fr := NewFramer(cliConn, cliConn) | |
| io.WriteString(cliConn, ClientPreface) | |
| fr.WriteSettings() | |
| fr.WriteSettingsAck() | |
| var henc hpackEncoder | |
| const reqPath = "/request" | |
| fr.WriteHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: henc.encodeHeaderRaw(t, | |
| ":method", "GET", | |
| ":path", reqPath, | |
| ":scheme", "https", | |
| ":authority", "foo.com", | |
| ), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| synctest.Wait() | |
| if got, want := gotRequest, reqPath; got != want { | |
| t.Errorf("got request: %q, want %q", got, want) | |
| } | |
| } | |
| // golang.org/issue/12895 | |
| func TestConfigureServer(t *testing.T) { synctestTest(t, testConfigureServer) } | |
| func testConfigureServer(t testing.TB) { | |
| tests := []struct { | |
| name string | |
| tlsConfig *tls.Config | |
| wantErr string | |
| }{ | |
| { | |
| name: "empty server", | |
| }, | |
| { | |
| name: "empty CipherSuites", | |
| tlsConfig: &tls.Config{}, | |
| }, | |
| { | |
| name: "bad CipherSuites but MinVersion TLS 1.3", | |
| tlsConfig: &tls.Config{ | |
| MinVersion: tls.VersionTLS13, | |
| CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, | |
| }, | |
| }, | |
| { | |
| name: "just the required cipher suite", | |
| tlsConfig: &tls.Config{ | |
| CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | |
| }, | |
| }, | |
| { | |
| name: "just the alternative required cipher suite", | |
| tlsConfig: &tls.Config{ | |
| CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, | |
| }, | |
| }, | |
| { | |
| name: "missing required cipher suite", | |
| tlsConfig: &tls.Config{ | |
| CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, | |
| }, | |
| wantErr: "is missing an HTTP/2-required", | |
| }, | |
| { | |
| name: "required after bad", | |
| tlsConfig: &tls.Config{ | |
| CipherSuites: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | |
| }, | |
| }, | |
| { | |
| name: "bad after required", | |
| tlsConfig: &tls.Config{ | |
| CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_RC4_128_SHA}, | |
| }, | |
| }, | |
| } | |
| for _, tt := range tests { | |
| srv := &http.Server{TLSConfig: tt.tlsConfig} | |
| err := ConfigureServer(srv, nil) | |
| if (err != nil) != (tt.wantErr != "") { | |
| if tt.wantErr != "" { | |
| t.Errorf("%s: success, but want error", tt.name) | |
| } else { | |
| t.Errorf("%s: unexpected error: %v", tt.name, err) | |
| } | |
| } | |
| if err != nil && tt.wantErr != "" && !strings.Contains(err.Error(), tt.wantErr) { | |
| t.Errorf("%s: err = %v; want substring %q", tt.name, err, tt.wantErr) | |
| } | |
| if err == nil && !srv.TLSConfig.PreferServerCipherSuites { | |
| t.Errorf("%s: PreferServerCipherSuite is false; want true", tt.name) | |
| } | |
| } | |
| } | |
| func TestServerNoAutoContentLengthOnHead(t *testing.T) { | |
| synctestTest(t, testServerNoAutoContentLengthOnHead) | |
| } | |
| func testServerNoAutoContentLengthOnHead(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| // No response body. (or smaller than one frame) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader(":method", "HEAD"), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| header: http.Header{ | |
| ":status": []string{"200"}, | |
| }, | |
| }) | |
| } | |
| // golang.org/issue/13495 | |
| func TestServerNoDuplicateContentType(t *testing.T) { | |
| synctestTest(t, testServerNoDuplicateContentType) | |
| } | |
| func testServerNoDuplicateContentType(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Header()["Content-Type"] = []string{""} | |
| fmt.Fprintf(w, "<html><head></head><body>hi</body></html>") | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| 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) { | |
| synctestTest(t, testServerContentLengthCanBeDisabled) | |
| } | |
| func testServerContentLengthCanBeDisabled(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Header()["Content-Length"] = nil | |
| fmt.Fprintf(w, "OK") | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| header: http.Header{ | |
| ":status": []string{"200"}, | |
| "content-type": []string{"text/plain; charset=utf-8"}, | |
| }, | |
| }) | |
| } | |
| func BenchmarkServer_GetRequest(b *testing.B) { | |
| DisableGoroutineTracking(b) | |
| b.ReportAllocs() | |
| const msg = "Hello, world." | |
| 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) | |
| } | |
| io.WriteString(w, msg) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| // Give the server quota to reply. (plus it has the 64KB) | |
| if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil { | |
| b.Fatal(err) | |
| } | |
| hbf := st.encodeHeader(":method", "GET") | |
| for i := 0; i < b.N; i++ { | |
| streamID := uint32(1 + 2*i) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: hbf, | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.wantFrameType(FrameHeaders) | |
| st.wantFrameType(FrameData) | |
| } | |
| } | |
| func BenchmarkServer_PostRequest(b *testing.B) { | |
| DisableGoroutineTracking(b) | |
| b.ReportAllocs() | |
| const msg = "Hello, world." | |
| 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) | |
| } | |
| io.WriteString(w, msg) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| // Give the server quota to reply. (plus it has the 64KB) | |
| if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil { | |
| b.Fatal(err) | |
| } | |
| hbf := st.encodeHeader(":method", "POST") | |
| for i := 0; i < b.N; i++ { | |
| streamID := uint32(1 + 2*i) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: hbf, | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.writeData(streamID, true, nil) | |
| st.wantFrameType(FrameHeaders) | |
| st.wantFrameType(FrameData) | |
| } | |
| } | |
| type connStateConn struct { | |
| net.Conn | |
| cs tls.ConnectionState | |
| } | |
| func (c connStateConn) ConnectionState() tls.ConnectionState { return c.cs } | |
| // golang.org/issue/12737 -- handle any net.Conn, not just | |
| // *tls.Conn. | |
| func TestServerHandleCustomConn(t *testing.T) { synctestTest(t, testServerHandleCustomConn) } | |
| func testServerHandleCustomConn(t testing.TB) { | |
| var s Server | |
| c1, c2 := net.Pipe() | |
| clientDone := make(chan struct{}) | |
| handlerDone := make(chan struct{}) | |
| var req *http.Request | |
| go func() { | |
| defer close(clientDone) | |
| defer c2.Close() | |
| fr := NewFramer(c2, c2) | |
| io.WriteString(c2, ClientPreface) | |
| fr.WriteSettings() | |
| fr.WriteSettingsAck() | |
| f, err := fr.ReadFrame() | |
| if err != nil { | |
| t.Error(err) | |
| return | |
| } | |
| if sf, ok := f.(*SettingsFrame); !ok || sf.IsAck() { | |
| t.Errorf("Got %v; want non-ACK SettingsFrame", SummarizeFrame(f)) | |
| return | |
| } | |
| f, err = fr.ReadFrame() | |
| if err != nil { | |
| t.Error(err) | |
| return | |
| } | |
| if sf, ok := f.(*SettingsFrame); !ok || !sf.IsAck() { | |
| t.Errorf("Got %v; want ACK SettingsFrame", SummarizeFrame(f)) | |
| return | |
| } | |
| var henc hpackEncoder | |
| fr.WriteHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: henc.encodeHeaderRaw(t, ":method", "GET", ":path", "/", ":scheme", "https", ":authority", "foo.com"), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| go io.Copy(io.Discard, c2) | |
| <-handlerDone | |
| }() | |
| const testString = "my custom ConnectionState" | |
| const cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F // defined in ciphers.go | |
| fakeConnState := tls.ConnectionState{ | |
| ServerName: testString, | |
| Version: tls.VersionTLS12, | |
| CipherSuite: cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | |
| } | |
| go s.ServeConn(connStateConn{c1, fakeConnState}, &ServeConnOpts{ | |
| BaseConfig: &http.Server{ | |
| Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| defer close(handlerDone) | |
| req = r | |
| }), | |
| }}) | |
| <-clientDone | |
| if req.TLS == nil { | |
| t.Fatalf("Request.TLS is nil. Got: %#v", req) | |
| } | |
| if req.TLS.ServerName != testString { | |
| t.Fatalf("Request.TLS = %+v; want ServerName of %q", req.TLS, testString) | |
| } | |
| } | |
| // golang.org/issue/14214 | |
| func TestServer_Rejects_ConnHeaders(t *testing.T) { synctestTest(t, testServer_Rejects_ConnHeaders) } | |
| func testServer_Rejects_ConnHeaders(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| t.Error("should not get to Handler") | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.bodylessReq1("connection", "foo") | |
| 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 { | |
| enc *hpack.Encoder | |
| buf bytes.Buffer | |
| } | |
| func (he *hpackEncoder) encodeHeaderRaw(t testing.TB, headers ...string) []byte { | |
| if len(headers)%2 == 1 { | |
| panic("odd number of kv args") | |
| } | |
| he.buf.Reset() | |
| if he.enc == nil { | |
| he.enc = hpack.NewEncoder(&he.buf) | |
| } | |
| for len(headers) > 0 { | |
| k, v := headers[0], headers[1] | |
| err := he.enc.WriteField(hpack.HeaderField{Name: k, Value: v}) | |
| if err != nil { | |
| t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err) | |
| } | |
| headers = headers[2:] | |
| } | |
| return he.buf.Bytes() | |
| } | |
| // golang.org/issue/14030 | |
| func TestExpect100ContinueAfterHandlerWrites(t *testing.T) { | |
| synctestTest(t, testExpect100ContinueAfterHandlerWrites) | |
| } | |
| func testExpect100ContinueAfterHandlerWrites(t testing.TB) { | |
| const msg = "Hello" | |
| const msg2 = "World" | |
| doRead := make(chan bool, 1) | |
| defer close(doRead) // fallback cleanup | |
| ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { | |
| io.WriteString(w, msg) | |
| w.(http.Flusher).Flush() | |
| // Do a read, which might force a 100-continue status to be sent. | |
| <-doRead | |
| r.Body.Read(make([]byte, 10)) | |
| io.WriteString(w, msg2) | |
| }) | |
| tr := &Transport{TLSClientConfig: tlsConfigInsecure} | |
| defer tr.CloseIdleConnections() | |
| req, _ := http.NewRequest("POST", ts.URL, io.LimitReader(neverEnding('A'), 2<<20)) | |
| req.Header.Set("Expect", "100-continue") | |
| res, err := tr.RoundTrip(req) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer res.Body.Close() | |
| buf := make([]byte, len(msg)) | |
| if _, err := io.ReadFull(res.Body, buf); err != nil { | |
| t.Fatal(err) | |
| } | |
| if string(buf) != msg { | |
| t.Fatalf("msg = %q; want %q", buf, msg) | |
| } | |
| doRead <- true | |
| if _, err := io.ReadFull(res.Body, buf); err != nil { | |
| t.Fatal(err) | |
| } | |
| if string(buf) != msg2 { | |
| t.Fatalf("second msg = %q; want %q", buf, msg2) | |
| } | |
| } | |
| type funcReader func([]byte) (n int, err error) | |
| func (f funcReader) Read(p []byte) (n int, err error) { return f(p) } | |
| // golang.org/issue/16481 -- return flow control when streams close with unread data. | |
| // (The Server version of the bug. See also TestUnreadFlowControlReturned_Transport) | |
| func TestUnreadFlowControlReturned_Server(t *testing.T) { | |
| for _, tt := range []struct { | |
| name string | |
| reqFn func(r *http.Request) | |
| }{ | |
| { | |
| "body-open", | |
| func(r *http.Request) {}, | |
| }, | |
| { | |
| "body-closed", | |
| func(r *http.Request) { | |
| r.Body.Close() | |
| }, | |
| }, | |
| { | |
| "read-1-byte-and-close", | |
| func(r *http.Request) { | |
| b := make([]byte, 1) | |
| r.Body.Read(b) | |
| r.Body.Close() | |
| }, | |
| }, | |
| } { | |
| synctestSubtest(t, tt.name, func(t testing.TB) { | |
| unblock := make(chan bool, 1) | |
| defer close(unblock) | |
| 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 | |
| }) | |
| tr := &Transport{TLSClientConfig: tlsConfigInsecure} | |
| defer tr.CloseIdleConnections() | |
| // This previously hung on the 4th iteration. | |
| iters := 100 | |
| if testing.Short() { | |
| iters = 20 | |
| } | |
| for i := 0; i < iters; i++ { | |
| body := io.MultiReader( | |
| io.LimitReader(neverEnding('A'), 16<<10), | |
| funcReader(func([]byte) (n int, err error) { | |
| unblock <- true | |
| return 0, io.EOF | |
| }), | |
| ) | |
| req, _ := http.NewRequest("POST", ts.URL, body) | |
| res, err := tr.RoundTrip(req) | |
| if err != nil { | |
| t.Fatal(tt.name, err) | |
| } | |
| res.Body.Close() | |
| } | |
| }) | |
| } | |
| } | |
| func TestServerReturnsStreamAndConnFlowControlOnBodyClose(t *testing.T) { | |
| synctestTest(t, testServerReturnsStreamAndConnFlowControlOnBodyClose) | |
| } | |
| func testServerReturnsStreamAndConnFlowControlOnBodyClose(t testing.TB) { | |
| unblockHandler := make(chan struct{}) | |
| defer close(unblockHandler) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| r.Body.Close() | |
| w.WriteHeader(200) | |
| w.(http.Flusher).Flush() | |
| <-unblockHandler | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndHeaders: true, | |
| }) | |
| 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(wantData{ | |
| streamID: 1, | |
| endStream: true, | |
| }) | |
| } | |
| func TestServerIdleTimeout(t *testing.T) { synctestTest(t, testServerIdleTimeout) } | |
| func testServerIdleTimeout(t testing.TB) { | |
| if testing.Short() { | |
| t.Skip("skipping in short mode") | |
| } | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| }, func(h2s *Server) { | |
| h2s.IdleTimeout = 500 * time.Millisecond | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.advance(500 * time.Millisecond) | |
| st.wantGoAway(0, ErrCodeNo) | |
| } | |
| func TestServerIdleTimeout_AfterRequest(t *testing.T) { | |
| synctestTest(t, testServerIdleTimeout_AfterRequest) | |
| } | |
| func testServerIdleTimeout_AfterRequest(t testing.TB) { | |
| if testing.Short() { | |
| t.Skip("skipping in short mode") | |
| } | |
| const ( | |
| requestTimeout = 2 * time.Second | |
| idleTimeout = 1 * time.Second | |
| ) | |
| var st *serverTester | |
| st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| time.Sleep(requestTimeout) | |
| }, func(h2s *Server) { | |
| h2s.IdleTimeout = idleTimeout | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| // Send a request which takes twice the timeout. Verifies the | |
| // idle timeout doesn't fire while we're in a request: | |
| st.bodylessReq1() | |
| st.advance(requestTimeout) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| }) | |
| // But the idle timeout should be rearmed after the request | |
| // is done: | |
| st.advance(idleTimeout) | |
| st.wantGoAway(1, ErrCodeNo) | |
| } | |
| // grpc-go closes the Request.Body currently with a Read. | |
| // Verify that it doesn't race. | |
| // See https://github.com/grpc/grpc-go/pull/938 | |
| func TestRequestBodyReadCloseRace(t *testing.T) { synctestTest(t, testRequestBodyReadCloseRace) } | |
| func testRequestBodyReadCloseRace(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| go r.Body.Close() | |
| io.Copy(io.Discard, r.Body) | |
| }) | |
| st.greet() | |
| data := make([]byte, 1024) | |
| for i := range 100 { | |
| streamID := uint32(1 + (i * 2)) // clients send odd numbers | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader(), | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, false, data) | |
| for { | |
| // Look for a RST_STREAM frame. | |
| // Skip over anything else (HEADERS and WINDOW_UPDATE). | |
| fr := st.readFrame() | |
| if fr == nil { | |
| t.Fatalf("got no RSTStreamFrame, want one") | |
| } | |
| rst, ok := fr.(*RSTStreamFrame) | |
| if !ok { | |
| continue | |
| } | |
| // We can get NO or STREAM_CLOSED depending on scheduling. | |
| if rst.ErrCode != ErrCodeNo && rst.ErrCode != ErrCodeStreamClosed { | |
| t.Fatalf("got RSTStreamFrame with error code %v, want ErrCodeNo or ErrCodeStreamClosed", rst.ErrCode) | |
| } | |
| break | |
| } | |
| } | |
| } | |
| func TestIssue20704Race(t *testing.T) { synctestTest(t, testIssue20704Race) } | |
| func testIssue20704Race(t testing.TB) { | |
| if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" { | |
| t.Skip("skipping in short mode") | |
| } | |
| const ( | |
| itemSize = 1 << 10 | |
| itemCount = 100 | |
| ) | |
| 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 | |
| } | |
| } | |
| }) | |
| tr := &Transport{TLSClientConfig: tlsConfigInsecure} | |
| defer tr.CloseIdleConnections() | |
| cl := &http.Client{Transport: tr} | |
| for i := 0; i < 1000; i++ { | |
| resp, err := cl.Get(ts.URL) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| // Force a RST stream to the server by closing without | |
| // reading the body: | |
| resp.Body.Close() | |
| } | |
| } | |
| func TestServer_Rejects_TooSmall(t *testing.T) { synctestTest(t, testServer_Rejects_TooSmall) } | |
| func testServer_Rejects_TooSmall(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| io.ReadAll(r.Body) | |
| return nil | |
| }, func(st *serverTester) { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader( | |
| ":method", "POST", | |
| "content-length", "4", | |
| ), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, true, []byte("12345")) | |
| st.wantRSTStream(1, ErrCodeProtocol) | |
| st.wantConnFlowControlConsumed(0) | |
| }) | |
| } | |
| // Tests that a handler setting "Connection: close" results in a GOAWAY being sent, | |
| // and the connection still completing. | |
| func TestServerHandlerConnectionClose(t *testing.T) { | |
| synctestTest(t, testServerHandlerConnectionClose) | |
| } | |
| func testServerHandlerConnectionClose(t testing.TB) { | |
| unblockHandler := make(chan bool, 1) | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.Header().Set("Connection", "close") | |
| w.Header().Set("Foo", "bar") | |
| w.(http.Flusher).Flush() | |
| <-unblockHandler | |
| return nil | |
| }, func(st *serverTester) { | |
| defer close(unblockHandler) // backup; in case of errors | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| var sawGoAway bool | |
| var sawRes bool | |
| var sawWindowUpdate bool | |
| for { | |
| f := st.readFrame() | |
| if f == nil { | |
| break | |
| } | |
| switch f := f.(type) { | |
| case *GoAwayFrame: | |
| sawGoAway = true | |
| if f.LastStreamID != 1 || f.ErrCode != ErrCodeNo { | |
| t.Errorf("unexpected GOAWAY frame: %v", SummarizeFrame(f)) | |
| } | |
| // Create a stream and reset it. | |
| // The server should ignore the stream. | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 3, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.fr.WriteRSTStream(3, ErrCodeCancel) | |
| // Create a stream and send data to it. | |
| // The server should return flow control, even though it | |
| // does not process the stream. | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 5, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| // Write enough data to trigger a window update. | |
| st.writeData(5, true, make([]byte, 1<<19)) | |
| case *HeadersFrame: | |
| goth := st.decodeHeader(f.HeaderBlockFragment()) | |
| wanth := [][2]string{ | |
| {":status", "200"}, | |
| {"foo", "bar"}, | |
| } | |
| if !reflect.DeepEqual(goth, wanth) { | |
| t.Errorf("got headers %v; want %v", goth, wanth) | |
| } | |
| sawRes = true | |
| case *DataFrame: | |
| if f.StreamID != 1 || !f.StreamEnded() || len(f.Data()) != 0 { | |
| t.Errorf("unexpected DATA frame: %v", SummarizeFrame(f)) | |
| } | |
| case *WindowUpdateFrame: | |
| if !sawGoAway { | |
| t.Errorf("unexpected WINDOW_UPDATE frame: %v", SummarizeFrame(f)) | |
| return | |
| } | |
| if f.StreamID != 0 { | |
| st.t.Fatalf("WindowUpdate StreamID = %d; want 5", f.FrameHeader.StreamID) | |
| return | |
| } | |
| sawWindowUpdate = true | |
| unblockHandler <- true | |
| st.sync() | |
| st.advance(GoAwayTimeout) | |
| default: | |
| t.Logf("unexpected frame: %v", SummarizeFrame(f)) | |
| } | |
| } | |
| if !sawGoAway { | |
| t.Errorf("didn't see GOAWAY") | |
| } | |
| if !sawRes { | |
| t.Errorf("didn't see response") | |
| } | |
| if !sawWindowUpdate { | |
| t.Errorf("didn't see WINDOW_UPDATE") | |
| } | |
| }) | |
| } | |
| func TestServer_Headers_HalfCloseRemote(t *testing.T) { | |
| synctestTest(t, testServer_Headers_HalfCloseRemote) | |
| } | |
| func testServer_Headers_HalfCloseRemote(t testing.TB) { | |
| var st *serverTester | |
| writeData := make(chan bool) | |
| writeHeaders := make(chan bool) | |
| leaveHandler := make(chan bool) | |
| st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| if !st.streamExists(1) { | |
| t.Errorf("stream 1 does not exist in handler") | |
| } | |
| if got, want := st.streamState(1), StateOpen; got != want { | |
| t.Errorf("in handler, state is %v; want %v", got, want) | |
| } | |
| writeData <- true | |
| if n, err := r.Body.Read(make([]byte, 1)); n != 0 || err != io.EOF { | |
| t.Errorf("body read = %d, %v; want 0, EOF", n, err) | |
| } | |
| if got, want := st.streamState(1), StateHalfClosedRemote; got != want { | |
| t.Errorf("in handler, state is %v; want %v", got, want) | |
| } | |
| writeHeaders <- true | |
| <-leaveHandler | |
| }) | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: false, // keep it open | |
| EndHeaders: true, | |
| }) | |
| <-writeData | |
| st.writeData(1, true, nil) | |
| <-writeHeaders | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: false, // keep it open | |
| EndHeaders: true, | |
| }) | |
| defer close(leaveHandler) | |
| st.wantRSTStream(1, ErrCodeStreamClosed) | |
| } | |
| func TestServerGracefulShutdown(t *testing.T) { synctestTest(t, testServerGracefulShutdown) } | |
| func testServerGracefulShutdown(t testing.TB) { | |
| handlerDone := make(chan struct{}) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| <-handlerDone | |
| w.Header().Set("x-foo", "bar") | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.bodylessReq1() | |
| 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 { | |
| t.Errorf("Read = %v, %v; want 0, non-nil", n, err) | |
| } | |
| } | |
| // Issue 31753: don't sniff when Content-Encoding is set | |
| func TestContentEncodingNoSniffing(t *testing.T) { | |
| type resp struct { | |
| name string | |
| body []byte | |
| // setting Content-Encoding as an interface instead of a string | |
| // directly, so as to differentiate between 3 states: | |
| // unset, empty string "" and set string "foo/bar". | |
| contentEncoding interface{} | |
| wantContentType string | |
| } | |
| resps := []*resp{ | |
| { | |
| name: "gzip content-encoding, gzipped", // don't sniff. | |
| contentEncoding: "application/gzip", | |
| wantContentType: "", | |
| body: func() []byte { | |
| buf := new(bytes.Buffer) | |
| gzw := gzip.NewWriter(buf) | |
| gzw.Write([]byte("doctype html><p>Hello</p>")) | |
| gzw.Close() | |
| return buf.Bytes() | |
| }(), | |
| }, | |
| { | |
| name: "zlib content-encoding, zlibbed", // don't sniff. | |
| contentEncoding: "application/zlib", | |
| wantContentType: "", | |
| body: func() []byte { | |
| buf := new(bytes.Buffer) | |
| zw := zlib.NewWriter(buf) | |
| zw.Write([]byte("doctype html><p>Hello</p>")) | |
| zw.Close() | |
| return buf.Bytes() | |
| }(), | |
| }, | |
| { | |
| name: "no content-encoding", // must sniff. | |
| wantContentType: "application/x-gzip", | |
| body: func() []byte { | |
| buf := new(bytes.Buffer) | |
| gzw := gzip.NewWriter(buf) | |
| gzw.Write([]byte("doctype html><p>Hello</p>")) | |
| gzw.Close() | |
| return buf.Bytes() | |
| }(), | |
| }, | |
| { | |
| name: "phony content-encoding", // don't sniff. | |
| contentEncoding: "foo/bar", | |
| body: []byte("doctype html><p>Hello</p>"), | |
| }, | |
| { | |
| name: "empty but set content-encoding", | |
| contentEncoding: "", | |
| wantContentType: "audio/mpeg", | |
| body: []byte("ID3"), | |
| }, | |
| } | |
| for _, tt := range resps { | |
| synctestSubtest(t, tt.name, func(t testing.TB) { | |
| 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) | |
| }) | |
| tr := &Transport{TLSClientConfig: tlsConfigInsecure} | |
| defer tr.CloseIdleConnections() | |
| req, _ := http.NewRequest("GET", ts.URL, nil) | |
| res, err := tr.RoundTrip(req) | |
| if err != nil { | |
| t.Fatalf("GET %s: %v", ts.URL, err) | |
| } | |
| defer res.Body.Close() | |
| g := res.Header.Get("Content-Encoding") | |
| 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. | |
| t.Errorf("Content-Encoding mismatch\n\tgot: %q\n\twant: %q", g, w) | |
| } else if g != "" { // "" should be the equivalent when the contentEncoding is unset. | |
| t.Errorf("Unexpected Content-Encoding %q", g) | |
| } | |
| } | |
| g = res.Header.Get("Content-Type") | |
| 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", ts.URL, g) | |
| }) | |
| } | |
| } | |
| func TestServerWindowUpdateOnBodyClose(t *testing.T) { | |
| synctestTest(t, testServerWindowUpdateOnBodyClose) | |
| } | |
| func testServerWindowUpdateOnBodyClose(t testing.TB) { | |
| const windowSize = 65535 * 2 | |
| content := make([]byte, windowSize) | |
| errc := make(chan error) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| buf := make([]byte, 4) | |
| n, err := io.ReadFull(r.Body, buf) | |
| if err != nil { | |
| errc <- err | |
| return | |
| } | |
| if n != len(buf) { | |
| errc <- fmt.Errorf("too few bytes read: %d", n) | |
| return | |
| } | |
| r.Body.Close() | |
| errc <- nil | |
| }, func(s *Server) { | |
| s.MaxUploadBufferPerConnection = windowSize | |
| s.MaxUploadBufferPerStream = windowSize | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, // clients send odd numbers | |
| BlockFragment: st.encodeHeader( | |
| ":method", "POST", | |
| "content-length", strconv.Itoa(len(content)), | |
| ), | |
| EndStream: false, // to say DATA frames are coming | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, false, content[:windowSize/2]) | |
| if err := <-errc; err != nil { | |
| t.Fatal(err) | |
| } | |
| // Wait for flow control credit for the portion of the request written so far. | |
| increments := windowSize / 2 | |
| for { | |
| f := st.readFrame() | |
| if f == nil { | |
| break | |
| } | |
| if wu, ok := f.(*WindowUpdateFrame); ok && wu.StreamID == 0 { | |
| increments -= int(wu.Increment) | |
| if increments == 0 { | |
| break | |
| } | |
| } | |
| } | |
| // Writing data after the stream is reset immediately returns flow control credit. | |
| st.writeData(1, false, content[windowSize/2:]) | |
| st.wantWindowUpdate(0, windowSize/2) | |
| } | |
| func TestNoErrorLoggedOnPostAfterGOAWAY(t *testing.T) { | |
| synctestTest(t, testNoErrorLoggedOnPostAfterGOAWAY) | |
| } | |
| func testNoErrorLoggedOnPostAfterGOAWAY(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}) | |
| defer st.Close() | |
| st.greet() | |
| content := "some content" | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader( | |
| ":method", "POST", | |
| "content-length", strconv.Itoa(len(content)), | |
| ), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: true, | |
| }) | |
| st.sc.StartGracefulShutdown() | |
| st.wantRSTStream(1, ErrCodeNo) | |
| st.wantGoAway(1, ErrCodeNo) | |
| st.writeData(1, true, []byte(content)) | |
| st.Close() | |
| if bytes.Contains(st.serverLogBuf.Bytes(), []byte("PROTOCOL_ERROR")) { | |
| t.Error("got protocol error") | |
| } | |
| } | |
| func TestServerSendsProcessing(t *testing.T) { synctestTest(t, testServerSendsProcessing) } | |
| func testServerSendsProcessing(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| w.WriteHeader(http.StatusProcessing) | |
| w.Write([]byte("stuff")) | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| 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"}, | |
| }, | |
| }) | |
| }) | |
| } | |
| func TestServerSendsEarlyHints(t *testing.T) { synctestTest(t, testServerSendsEarlyHints) } | |
| func testServerSendsEarlyHints(t testing.TB) { | |
| testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error { | |
| h := w.Header() | |
| h.Add("Content-Length", "123") | |
| h.Add("Link", "</style.css>; rel=preload; as=style") | |
| h.Add("Link", "</script.js>; rel=preload; as=script") | |
| w.WriteHeader(http.StatusEarlyHints) | |
| h.Add("Link", "</foo.js>; rel=preload; as=script") | |
| w.WriteHeader(http.StatusEarlyHints) | |
| w.Write([]byte("stuff")) | |
| return nil | |
| }, func(st *serverTester) { | |
| getSlash(st) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| header: http.Header{ | |
| ":status": []string{"103"}, | |
| "link": []string{ | |
| "</style.css>; rel=preload; as=style", | |
| "</script.js>; rel=preload; as=script", | |
| }, | |
| }, | |
| }) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| header: http.Header{ | |
| ":status": []string{"103"}, | |
| "link": []string{ | |
| "</style.css>; rel=preload; as=style", | |
| "</script.js>; rel=preload; as=script", | |
| "</foo.js>; rel=preload; as=script", | |
| }, | |
| }, | |
| }) | |
| st.wantHeaders(wantHeader{ | |
| streamID: 1, | |
| endStream: false, | |
| header: http.Header{ | |
| ":status": []string{"200"}, | |
| "link": []string{ | |
| "</style.css>; rel=preload; as=style", | |
| "</script.js>; rel=preload; as=script", | |
| "</foo.js>; rel=preload; as=script", | |
| }, | |
| "content-type": []string{"text/plain; charset=utf-8"}, | |
| "content-length": []string{"123"}, | |
| }, | |
| }) | |
| }) | |
| } | |
| func TestProtocolErrorAfterGoAway(t *testing.T) { synctestTest(t, testProtocolErrorAfterGoAway) } | |
| func testProtocolErrorAfterGoAway(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| io.Copy(io.Discard, r.Body) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| content := "some content" | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader( | |
| ":method", "POST", | |
| "content-length", strconv.Itoa(len(content)), | |
| ), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| st.writeData(1, false, []byte(content[:5])) | |
| // Send a GOAWAY with ErrCodeNo, followed by a bogus window update. | |
| // The server should close the connection. | |
| if err := st.fr.WriteGoAway(1, ErrCodeNo, nil); err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := st.fr.WriteWindowUpdate(0, 1<<31-1); err != nil { | |
| t.Fatal(err) | |
| } | |
| st.advance(GoAwayTimeout) | |
| st.wantGoAway(1, ErrCodeNo) | |
| st.wantClosed() | |
| } | |
| func TestServerInitialFlowControlWindow(t *testing.T) { | |
| for _, want := range []int32{ | |
| 65535, | |
| 1 << 19, | |
| 1 << 21, | |
| // For MaxUploadBufferPerConnection values in the range | |
| // (65535, 65535*2), we don't send an initial WINDOW_UPDATE | |
| // because we only send flow control when the window drops | |
| // below half of the maximum. Perhaps it would be nice to | |
| // test this case, but we currently do not. | |
| 65535 * 2, | |
| } { | |
| synctestSubtest(t, fmt.Sprint(want), func(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| }, func(s *Server) { | |
| s.MaxUploadBufferPerConnection = want | |
| }) | |
| st.writePreface() | |
| st.writeSettings() | |
| _ = readFrame[*SettingsFrame](t, st) | |
| st.writeSettingsAck() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| window := 65535 | |
| Frames: | |
| for { | |
| f := st.readFrame() | |
| switch f := f.(type) { | |
| case *WindowUpdateFrame: | |
| if f.FrameHeader.StreamID != 0 { | |
| t.Errorf("WindowUpdate StreamID = %d; want 0", f.FrameHeader.StreamID) | |
| return | |
| } | |
| window += int(f.Increment) | |
| case *HeadersFrame: | |
| break Frames | |
| case nil: | |
| break Frames | |
| default: | |
| } | |
| } | |
| if window != int(want) { | |
| t.Errorf("got initial flow control window = %v, want %v", window, want) | |
| } | |
| }) | |
| } | |
| } | |
| // TestServerWriteDoesNotRetainBufferAfterReturn checks for access to | |
| // the slice passed to ResponseWriter.Write after Write returns. | |
| // | |
| // Terminating the request stream on the client causes Write to return. | |
| // We should not access the slice after this point. | |
| func TestServerWriteDoesNotRetainBufferAfterReturn(t *testing.T) { | |
| synctestTest(t, testServerWriteDoesNotRetainBufferAfterReturn) | |
| } | |
| func testServerWriteDoesNotRetainBufferAfterReturn(t testing.TB) { | |
| donec := make(chan struct{}) | |
| ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { | |
| defer close(donec) | |
| buf := make([]byte, 1<<20) | |
| var i byte | |
| for { | |
| i++ | |
| _, err := w.Write(buf) | |
| for j := range buf { | |
| buf[j] = byte(i) // trigger race detector | |
| } | |
| if err != nil { | |
| return | |
| } | |
| } | |
| }) | |
| tr := &Transport{TLSClientConfig: tlsConfigInsecure} | |
| defer tr.CloseIdleConnections() | |
| req, _ := http.NewRequest("GET", ts.URL, nil) | |
| res, err := tr.RoundTrip(req) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| res.Body.Close() | |
| <-donec | |
| } | |
| // TestServerWriteDoesNotRetainBufferAfterServerClose checks for access to | |
| // the slice passed to ResponseWriter.Write after Write returns. | |
| // | |
| // Shutting down the Server causes Write to return. | |
| // We should not access the slice after this point. | |
| func TestServerWriteDoesNotRetainBufferAfterServerClose(t *testing.T) { | |
| synctestTest(t, testServerWriteDoesNotRetainBufferAfterServerClose) | |
| } | |
| func testServerWriteDoesNotRetainBufferAfterServerClose(t testing.TB) { | |
| donec := make(chan struct{}, 1) | |
| ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { | |
| donec <- struct{}{} | |
| defer close(donec) | |
| buf := make([]byte, 1<<20) | |
| var i byte | |
| for { | |
| i++ | |
| _, err := w.Write(buf) | |
| for j := range buf { | |
| buf[j] = byte(i) | |
| } | |
| if err != nil { | |
| return | |
| } | |
| } | |
| }) | |
| tr := &Transport{TLSClientConfig: tlsConfigInsecure} | |
| defer tr.CloseIdleConnections() | |
| req, _ := http.NewRequest("GET", ts.URL, nil) | |
| res, err := tr.RoundTrip(req) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer res.Body.Close() | |
| <-donec | |
| ts.Config.Close() | |
| <-donec | |
| } | |
| func TestServerMaxHandlerGoroutines(t *testing.T) { synctestTest(t, testServerMaxHandlerGoroutines) } | |
| func testServerMaxHandlerGoroutines(t testing.TB) { | |
| const maxHandlers = 10 | |
| handlerc := make(chan chan bool) | |
| donec := make(chan struct{}) | |
| defer close(donec) | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| stopc := make(chan bool, 1) | |
| select { | |
| case handlerc <- stopc: | |
| case <-donec: | |
| } | |
| select { | |
| case shouldPanic := <-stopc: | |
| if shouldPanic { | |
| panic(http.ErrAbortHandler) | |
| } | |
| case <-donec: | |
| } | |
| }, func(s *Server) { | |
| s.MaxConcurrentStreams = maxHandlers | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| // Make maxHandlers concurrent requests. | |
| // Reset them all, but only after the handler goroutines have started. | |
| var stops []chan bool | |
| streamID := uint32(1) | |
| for i := 0; i < maxHandlers; i++ { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| stops = append(stops, <-handlerc) | |
| st.fr.WriteRSTStream(streamID, ErrCodeCancel) | |
| streamID += 2 | |
| } | |
| // Start another request, and immediately reset it. | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.fr.WriteRSTStream(streamID, ErrCodeCancel) | |
| streamID += 2 | |
| // Start another two requests. Don't reset these. | |
| for i := 0; i < 2; i++ { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| streamID += 2 | |
| } | |
| // The initial maxHandlers handlers are still executing, | |
| // so the last two requests don't start any new handlers. | |
| select { | |
| case <-handlerc: | |
| t.Errorf("handler unexpectedly started while maxHandlers are already running") | |
| case <-time.After(1 * time.Millisecond): | |
| } | |
| // Tell two handlers to exit. | |
| // The pending requests which weren't reset start handlers. | |
| stops[0] <- false // normal exit | |
| stops[1] <- true // panic | |
| stops = stops[2:] | |
| stops = append(stops, <-handlerc) | |
| stops = append(stops, <-handlerc) | |
| // Make a bunch more requests. | |
| // Eventually, the server tells us to go away. | |
| for i := 0; i < 5*maxHandlers; i++ { | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| st.fr.WriteRSTStream(streamID, ErrCodeCancel) | |
| streamID += 2 | |
| } | |
| fr := readFrame[*GoAwayFrame](t, st) | |
| if fr.ErrCode != ErrCodeEnhanceYourCalm { | |
| t.Errorf("err code = %v; want %v", fr.ErrCode, ErrCodeEnhanceYourCalm) | |
| } | |
| for _, s := range stops { | |
| close(s) | |
| } | |
| } | |
| func TestServerContinuationFlood(t *testing.T) { synctestTest(t, testServerContinuationFlood) } | |
| func testServerContinuationFlood(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| fmt.Println(r.Header) | |
| }, func(s *http.Server) { | |
| s.MaxHeaderBytes = 4096 | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| }) | |
| for i := 0; i < 1000; i++ { | |
| st.fr.WriteContinuation(1, false, st.encodeHeaderRaw( | |
| fmt.Sprintf("x-%v", i), "1234567890", | |
| )) | |
| } | |
| st.fr.WriteContinuation(1, true, st.encodeHeaderRaw( | |
| "x-last-header", "1", | |
| )) | |
| for { | |
| f := st.readFrame() | |
| if f == nil { | |
| break | |
| } | |
| switch f := f.(type) { | |
| case *HeadersFrame: | |
| t.Fatalf("received HEADERS frame; want GOAWAY and a closed connection") | |
| case *GoAwayFrame: | |
| // We might not see the GOAWAY (see below), but if we do it should | |
| // indicate that the server processed this request so the client doesn't | |
| // attempt to retry it. | |
| if got, want := f.LastStreamID, uint32(1); got != want { | |
| t.Errorf("received GOAWAY with LastStreamId %v, want %v", got, want) | |
| } | |
| } | |
| } | |
| // We expect to have seen a GOAWAY before the connection closes, | |
| // but the server will close the connection after one second | |
| // whether or not it has finished sending the GOAWAY. On windows-amd64-race | |
| // builders, this fairly consistently results in the connection closing without | |
| // the GOAWAY being sent. | |
| // | |
| // Since the server's behavior is inherently racy here and the important thing | |
| // is that the connection is closed, don't check for the GOAWAY having been sent. | |
| } | |
| func TestServerContinuationAfterInvalidHeader(t *testing.T) { | |
| synctestTest(t, testServerContinuationAfterInvalidHeader) | |
| } | |
| func testServerContinuationAfterInvalidHeader(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| fmt.Println(r.Header) | |
| }) | |
| defer st.Close() | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| }) | |
| st.fr.WriteContinuation(1, false, st.encodeHeaderRaw( | |
| "x-invalid-header", "\x00", | |
| )) | |
| st.fr.WriteContinuation(1, true, st.encodeHeaderRaw( | |
| "x-valid-header", "1", | |
| )) | |
| var sawGoAway bool | |
| for { | |
| f := st.readFrame() | |
| if f == nil { | |
| break | |
| } | |
| switch f.(type) { | |
| case *GoAwayFrame: | |
| sawGoAway = true | |
| case *HeadersFrame: | |
| t.Fatalf("received HEADERS frame; want GOAWAY") | |
| } | |
| } | |
| if !sawGoAway { | |
| t.Errorf("connection closed with no GOAWAY frame; want one") | |
| } | |
| } | |
| func TestServerUpgradeRequestPrefaceFailure(t *testing.T) { | |
| synctestTest(t, testServerUpgradeRequestPrefaceFailure) | |
| } | |
| func testServerUpgradeRequestPrefaceFailure(t testing.TB) { | |
| // 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) { synctestTest(t, testServerRequestCancelOnError) } | |
| func testServerRequestCancelOnError(t testing.TB) { | |
| 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) { | |
| synctestTest(t, testServerSetReadWriteDeadlineRace) | |
| } | |
| func testServerSetReadWriteDeadlineRace(t testing.TB) { | |
| 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() | |
| } | |
| func TestServerWriteByteTimeout(t *testing.T) { synctestTest(t, testServerWriteByteTimeout) } | |
| func testServerWriteByteTimeout(t testing.TB) { | |
| const timeout = 1 * time.Second | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Write(make([]byte, 100)) | |
| }, func(s *Server) { | |
| s.WriteByteTimeout = timeout | |
| }) | |
| st.greet() | |
| st.cc.(*synctestNetConn).SetReadBufferSize(1) // write one byte at a time | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| // Read a few bytes, staying just under WriteByteTimeout. | |
| for i := 0; i < 10; i++ { | |
| st.advance(timeout - 1) | |
| if n, err := st.cc.Read(make([]byte, 1)); n != 1 || err != nil { | |
| t.Fatalf("read %v: %v, %v; want 1, nil", i, n, err) | |
| } | |
| } | |
| // Wait for WriteByteTimeout. | |
| // The connection should close. | |
| st.advance(1 * time.Second) // timeout after writing one byte | |
| st.advance(1 * time.Second) // timeout after failing to write any more bytes | |
| st.wantClosed() | |
| } | |
| func TestServerPingSent(t *testing.T) { synctestTest(t, testServerPingSent) } | |
| func testServerPingSent(t testing.TB) { | |
| const readIdleTimeout = 15 * time.Second | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| }, func(s *Server) { | |
| s.ReadIdleTimeout = readIdleTimeout | |
| }) | |
| st.greet() | |
| st.wantIdle() | |
| st.advance(readIdleTimeout) | |
| _ = readFrame[*PingFrame](t, st) | |
| st.wantIdle() | |
| st.advance(14 * time.Second) | |
| st.wantIdle() | |
| st.advance(1 * time.Second) | |
| st.wantClosed() | |
| } | |
| func TestServerPingResponded(t *testing.T) { synctestTest(t, testServerPingResponded) } | |
| func testServerPingResponded(t testing.TB) { | |
| const readIdleTimeout = 15 * time.Second | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| }, func(s *Server) { | |
| s.ReadIdleTimeout = readIdleTimeout | |
| }) | |
| st.greet() | |
| st.wantIdle() | |
| st.advance(readIdleTimeout) | |
| pf := readFrame[*PingFrame](t, st) | |
| st.wantIdle() | |
| st.advance(14 * time.Second) | |
| st.wantIdle() | |
| st.writePing(true, pf.Data) | |
| st.advance(2 * time.Second) | |
| st.wantIdle() | |
| } | |
| // golang.org/issue/15425: test that a handler closing the request | |
| // body doesn't terminate the stream to the peer. (It just stops | |
| // readability from the handler's side, and eventually the client | |
| // runs out of flow control tokens) | |
| func TestServerSendDataAfterRequestBodyClose(t *testing.T) { | |
| synctestTest(t, testServerSendDataAfterRequestBodyClose) | |
| } | |
| func testServerSendDataAfterRequestBodyClose(t testing.TB) { | |
| st := newServerTester(t, nil) | |
| st.greet() | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: 1, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: false, | |
| EndHeaders: true, | |
| }) | |
| // Handler starts writing the response body. | |
| call := st.nextHandlerCall() | |
| call.do(func(w http.ResponseWriter, req *http.Request) { | |
| w.Write([]byte("one")) | |
| http.NewResponseController(w).Flush() | |
| }) | |
| st.wantFrameType(FrameHeaders) | |
| st.wantData(wantData{ | |
| streamID: 1, | |
| endStream: false, | |
| data: []byte("one"), | |
| }) | |
| st.wantIdle() | |
| // Handler closes the request body. | |
| // This is not observable by the client. | |
| call.do(func(w http.ResponseWriter, req *http.Request) { | |
| req.Body.Close() | |
| }) | |
| st.wantIdle() | |
| // The client can still send request data, which is discarded. | |
| st.writeData(1, false, []byte("client-sent data")) | |
| st.wantIdle() | |
| // Handler can still write more response body, | |
| // which is sent to the client. | |
| call.do(func(w http.ResponseWriter, req *http.Request) { | |
| w.Write([]byte("two")) | |
| http.NewResponseController(w).Flush() | |
| }) | |
| st.wantData(wantData{ | |
| streamID: 1, | |
| endStream: false, | |
| data: []byte("two"), | |
| }) | |
| st.wantIdle() | |
| } | |
| func TestServerSettingNoRFC7540Priorities(t *testing.T) { | |
| synctestTest(t, testServerSettingNoRFC7540Priorities) | |
| } | |
| func testServerSettingNoRFC7540Priorities(t testing.TB) { | |
| tests := []struct { | |
| ws func() WriteScheduler | |
| wantNoRFC7540Setting bool | |
| }{ | |
| { | |
| ws: func() WriteScheduler { | |
| return NewPriorityWriteSchedulerRFC7540(nil) | |
| }, | |
| wantNoRFC7540Setting: false, | |
| }, | |
| { | |
| ws: NewPriorityWriteSchedulerRFC9218, | |
| wantNoRFC7540Setting: true, | |
| }, | |
| { | |
| ws: NewRandomWriteScheduler, | |
| wantNoRFC7540Setting: true, | |
| }, | |
| { | |
| ws: NewRoundRobinWriteScheduler, | |
| wantNoRFC7540Setting: true, | |
| }, | |
| } | |
| for _, tt := range tests { | |
| st := newServerTester(t, nil, func(s *Server) { | |
| s.NewWriteScheduler = tt.ws | |
| }) | |
| defer st.Close() | |
| var gotNoRFC7540Setting bool | |
| st.greetAndCheckSettings(func(s Setting) error { | |
| if s.ID != SettingNoRFC7540Priorities { | |
| return nil | |
| } | |
| gotNoRFC7540Setting = s.Val == 1 | |
| return nil | |
| }) | |
| if tt.wantNoRFC7540Setting != gotNoRFC7540Setting { | |
| t.Errorf("want SETTINGS_NO_RFC7540_PRIORITIES to be %v, got %v", tt.wantNoRFC7540Setting, gotNoRFC7540Setting) | |
| } | |
| } | |
| } | |
| func TestServerSettingNoRFC7540PrioritiesInvalid(t *testing.T) { | |
| synctestTest(t, testServerSettingNoRFC7540PrioritiesInvalid) | |
| } | |
| func testServerSettingNoRFC7540PrioritiesInvalid(t testing.TB) { | |
| st := newServerTester(t, nil) | |
| defer st.Close() | |
| st.writePreface() | |
| st.writeSettings(Setting{ID: SettingNoRFC7540Priorities, Val: 2}) | |
| synctest.Wait() | |
| st.readFrame() // SETTINGS frame | |
| st.readFrame() // WINDOW_UPDATE frame | |
| st.wantGoAway(0, ErrCodeProtocol) | |
| } | |
| // This test documents current behavior, rather than ideal behavior that we | |
| // would necessarily like to see. Refer to go.dev/issues/75936 for details. | |
| func TestServerRFC7540PrioritySmallPayload(t *testing.T) { | |
| synctestTest(t, testServerRFC7540PrioritySmallPayload) | |
| } | |
| func testServerRFC7540PrioritySmallPayload(t testing.TB) { | |
| endTest := false | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| for !endTest { | |
| w.Write([]byte("a")) | |
| if f, ok := w.(http.Flusher); ok { | |
| f.Flush() | |
| } | |
| } | |
| }, func(s *Server) { | |
| s.NewWriteScheduler = func() WriteScheduler { | |
| return NewPriorityWriteSchedulerRFC7540(nil) | |
| } | |
| }) | |
| if syncConn, ok := st.cc.(*synctestNetConn); ok { | |
| syncConn.SetReadBufferSize(1) | |
| } else { | |
| t.Fatal("Server connection is not synctestNetConn") | |
| } | |
| defer st.Close() | |
| defer func() { endTest = true }() | |
| st.greet() | |
| // Create 5 streams with weight of 0, and another 5 streams with weight of | |
| // 255. | |
| // Since each stream receives an infinite number of bytes, we should expect | |
| // to see that almost all of the response we get are for the streams with | |
| // weight of 255. | |
| for i := 1; i <= 19; i += 2 { | |
| weight := 1 | |
| if i > 10 { | |
| weight = 255 | |
| } | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: uint32(i), | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| Priority: PriorityParam{StreamDep: 0, Weight: uint8(weight)}, | |
| }) | |
| synctest.Wait() | |
| } | |
| // In the current implementation however, the response we get are | |
| // distributed equally amongst all the streams, regardless of weight. | |
| streamWriteCount := make(map[uint32]int) | |
| totalWriteCount := 10000 | |
| for range totalWriteCount { | |
| f := st.readFrame() | |
| if f == nil { | |
| break | |
| } | |
| streamWriteCount[f.Header().StreamID] += 1 | |
| } | |
| for streamID, writeCount := range streamWriteCount { | |
| expectedWriteCount := totalWriteCount / len(streamWriteCount) | |
| errorMargin := expectedWriteCount / 100 | |
| if writeCount >= expectedWriteCount+errorMargin || writeCount <= expectedWriteCount-errorMargin { | |
| t.Errorf("Expected stream %v to receive %v±%v writes, got %v", streamID, expectedWriteCount, errorMargin, writeCount) | |
| } | |
| } | |
| } | |
| // This test documents current behavior, rather than ideal behavior that we | |
| // would necessarily like to see. Refer to go.dev/issues/75936 for details. | |
| func TestServerRFC9218PrioritySmallPayload(t *testing.T) { | |
| synctestTest(t, testServerRFC9218PrioritySmallPayload) | |
| } | |
| func testServerRFC9218PrioritySmallPayload(t testing.TB) { | |
| endTest := false | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| for !endTest { | |
| w.Write([]byte("a")) | |
| if f, ok := w.(http.Flusher); ok { | |
| f.Flush() | |
| } | |
| } | |
| }, func(s *Server) { | |
| s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218 | |
| }) | |
| if syncConn, ok := st.cc.(*synctestNetConn); ok { | |
| syncConn.SetReadBufferSize(1) | |
| } else { | |
| t.Fatal("Server connection is not synctestNetConn") | |
| } | |
| defer st.Close() | |
| defer func() { endTest = true }() | |
| st.greet() | |
| // Create 5 streams with urgency of 0, and another 5 streams with urgency | |
| // of 7. | |
| // Since each stream receives an infinite number of bytes, we should expect | |
| // to see that almost all of the response we get are for the streams with | |
| // urgency of 0. | |
| for i := 1; i <= 19; i += 2 { | |
| urgency := uint8(0) | |
| if i > 10 { | |
| urgency = 7 | |
| } | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: uint32(i), | |
| BlockFragment: st.encodeHeader("priority", fmt.Sprintf("u=%d", urgency)), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| synctest.Wait() | |
| } | |
| // In the current implementation however, the response we get are | |
| // distributed equally amongst all the streams, regardless of weight. | |
| streamWriteCount := make(map[uint32]int) | |
| totalWriteCount := 10000 | |
| for range totalWriteCount { | |
| f := st.readFrame() | |
| if f == nil { | |
| break | |
| } | |
| streamWriteCount[f.Header().StreamID] += 1 | |
| } | |
| for streamID, writeCount := range streamWriteCount { | |
| expectedWriteCount := totalWriteCount / len(streamWriteCount) | |
| errorMargin := expectedWriteCount / 100 | |
| if writeCount >= expectedWriteCount+errorMargin || writeCount <= expectedWriteCount-errorMargin { | |
| t.Errorf("Expected stream %v to receive %v±%v writes, got %v", streamID, expectedWriteCount, errorMargin, writeCount) | |
| } | |
| } | |
| } | |
| func TestServerRFC9218Priority(t *testing.T) { | |
| synctestTest(t, testServerRFC9218Priority) | |
| } | |
| func testServerRFC9218Priority(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Write(slices.Repeat([]byte("a"), 16<<20)) | |
| if f, ok := w.(http.Flusher); ok { | |
| f.Flush() | |
| } | |
| }, func(s *Server) { | |
| s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218 | |
| }) | |
| defer st.Close() | |
| if syncConn, ok := st.cc.(*synctestNetConn); ok { | |
| syncConn.SetReadBufferSize(1) | |
| } else { | |
| t.Fatal("Server connection is not synctestNetConn") | |
| } | |
| st.greet() | |
| st.writeWindowUpdate(0, 1<<30) | |
| synctest.Wait() | |
| // Create 8 streams, where streams with larger ID has lower urgency value | |
| // (i.e. more urgent). | |
| for i := range 8 { | |
| streamID := uint32(i*2 + 1) | |
| urgency := 7 - i | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader("priority", fmt.Sprintf("u=%d", urgency)), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| } | |
| synctest.Wait() | |
| // Keep track of the last frame seen for each stream, indicating that they | |
| // are done being processed. | |
| lastFrame := make(map[uint32]int) | |
| for i := 0; ; i++ { | |
| f := st.readFrame() | |
| if f == nil { | |
| break | |
| } | |
| lastFrame[f.Header().StreamID] = i | |
| } | |
| for i := range 7 { | |
| streamID := uint32(i*2 + 1) | |
| nextStreamID := streamID + 2 | |
| if lastFrame[streamID] < lastFrame[nextStreamID] { | |
| t.Errorf("stream %d finished before stream %d unexpectedly", streamID, nextStreamID) | |
| } | |
| } | |
| } | |
| func TestServerRFC9218PriorityIgnoredWhenProxied(t *testing.T) { | |
| synctestTest(t, testServerRFC9218PriorityIgnoredWhenProxied) | |
| } | |
| func testServerRFC9218PriorityIgnoredWhenProxied(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Write(slices.Repeat([]byte("a"), 16<<20)) | |
| if f, ok := w.(http.Flusher); ok { | |
| f.Flush() | |
| } | |
| }, func(s *Server) { | |
| s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218 | |
| }) | |
| defer st.Close() | |
| if syncConn, ok := st.cc.(*synctestNetConn); ok { | |
| syncConn.SetReadBufferSize(1) | |
| } else { | |
| t.Fatal("Server connection is not synctestNetConn") | |
| } | |
| st.greet() | |
| st.writeWindowUpdate(0, 1<<30) | |
| synctest.Wait() | |
| // Create 8 streams, where streams with larger ID has lower urgency value | |
| // (i.e. more urgent). These should be ignored since the requests are | |
| // coming through a proxy. | |
| for i := range 8 { | |
| streamID := uint32(i*2 + 1) | |
| urgency := 7 - i | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader("priority", fmt.Sprintf("u=%d", urgency), "via", "a proxy"), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| } | |
| synctest.Wait() | |
| var streamFrameOrder []uint32 | |
| for f := st.readFrame(); f != nil; f = st.readFrame() { | |
| streamFrameOrder = append(streamFrameOrder, f.Header().StreamID) | |
| } | |
| // Only check the middle-half of the frame processing order, since the | |
| // beginning and end can be not perfectly round-robin (e.g. stream 1 gets | |
| // processed a few times while waiting before other streams are opened). | |
| half := streamFrameOrder[len(streamFrameOrder)/4 : len(streamFrameOrder)*3/4] | |
| if !slices.Equal(slices.Compact(half), half) { | |
| t.Errorf("want stream to be processed in round-robin manner when proxied, got: %v", streamFrameOrder) | |
| } | |
| } | |
| func TestServerRFC9218PriorityAware(t *testing.T) { | |
| synctestTest(t, testServerRFC9218PriorityAware) | |
| } | |
| func testServerRFC9218PriorityAware(t testing.TB) { | |
| st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { | |
| w.Write(slices.Repeat([]byte("a"), 16<<20)) | |
| if f, ok := w.(http.Flusher); ok { | |
| f.Flush() | |
| } | |
| }, func(s *Server) { | |
| s.NewWriteScheduler = NewPriorityWriteSchedulerRFC9218 | |
| }) | |
| defer st.Close() | |
| if syncConn, ok := st.cc.(*synctestNetConn); ok { | |
| syncConn.SetReadBufferSize(1) | |
| } else { | |
| t.Fatal("Server connection is not synctestNetConn") | |
| } | |
| st.greet() | |
| st.writeWindowUpdate(0, 1<<30) | |
| synctest.Wait() | |
| // When there is no indication that the client is aware of RFC 9218 | |
| // priority, it should process streams in a round-robin manner. | |
| streamCount := 10 | |
| for i := range streamCount { | |
| streamID := uint32(i*2 + 1) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| } | |
| synctest.Wait() | |
| var streamFrameOrder []uint32 | |
| for f := st.readFrame(); f != nil; f = st.readFrame() { | |
| streamFrameOrder = append(streamFrameOrder, f.Header().StreamID) | |
| } | |
| // Only check the middle-half of the frame processing order, since the | |
| // beginning and end can be not perfectly round-robin (e.g. stream 1 gets | |
| // processed a few times while waiting before other streams are opened). | |
| half := streamFrameOrder[len(streamFrameOrder)/4 : len(streamFrameOrder)*3/4] | |
| if !slices.Equal(slices.Compact(half), half) { | |
| t.Errorf("want stream to be processed in round-robin manner when unaware of priority, got: %v", streamFrameOrder) | |
| } | |
| // Send a PRIORITY_UPDATE frame for stream 1 which would have finished by | |
| // now. So, this is a no-op, but makes it so that the server is aware that | |
| // the client is aware of RFC 9218 priority. | |
| st.writePriorityUpdate(1, "") | |
| synctest.Wait() | |
| // Now that the server knows that the client is aware of RFC 9218 priority, | |
| // streams should be processed one-by-one to completion when no explicit | |
| // priority is given as they all have the same urgency and are | |
| // non-incremental. | |
| streamFrameOrder = []uint32{} | |
| for i := range streamCount { | |
| i += streamCount | |
| streamID := uint32(i*2 + 1) | |
| st.writeHeaders(HeadersFrameParam{ | |
| StreamID: streamID, | |
| BlockFragment: st.encodeHeader(), | |
| EndStream: true, | |
| EndHeaders: true, | |
| }) | |
| } | |
| for f := st.readFrame(); f != nil; f = st.readFrame() { | |
| streamFrameOrder = append(streamFrameOrder, f.Header().StreamID) | |
| } | |
| if !slices.Equal(slices.Compact(half), half) { | |
| t.Errorf("want stream to be processed one-by-one to completion when aware of priority, got: %v", streamFrameOrder) | |
| } | |
| } |