diff options
| author | Damien Neil <dneil@google.com> | 2026-03-03 08:12:25 -0800 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2026-03-12 08:13:20 -0700 |
| commit | 080aa8e9647e5211650f34f3a93fb493afbe396d (patch) | |
| tree | ab00dcd761d3622f08ff5aa7e2a52ff8c1fd0591 /src | |
| parent | 81908597a8787b09b1da90e7c6d3461b4302820f (diff) | |
| download | go-080aa8e9647e5211650f34f3a93fb493afbe396d.tar.xz | |
net/http: use net/http/internal/http2 rather than h2_bundle.go
Rework net/http/internal/http2 to use internally-defined types
rather than net/http types (to avoid an import cycle).
Remove h2_bundle.go, and replace it with calls into
net/http/internal/http2 instead.
For #67810
Change-Id: I56a1b28dbd0e302ab15a30f819dd46256a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/751304
Reviewed-by: Nicholas Husin <nsh@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Diffstat (limited to 'src')
36 files changed, 1336 insertions, 13528 deletions
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 50346c35cd..be424e1b16 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -662,8 +662,8 @@ var depsRules = ` mime/multipart, log < net/http/internal/httpcommon, net/http/internal/httpsfv - < net/http - < net/http/internal/http2; + < net/http/internal/http2 + < net/http; # HTTP-aware packages diff --git a/src/net/http/client.go b/src/net/http/client.go index f5c5d2e449..64dc6405cf 100644 --- a/src/net/http/client.go +++ b/src/net/http/client.go @@ -329,7 +329,7 @@ func knownRoundTripperImpl(rt RoundTripper, req *Request) bool { return knownRoundTripperImpl(altRT, req) } return true - case *http2Transport, http2noDialH2RoundTripper: + case http2RoundTripper: return true } // There's a very minor chance of a false positive with this. diff --git a/src/net/http/client_test.go b/src/net/http/client_test.go index 9952ed52c6..8c3fe1b5a2 100644 --- a/src/net/http/client_test.go +++ b/src/net/http/client_test.go @@ -60,7 +60,9 @@ func pedanticReadAll(r io.Reader) (b []byte, err error) { } } -func TestClient(t *testing.T) { run(t, testClient) } +func TestClient(t *testing.T) { + run(t, testClient, []testMode{http1Mode, https1Mode, http2UnencryptedMode, http2Mode}) +} func testClient(t *testing.T, mode testMode) { ts := newClientServerTest(t, mode, robotsTxtHandler).ts @@ -2286,6 +2288,11 @@ func testProbeZeroLengthBody(t *testing.T, mode testMode) { defer wg.Done() req, _ := NewRequest("GET", cst.ts.URL, bodyr) res, err := cst.c.Do(req) + if err != nil { + t.Error(err) + return + } + defer res.Body.Close() b, err := io.ReadAll(res.Body) if err != nil { t.Error(err) diff --git a/src/net/http/clientserver_test.go b/src/net/http/clientserver_test.go index 2bca1d3253..c25db82fe4 100644 --- a/src/net/http/clientserver_test.go +++ b/src/net/http/clientserver_test.go @@ -178,7 +178,7 @@ var optFakeNet = new(struct{}) // The optFakeNet option configures the server and client to use a fake network implementation, // suitable for use in testing/synctest tests. func newClientServerTest(t testing.TB, mode testMode, h Handler, opts ...any) *clientServerTest { - if mode == http2Mode { + if mode == http2Mode || mode == http2UnencryptedMode { CondSkipHTTP2(t) } cst := &clientServerTest{ @@ -205,12 +205,6 @@ func newClientServerTest(t testing.TB, mode testMode, h Handler, opts ...any) *c cst.ts = httptest.NewUnstartedServer(h) } - if mode == http2UnencryptedMode { - p := &Protocols{} - p.SetUnencryptedHTTP2(true) - cst.ts.Config.Protocols = p - } - for _, opt := range opts { switch opt := opt.(type) { case func(*Transport): @@ -228,16 +222,23 @@ func newClientServerTest(t testing.TB, mode testMode, h Handler, opts ...any) *c cst.ts.Config.ErrorLog = log.New(testLogWriter{t}, "", 0) } + p := &Protocols{} + if cst.ts.Config.Protocols == nil { + cst.ts.Config.Protocols = p + } switch mode { case http1Mode: + p.SetHTTP1(true) cst.ts.Start() case https1Mode: + p.SetHTTP1(true) cst.ts.StartTLS() case http2UnencryptedMode: - ExportHttp2ConfigureServer(cst.ts.Config, nil) + p.SetUnencryptedHTTP2(true) cst.ts.Start() case http2Mode: - ExportHttp2ConfigureServer(cst.ts.Config, nil) + p.SetHTTP2(true) + cst.ts.EnableHTTP2 = true cst.ts.TLS = cst.ts.Config.TLSConfig cst.ts.StartTLS() default: @@ -245,18 +246,10 @@ func newClientServerTest(t testing.TB, mode testMode, h Handler, opts ...any) *c } cst.c = cst.ts.Client() cst.tr = cst.c.Transport.(*Transport) - if mode == http2Mode || mode == http2UnencryptedMode { - if err := ExportHttp2ConfigureTransport(cst.tr); err != nil { - t.Fatal(err) - } - } for _, f := range transportFuncs { f(cst.tr) } - - if mode == http2UnencryptedMode { - p := &Protocols{} - p.SetUnencryptedHTTP2(true) + if cst.tr.Protocols == nil { cst.tr.Protocols = p } @@ -1163,10 +1156,9 @@ func testTransportDiscardsUnneededConns(t *testing.T, mode testMode) { c := noteCloseConn{rc, func() { atomic.AddInt32(&numClose, 1) }} return tls.Client(c, tlsConfig), nil }, + Protocols: &Protocols{}, } - if err := ExportHttp2ConfigureTransport(tr); err != nil { - t.Fatal(err) - } + tr.Protocols.SetHTTP2(true) defer tr.CloseIdleConnections() c := &Client{Transport: tr} @@ -1725,6 +1717,16 @@ func TestH12_WebSocketUpgrade(t *testing.T) { } res.Proto = "HTTP/IGNORE" // skip later checks that Proto must be 1.1 vs 2.0 }, + Opts: []any{ + func(s *Server) { + // Configure servers to support HTTP/1 and HTTP/2, + // so we can verify that we use HTTP/1 + // even when HTTP/2 is an option. + s.Protocols = &Protocols{} + s.Protocols.SetHTTP1(true) + s.Protocols.SetHTTP2(true) + }, + }, }.run(t) } diff --git a/src/net/http/export_test.go b/src/net/http/export_test.go index 472d9c37ae..b499769c4f 100644 --- a/src/net/http/export_test.go +++ b/src/net/http/export_test.go @@ -29,7 +29,6 @@ var ( ExportErrServerClosedIdle = errServerClosedIdle ExportServeFile = serveFile ExportScanETag = scanETag - ExportHttp2ConfigureServer = http2ConfigureServer Export_shouldCopyHeaderOnRedirect = shouldCopyHeaderOnRedirect Export_writeStatusLine = writeStatusLine Export_is408Message = is408Message @@ -135,29 +134,15 @@ func (t *Transport) IdleConnStrsForTesting() []string { defer t.idleMu.Unlock() for _, conns := range t.idleConn { for _, pc := range conns { + if pc.conn == nil { + continue + } ret = append(ret, pc.conn.LocalAddr().String()+"/"+pc.conn.RemoteAddr().String()) } } - slices.Sort(ret) - return ret -} - -func (t *Transport) IdleConnStrsForTesting_h2() []string { - var ret []string - noDialPool := t.h2transport.(*http2Transport).ConnPool.(http2noDialClientConnPool) - pool := noDialPool.http2clientConnPool - - pool.mu.Lock() - defer pool.mu.Unlock() - - for k, ccs := range pool.conns { - for _, cc := range ccs { - if cc.idleState().canTakeNewRequest { - ret = append(ret, k) - } - } + if f, ok := t.h2transport.(interface{ IdleConnStrsForTesting() []string }); ok { + ret = append(ret, f.IdleConnStrsForTesting()...) } - slices.Sort(ret) return ret } @@ -256,15 +241,6 @@ func hookSetter(dst *func()) func(func()) { } } -func ExportHttp2ConfigureTransport(t *Transport) error { - t2, err := http2configureTransports(t) - if err != nil { - return err - } - t.h2transport = t2 - return nil -} - func (s *Server) ExportAllConnsIdle() bool { s.mu.Lock() defer s.mu.Unlock() diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go deleted file mode 100644 index 7793d1cd5e..0000000000 --- a/src/net/http/h2_bundle.go +++ /dev/null @@ -1,12437 +0,0 @@ -//go:build !nethttpomithttp2 - -// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. -// $ bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 -import=golang.org/x/net/internal/httpcommon=net/http/internal/httpcommon -import=golang.org/x/net/internal/httpsfv=net/http/internal/httpsfv golang.org/x/net/http2 - -// Package http2 implements the HTTP/2 protocol. -// -// This package is low-level and intended to be used directly by very -// few people. Most users will use it indirectly through the automatic -// use by the net/http package (from Go 1.6 and later). -// For use in earlier Go versions see ConfigureServer. (Transport support -// requires Go 1.6 or later) -// -// See https://http2.github.io/ for more information on HTTP/2. -// - -package http - -import ( - "bufio" - "bytes" - "compress/flate" - "compress/gzip" - "context" - "crypto/rand" - "crypto/tls" - "encoding/binary" - "errors" - "fmt" - "io" - "io/fs" - "log" - "math" - "math/bits" - mathrand "math/rand" - "net" - "net/http/httptrace" - "net/http/internal/httpcommon" - "net/http/internal/httpsfv" - "net/textproto" - "net/url" - "os" - "reflect" - "runtime" - "slices" - "sort" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "golang.org/x/net/http/httpguts" - "golang.org/x/net/http2/hpack" - "golang.org/x/net/idna" -) - -// The HTTP protocols are defined in terms of ASCII, not Unicode. This file -// contains helper functions which may use Unicode-aware functions which would -// otherwise be unsafe and could introduce vulnerabilities if used improperly. - -// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t -// are equal, ASCII-case-insensitively. -func http2asciiEqualFold(s, t string) bool { - if len(s) != len(t) { - return false - } - for i := 0; i < len(s); i++ { - if http2lower(s[i]) != http2lower(t[i]) { - return false - } - } - return true -} - -// lower returns the ASCII lowercase version of b. -func http2lower(b byte) byte { - if 'A' <= b && b <= 'Z' { - return b + ('a' - 'A') - } - return b -} - -// isASCIIPrint returns whether s is ASCII and printable according to -// https://tools.ietf.org/html/rfc20#section-4.2. -func http2isASCIIPrint(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] < ' ' || s[i] > '~' { - return false - } - } - return true -} - -// asciiToLower returns the lowercase version of s if s is ASCII and printable, -// and whether or not it was. -func http2asciiToLower(s string) (lower string, ok bool) { - if !http2isASCIIPrint(s) { - return "", false - } - return strings.ToLower(s), true -} - -// A list of the possible cipher suite ids. Taken from -// https://www.iana.org/assignments/tls-parameters/tls-parameters.txt - -const ( - http2cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000 - http2cipher_TLS_RSA_WITH_NULL_MD5 uint16 = 0x0001 - http2cipher_TLS_RSA_WITH_NULL_SHA uint16 = 0x0002 - http2cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0003 - http2cipher_TLS_RSA_WITH_RC4_128_MD5 uint16 = 0x0004 - http2cipher_TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005 - http2cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x0006 - http2cipher_TLS_RSA_WITH_IDEA_CBC_SHA uint16 = 0x0007 - http2cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0008 - http2cipher_TLS_RSA_WITH_DES_CBC_SHA uint16 = 0x0009 - http2cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000A - http2cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000B - http2cipher_TLS_DH_DSS_WITH_DES_CBC_SHA uint16 = 0x000C - http2cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x000D - http2cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000E - http2cipher_TLS_DH_RSA_WITH_DES_CBC_SHA uint16 = 0x000F - http2cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0010 - http2cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0011 - http2cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA uint16 = 0x0012 - http2cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x0013 - http2cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0014 - http2cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA uint16 = 0x0015 - http2cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0016 - http2cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0017 - http2cipher_TLS_DH_anon_WITH_RC4_128_MD5 uint16 = 0x0018 - http2cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0019 - http2cipher_TLS_DH_anon_WITH_DES_CBC_SHA uint16 = 0x001A - http2cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0x001B - // Reserved uint16 = 0x001C-1D - http2cipher_TLS_KRB5_WITH_DES_CBC_SHA uint16 = 0x001E - http2cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA uint16 = 0x001F - http2cipher_TLS_KRB5_WITH_RC4_128_SHA uint16 = 0x0020 - http2cipher_TLS_KRB5_WITH_IDEA_CBC_SHA uint16 = 0x0021 - http2cipher_TLS_KRB5_WITH_DES_CBC_MD5 uint16 = 0x0022 - http2cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5 uint16 = 0x0023 - http2cipher_TLS_KRB5_WITH_RC4_128_MD5 uint16 = 0x0024 - http2cipher_TLS_KRB5_WITH_IDEA_CBC_MD5 uint16 = 0x0025 - http2cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA uint16 = 0x0026 - http2cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA uint16 = 0x0027 - http2cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA uint16 = 0x0028 - http2cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 uint16 = 0x0029 - http2cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x002A - http2cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5 uint16 = 0x002B - http2cipher_TLS_PSK_WITH_NULL_SHA uint16 = 0x002C - http2cipher_TLS_DHE_PSK_WITH_NULL_SHA uint16 = 0x002D - http2cipher_TLS_RSA_PSK_WITH_NULL_SHA uint16 = 0x002E - http2cipher_TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002F - http2cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0030 - http2cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0031 - http2cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0032 - http2cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0033 - http2cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA uint16 = 0x0034 - http2cipher_TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035 - http2cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0036 - http2cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0037 - http2cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0038 - http2cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0039 - http2cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA uint16 = 0x003A - http2cipher_TLS_RSA_WITH_NULL_SHA256 uint16 = 0x003B - http2cipher_TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003C - http2cipher_TLS_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x003D - http2cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x003E - http2cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003F - http2cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x0040 - http2cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0041 - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0042 - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0043 - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0044 - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0045 - http2cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0046 - // Reserved uint16 = 0x0047-4F - // Reserved uint16 = 0x0050-58 - // Reserved uint16 = 0x0059-5C - // Unassigned uint16 = 0x005D-5F - // Reserved uint16 = 0x0060-66 - http2cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x0067 - http2cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x0068 - http2cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x0069 - http2cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x006A - http2cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x006B - http2cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256 uint16 = 0x006C - http2cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256 uint16 = 0x006D - // Unassigned uint16 = 0x006E-83 - http2cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0084 - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0085 - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0086 - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0087 - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0088 - http2cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0089 - http2cipher_TLS_PSK_WITH_RC4_128_SHA uint16 = 0x008A - http2cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008B - http2cipher_TLS_PSK_WITH_AES_128_CBC_SHA uint16 = 0x008C - http2cipher_TLS_PSK_WITH_AES_256_CBC_SHA uint16 = 0x008D - http2cipher_TLS_DHE_PSK_WITH_RC4_128_SHA uint16 = 0x008E - http2cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008F - http2cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0090 - http2cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0091 - http2cipher_TLS_RSA_PSK_WITH_RC4_128_SHA uint16 = 0x0092 - http2cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x0093 - http2cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0094 - http2cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0095 - http2cipher_TLS_RSA_WITH_SEED_CBC_SHA uint16 = 0x0096 - http2cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA uint16 = 0x0097 - http2cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA uint16 = 0x0098 - http2cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA uint16 = 0x0099 - http2cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA uint16 = 0x009A - http2cipher_TLS_DH_anon_WITH_SEED_CBC_SHA uint16 = 0x009B - http2cipher_TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009C - http2cipher_TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009D - http2cipher_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009E - http2cipher_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009F - http2cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x00A0 - http2cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x00A1 - http2cipher_TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A2 - http2cipher_TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A3 - http2cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A4 - http2cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A5 - http2cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256 uint16 = 0x00A6 - http2cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384 uint16 = 0x00A7 - http2cipher_TLS_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00A8 - http2cipher_TLS_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00A9 - http2cipher_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AA - http2cipher_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AB - http2cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AC - http2cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AD - http2cipher_TLS_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00AE - http2cipher_TLS_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00AF - http2cipher_TLS_PSK_WITH_NULL_SHA256 uint16 = 0x00B0 - http2cipher_TLS_PSK_WITH_NULL_SHA384 uint16 = 0x00B1 - http2cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B2 - http2cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B3 - http2cipher_TLS_DHE_PSK_WITH_NULL_SHA256 uint16 = 0x00B4 - http2cipher_TLS_DHE_PSK_WITH_NULL_SHA384 uint16 = 0x00B5 - http2cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B6 - http2cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B7 - http2cipher_TLS_RSA_PSK_WITH_NULL_SHA256 uint16 = 0x00B8 - http2cipher_TLS_RSA_PSK_WITH_NULL_SHA384 uint16 = 0x00B9 - http2cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BA - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BB - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BC - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BD - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BE - http2cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BF - http2cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C0 - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C1 - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C2 - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C3 - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C4 - http2cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C5 - // Unassigned uint16 = 0x00C6-FE - http2cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV uint16 = 0x00FF - // Unassigned uint16 = 0x01-55,* - http2cipher_TLS_FALLBACK_SCSV uint16 = 0x5600 - // Unassigned uint16 = 0x5601 - 0xC000 - http2cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA uint16 = 0xC001 - http2cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA uint16 = 0xC002 - http2cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC003 - http2cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC004 - http2cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC005 - http2cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA uint16 = 0xC006 - http2cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xC007 - http2cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC008 - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC009 - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC00A - http2cipher_TLS_ECDH_RSA_WITH_NULL_SHA uint16 = 0xC00B - http2cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA uint16 = 0xC00C - http2cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC00D - http2cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC00E - http2cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC00F - http2cipher_TLS_ECDHE_RSA_WITH_NULL_SHA uint16 = 0xC010 - http2cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xC011 - http2cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC012 - http2cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC013 - http2cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC014 - http2cipher_TLS_ECDH_anon_WITH_NULL_SHA uint16 = 0xC015 - http2cipher_TLS_ECDH_anon_WITH_RC4_128_SHA uint16 = 0xC016 - http2cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0xC017 - http2cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA uint16 = 0xC018 - http2cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA uint16 = 0xC019 - http2cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01A - http2cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01B - http2cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01C - http2cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA uint16 = 0xC01D - http2cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC01E - http2cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA uint16 = 0xC01F - http2cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA uint16 = 0xC020 - http2cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC021 - http2cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA uint16 = 0xC022 - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC023 - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC024 - http2cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC025 - http2cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC026 - http2cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC027 - http2cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC028 - http2cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC029 - http2cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC02A - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02B - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02C - http2cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02D - http2cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02E - http2cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02F - http2cipher_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC030 - http2cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC031 - http2cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC032 - http2cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA uint16 = 0xC033 - http2cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0xC034 - http2cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0xC035 - http2cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0xC036 - http2cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0xC037 - http2cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0xC038 - http2cipher_TLS_ECDHE_PSK_WITH_NULL_SHA uint16 = 0xC039 - http2cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256 uint16 = 0xC03A - http2cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384 uint16 = 0xC03B - http2cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03C - http2cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03D - http2cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03E - http2cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03F - http2cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC040 - http2cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC041 - http2cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC042 - http2cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC043 - http2cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC044 - http2cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC045 - http2cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC046 - http2cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC047 - http2cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC048 - http2cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC049 - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04A - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04B - http2cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04C - http2cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04D - http2cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04E - http2cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04F - http2cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC050 - http2cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC051 - http2cipher_TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC052 - http2cipher_TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC053 - http2cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC054 - http2cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC055 - http2cipher_TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC056 - http2cipher_TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC057 - http2cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC058 - http2cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC059 - http2cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05A - http2cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05B - http2cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05C - http2cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05D - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05E - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05F - http2cipher_TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC060 - http2cipher_TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC061 - http2cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC062 - http2cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC063 - http2cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC064 - http2cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC065 - http2cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC066 - http2cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC067 - http2cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC068 - http2cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC069 - http2cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06A - http2cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06B - http2cipher_TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06C - http2cipher_TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06D - http2cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06E - http2cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06F - http2cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC070 - http2cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC071 - http2cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC072 - http2cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC073 - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC074 - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC075 - http2cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC076 - http2cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC077 - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC078 - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC079 - http2cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07A - http2cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07B - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07C - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07D - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07E - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07F - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC080 - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC081 - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC082 - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC083 - http2cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC084 - http2cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC085 - http2cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC086 - http2cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC087 - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC088 - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC089 - http2cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08A - http2cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08B - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08C - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08D - http2cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08E - http2cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08F - http2cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC090 - http2cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC091 - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC092 - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC093 - http2cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC094 - http2cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC095 - http2cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC096 - http2cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC097 - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC098 - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC099 - http2cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC09A - http2cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC09B - http2cipher_TLS_RSA_WITH_AES_128_CCM uint16 = 0xC09C - http2cipher_TLS_RSA_WITH_AES_256_CCM uint16 = 0xC09D - http2cipher_TLS_DHE_RSA_WITH_AES_128_CCM uint16 = 0xC09E - http2cipher_TLS_DHE_RSA_WITH_AES_256_CCM uint16 = 0xC09F - http2cipher_TLS_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A0 - http2cipher_TLS_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A1 - http2cipher_TLS_DHE_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A2 - http2cipher_TLS_DHE_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A3 - http2cipher_TLS_PSK_WITH_AES_128_CCM uint16 = 0xC0A4 - http2cipher_TLS_PSK_WITH_AES_256_CCM uint16 = 0xC0A5 - http2cipher_TLS_DHE_PSK_WITH_AES_128_CCM uint16 = 0xC0A6 - http2cipher_TLS_DHE_PSK_WITH_AES_256_CCM uint16 = 0xC0A7 - http2cipher_TLS_PSK_WITH_AES_128_CCM_8 uint16 = 0xC0A8 - http2cipher_TLS_PSK_WITH_AES_256_CCM_8 uint16 = 0xC0A9 - http2cipher_TLS_PSK_DHE_WITH_AES_128_CCM_8 uint16 = 0xC0AA - http2cipher_TLS_PSK_DHE_WITH_AES_256_CCM_8 uint16 = 0xC0AB - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM uint16 = 0xC0AC - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM uint16 = 0xC0AD - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 uint16 = 0xC0AE - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 uint16 = 0xC0AF - // Unassigned uint16 = 0xC0B0-FF - // Unassigned uint16 = 0xC1-CB,* - // Unassigned uint16 = 0xCC00-A7 - http2cipher_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA8 - http2cipher_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA9 - http2cipher_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAA - http2cipher_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAB - http2cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAC - http2cipher_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAD - http2cipher_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAE -) - -// isBadCipher reports whether the cipher is blacklisted by the HTTP/2 spec. -// References: -// https://tools.ietf.org/html/rfc7540#appendix-A -// Reject cipher suites from Appendix A. -// "This list includes those cipher suites that do not -// offer an ephemeral key exchange and those that are -// based on the TLS null, stream or block cipher type" -func http2isBadCipher(cipher uint16) bool { - switch cipher { - case http2cipher_TLS_NULL_WITH_NULL_NULL, - http2cipher_TLS_RSA_WITH_NULL_MD5, - http2cipher_TLS_RSA_WITH_NULL_SHA, - http2cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5, - http2cipher_TLS_RSA_WITH_RC4_128_MD5, - http2cipher_TLS_RSA_WITH_RC4_128_SHA, - http2cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5, - http2cipher_TLS_RSA_WITH_IDEA_CBC_SHA, - http2cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA, - http2cipher_TLS_RSA_WITH_DES_CBC_SHA, - http2cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA, - http2cipher_TLS_DH_DSS_WITH_DES_CBC_SHA, - http2cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA, - http2cipher_TLS_DH_RSA_WITH_DES_CBC_SHA, - http2cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, - http2cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA, - http2cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5, - http2cipher_TLS_DH_anon_WITH_RC4_128_MD5, - http2cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, - http2cipher_TLS_DH_anon_WITH_DES_CBC_SHA, - http2cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_KRB5_WITH_DES_CBC_SHA, - http2cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_KRB5_WITH_RC4_128_SHA, - http2cipher_TLS_KRB5_WITH_IDEA_CBC_SHA, - http2cipher_TLS_KRB5_WITH_DES_CBC_MD5, - http2cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5, - http2cipher_TLS_KRB5_WITH_RC4_128_MD5, - http2cipher_TLS_KRB5_WITH_IDEA_CBC_MD5, - http2cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA, - http2cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA, - http2cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA, - http2cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5, - http2cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5, - http2cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5, - http2cipher_TLS_PSK_WITH_NULL_SHA, - http2cipher_TLS_DHE_PSK_WITH_NULL_SHA, - http2cipher_TLS_RSA_PSK_WITH_NULL_SHA, - http2cipher_TLS_RSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA, - http2cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA, - http2cipher_TLS_RSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA, - http2cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA, - http2cipher_TLS_RSA_WITH_NULL_SHA256, - http2cipher_TLS_RSA_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_RSA_WITH_AES_256_CBC_SHA256, - http2cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA, - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA, - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA, - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA, - http2cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256, - http2cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256, - http2cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - http2cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - http2cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256, - http2cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA, - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA, - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA, - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA, - http2cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA, - http2cipher_TLS_PSK_WITH_RC4_128_SHA, - http2cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_PSK_WITH_AES_128_CBC_SHA, - http2cipher_TLS_PSK_WITH_AES_256_CBC_SHA, - http2cipher_TLS_DHE_PSK_WITH_RC4_128_SHA, - http2cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA, - http2cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA, - http2cipher_TLS_RSA_PSK_WITH_RC4_128_SHA, - http2cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA, - http2cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA, - http2cipher_TLS_RSA_WITH_SEED_CBC_SHA, - http2cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA, - http2cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA, - http2cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA, - http2cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA, - http2cipher_TLS_DH_anon_WITH_SEED_CBC_SHA, - http2cipher_TLS_RSA_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_RSA_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_PSK_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_PSK_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_PSK_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_PSK_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_PSK_WITH_NULL_SHA256, - http2cipher_TLS_PSK_WITH_NULL_SHA384, - http2cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_DHE_PSK_WITH_NULL_SHA256, - http2cipher_TLS_DHE_PSK_WITH_NULL_SHA384, - http2cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_RSA_PSK_WITH_NULL_SHA256, - http2cipher_TLS_RSA_PSK_WITH_NULL_SHA384, - http2cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256, - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256, - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256, - http2cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256, - http2cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256, - http2cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256, - http2cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV, - http2cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA, - http2cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA, - http2cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA, - http2cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - http2cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_ECDH_RSA_WITH_NULL_SHA, - http2cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA, - http2cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_ECDHE_RSA_WITH_NULL_SHA, - http2cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA, - http2cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_ECDH_anon_WITH_NULL_SHA, - http2cipher_TLS_ECDH_anon_WITH_RC4_128_SHA, - http2cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA, - http2cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA, - http2cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, - http2cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA, - http2cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, - http2cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA, - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, - http2cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, - http2cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA, - http2cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA, - http2cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, - http2cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, - http2cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, - http2cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384, - http2cipher_TLS_ECDHE_PSK_WITH_NULL_SHA, - http2cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256, - http2cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384, - http2cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256, - http2cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384, - http2cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256, - http2cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384, - http2cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256, - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384, - http2cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256, - http2cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384, - http2cipher_TLS_RSA_WITH_AES_128_CCM, - http2cipher_TLS_RSA_WITH_AES_256_CCM, - http2cipher_TLS_RSA_WITH_AES_128_CCM_8, - http2cipher_TLS_RSA_WITH_AES_256_CCM_8, - http2cipher_TLS_PSK_WITH_AES_128_CCM, - http2cipher_TLS_PSK_WITH_AES_256_CCM, - http2cipher_TLS_PSK_WITH_AES_128_CCM_8, - http2cipher_TLS_PSK_WITH_AES_256_CCM_8: - return true - default: - return false - } -} - -// ClientConnPool manages a pool of HTTP/2 client connections. -type http2ClientConnPool interface { - // GetClientConn returns a specific HTTP/2 connection (usually - // a TLS-TCP connection) to an HTTP/2 server. On success, the - // returned ClientConn accounts for the upcoming RoundTrip - // call, so the caller should not omit it. If the caller needs - // to, ClientConn.RoundTrip can be called with a bogus - // new(http.Request) to release the stream reservation. - GetClientConn(req *Request, addr string) (*http2ClientConn, error) - MarkDead(*http2ClientConn) -} - -// clientConnPoolIdleCloser is the interface implemented by ClientConnPool -// implementations which can close their idle connections. -type http2clientConnPoolIdleCloser interface { - http2ClientConnPool - closeIdleConnections() -} - -var ( - _ http2clientConnPoolIdleCloser = (*http2clientConnPool)(nil) - _ http2clientConnPoolIdleCloser = http2noDialClientConnPool{} -) - -// TODO: use singleflight for dialing and addConnCalls? -type http2clientConnPool struct { - t *http2Transport - - mu sync.Mutex // TODO: maybe switch to RWMutex - // TODO: add support for sharing conns based on cert names - // (e.g. share conn for googleapis.com and appspot.com) - conns map[string][]*http2ClientConn // key is host:port - dialing map[string]*http2dialCall // currently in-flight dials - keys map[*http2ClientConn][]string - addConnCalls map[string]*http2addConnCall // in-flight addConnIfNeeded calls -} - -func (p *http2clientConnPool) GetClientConn(req *Request, addr string) (*http2ClientConn, error) { - return p.getClientConn(req, addr, http2dialOnMiss) -} - -const ( - http2dialOnMiss = true - http2noDialOnMiss = false -) - -func (p *http2clientConnPool) getClientConn(req *Request, addr string, dialOnMiss bool) (*http2ClientConn, error) { - // TODO(dneil): Dial a new connection when t.DisableKeepAlives is set? - if http2isConnectionCloseRequest(req) && dialOnMiss { - // It gets its own connection. - http2traceGetConn(req, addr) - const singleUse = true - cc, err := p.t.dialClientConn(req.Context(), addr, singleUse) - if err != nil { - return nil, err - } - return cc, nil - } - for { - p.mu.Lock() - for _, cc := range p.conns[addr] { - if cc.ReserveNewRequest() { - // When a connection is presented to us by the net/http package, - // the GetConn hook has already been called. - // Don't call it a second time here. - if !cc.getConnCalled { - http2traceGetConn(req, addr) - } - cc.getConnCalled = false - p.mu.Unlock() - return cc, nil - } - } - if !dialOnMiss { - p.mu.Unlock() - return nil, http2ErrNoCachedConn - } - http2traceGetConn(req, addr) - call := p.getStartDialLocked(req.Context(), addr) - p.mu.Unlock() - <-call.done - if http2shouldRetryDial(call, req) { - continue - } - cc, err := call.res, call.err - if err != nil { - return nil, err - } - if cc.ReserveNewRequest() { - return cc, nil - } - } -} - -// dialCall is an in-flight Transport dial call to a host. -type http2dialCall struct { - _ http2incomparable - p *http2clientConnPool - // the context associated with the request - // that created this dialCall - ctx context.Context - done chan struct{} // closed when done - res *http2ClientConn // valid after done is closed - err error // valid after done is closed -} - -// requires p.mu is held. -func (p *http2clientConnPool) getStartDialLocked(ctx context.Context, addr string) *http2dialCall { - if call, ok := p.dialing[addr]; ok { - // A dial is already in-flight. Don't start another. - return call - } - call := &http2dialCall{p: p, done: make(chan struct{}), ctx: ctx} - if p.dialing == nil { - p.dialing = make(map[string]*http2dialCall) - } - p.dialing[addr] = call - go call.dial(call.ctx, addr) - return call -} - -// run in its own goroutine. -func (c *http2dialCall) dial(ctx context.Context, addr string) { - const singleUse = false // shared conn - c.res, c.err = c.p.t.dialClientConn(ctx, addr, singleUse) - - c.p.mu.Lock() - delete(c.p.dialing, addr) - if c.err == nil { - c.p.addConnLocked(addr, c.res) - } - c.p.mu.Unlock() - - close(c.done) -} - -// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't -// already exist. It coalesces concurrent calls with the same key. -// This is used by the http1 Transport code when it creates a new connection. Because -// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know -// the protocol), it can get into a situation where it has multiple TLS connections. -// This code decides which ones live or die. -// The return value used is whether c was used. -// c is never closed. -func (p *http2clientConnPool) addConnIfNeeded(key string, t *http2Transport, c net.Conn) (used bool, err error) { - p.mu.Lock() - for _, cc := range p.conns[key] { - if cc.CanTakeNewRequest() { - p.mu.Unlock() - return false, nil - } - } - call, dup := p.addConnCalls[key] - if !dup { - if p.addConnCalls == nil { - p.addConnCalls = make(map[string]*http2addConnCall) - } - call = &http2addConnCall{ - p: p, - done: make(chan struct{}), - } - p.addConnCalls[key] = call - go call.run(t, key, c) - } - p.mu.Unlock() - - <-call.done - if call.err != nil { - return false, call.err - } - return !dup, nil -} - -type http2addConnCall struct { - _ http2incomparable - p *http2clientConnPool - done chan struct{} // closed when done - err error -} - -func (c *http2addConnCall) run(t *http2Transport, key string, nc net.Conn) { - cc, err := t.NewClientConn(nc) - - p := c.p - p.mu.Lock() - if err != nil { - c.err = err - } else { - cc.getConnCalled = true // already called by the net/http package - p.addConnLocked(key, cc) - } - delete(p.addConnCalls, key) - p.mu.Unlock() - close(c.done) -} - -// p.mu must be held -func (p *http2clientConnPool) addConnLocked(key string, cc *http2ClientConn) { - for _, v := range p.conns[key] { - if v == cc { - return - } - } - if p.conns == nil { - p.conns = make(map[string][]*http2ClientConn) - } - if p.keys == nil { - p.keys = make(map[*http2ClientConn][]string) - } - p.conns[key] = append(p.conns[key], cc) - p.keys[cc] = append(p.keys[cc], key) -} - -func (p *http2clientConnPool) MarkDead(cc *http2ClientConn) { - p.mu.Lock() - defer p.mu.Unlock() - for _, key := range p.keys[cc] { - vv, ok := p.conns[key] - if !ok { - continue - } - newList := http2filterOutClientConn(vv, cc) - if len(newList) > 0 { - p.conns[key] = newList - } else { - delete(p.conns, key) - } - } - delete(p.keys, cc) -} - -func (p *http2clientConnPool) closeIdleConnections() { - p.mu.Lock() - defer p.mu.Unlock() - // TODO: don't close a cc if it was just added to the pool - // milliseconds ago and has never been used. There's currently - // a small race window with the HTTP/1 Transport's integration - // where it can add an idle conn just before using it, and - // somebody else can concurrently call CloseIdleConns and - // break some caller's RoundTrip. - for _, vv := range p.conns { - for _, cc := range vv { - cc.closeIfIdle() - } - } -} - -func http2filterOutClientConn(in []*http2ClientConn, exclude *http2ClientConn) []*http2ClientConn { - out := in[:0] - for _, v := range in { - if v != exclude { - out = append(out, v) - } - } - // If we filtered it out, zero out the last item to prevent - // the GC from seeing it. - if len(in) != len(out) { - in[len(in)-1] = nil - } - return out -} - -// noDialClientConnPool is an implementation of http2.ClientConnPool -// which never dials. We let the HTTP/1.1 client dial and use its TLS -// connection instead. -type http2noDialClientConnPool struct{ *http2clientConnPool } - -func (p http2noDialClientConnPool) GetClientConn(req *Request, addr string) (*http2ClientConn, error) { - return p.getClientConn(req, addr, http2noDialOnMiss) -} - -// shouldRetryDial reports whether the current request should -// retry dialing after the call finished unsuccessfully, for example -// if the dial was canceled because of a context cancellation or -// deadline expiry. -func http2shouldRetryDial(call *http2dialCall, req *Request) bool { - if call.err == nil { - // No error, no need to retry - return false - } - if call.ctx == req.Context() { - // If the call has the same context as the request, the dial - // should not be retried, since any cancellation will have come - // from this request. - return false - } - if !errors.Is(call.err, context.Canceled) && !errors.Is(call.err, context.DeadlineExceeded) { - // If the call error is not because of a context cancellation or a deadline expiry, - // the dial should not be retried. - return false - } - // Only retry if the error is a context cancellation error or deadline expiry - // and the context associated with the call was canceled or expired. - return call.ctx.Err() != nil -} - -func http2clientPriorityDisabled(s *Server) bool { - return s.DisableClientPriority -} - -// http2Config is a package-internal version of net/http.HTTP2Config. -// -// http.HTTP2Config was added in Go 1.24. -// When running with a version of net/http that includes HTTP2Config, -// we merge the configuration with the fields in Transport or Server -// to produce an http2Config. -// -// Zero valued fields in http2Config are interpreted as in the -// net/http.HTTPConfig documentation. -// -// Precedence order for reconciling configurations is: -// -// - Use the net/http.{Server,Transport}.HTTP2Config value, when non-zero. -// - Otherwise use the http2.{Server.Transport} value. -// - If the resulting value is zero or out of range, use a default. -type http2http2Config struct { - MaxConcurrentStreams uint32 - StrictMaxConcurrentRequests bool - MaxDecoderHeaderTableSize uint32 - MaxEncoderHeaderTableSize uint32 - MaxReadFrameSize uint32 - MaxUploadBufferPerConnection int32 - MaxUploadBufferPerStream int32 - SendPingTimeout time.Duration - PingTimeout time.Duration - WriteByteTimeout time.Duration - PermitProhibitedCipherSuites bool - CountError func(errType string) -} - -// configFromServer merges configuration settings from -// net/http.Server.HTTP2Config and http2.Server. -func http2configFromServer(h1 *Server, h2 *http2Server) http2http2Config { - conf := http2http2Config{ - MaxConcurrentStreams: h2.MaxConcurrentStreams, - MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, - MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, - MaxReadFrameSize: h2.MaxReadFrameSize, - MaxUploadBufferPerConnection: h2.MaxUploadBufferPerConnection, - MaxUploadBufferPerStream: h2.MaxUploadBufferPerStream, - SendPingTimeout: h2.ReadIdleTimeout, - PingTimeout: h2.PingTimeout, - WriteByteTimeout: h2.WriteByteTimeout, - PermitProhibitedCipherSuites: h2.PermitProhibitedCipherSuites, - CountError: h2.CountError, - } - http2fillNetHTTPConfig(&conf, h1.HTTP2) - http2setConfigDefaults(&conf, true) - return conf -} - -// configFromTransport merges configuration settings from h2 and h2.t1.HTTP2 -// (the net/http Transport). -func http2configFromTransport(h2 *http2Transport) http2http2Config { - conf := http2http2Config{ - StrictMaxConcurrentRequests: h2.StrictMaxConcurrentStreams, - MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, - MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, - MaxReadFrameSize: h2.MaxReadFrameSize, - SendPingTimeout: h2.ReadIdleTimeout, - PingTimeout: h2.PingTimeout, - WriteByteTimeout: h2.WriteByteTimeout, - } - - // Unlike most config fields, where out-of-range values revert to the default, - // Transport.MaxReadFrameSize clips. - if conf.MaxReadFrameSize < http2minMaxFrameSize { - conf.MaxReadFrameSize = http2minMaxFrameSize - } else if conf.MaxReadFrameSize > http2maxFrameSize { - conf.MaxReadFrameSize = http2maxFrameSize - } - - if h2.t1 != nil { - http2fillNetHTTPConfig(&conf, h2.t1.HTTP2) - } - http2setConfigDefaults(&conf, false) - return conf -} - -func http2setDefault[T ~int | ~int32 | ~uint32 | ~int64](v *T, minval, maxval, defval T) { - if *v < minval || *v > maxval { - *v = defval - } -} - -func http2setConfigDefaults(conf *http2http2Config, server bool) { - http2setDefault(&conf.MaxConcurrentStreams, 1, math.MaxUint32, http2defaultMaxStreams) - http2setDefault(&conf.MaxEncoderHeaderTableSize, 1, math.MaxUint32, http2initialHeaderTableSize) - http2setDefault(&conf.MaxDecoderHeaderTableSize, 1, math.MaxUint32, http2initialHeaderTableSize) - if server { - http2setDefault(&conf.MaxUploadBufferPerConnection, http2initialWindowSize, math.MaxInt32, 1<<20) - } else { - http2setDefault(&conf.MaxUploadBufferPerConnection, http2initialWindowSize, math.MaxInt32, http2transportDefaultConnFlow) - } - if server { - http2setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, 1<<20) - } else { - http2setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, http2transportDefaultStreamFlow) - } - http2setDefault(&conf.MaxReadFrameSize, http2minMaxFrameSize, http2maxFrameSize, http2defaultMaxReadFrameSize) - http2setDefault(&conf.PingTimeout, 1, math.MaxInt64, 15*time.Second) -} - -// adjustHTTP1MaxHeaderSize converts a limit in bytes on the size of an HTTP/1 header -// to an HTTP/2 MAX_HEADER_LIST_SIZE value. -func http2adjustHTTP1MaxHeaderSize(n int64) int64 { - // http2's count is in a slightly different unit and includes 32 bytes per pair. - // So, take the net/http.Server value and pad it up a bit, assuming 10 headers. - const perFieldOverhead = 32 // per http2 spec - const typicalHeaders = 10 // conservative - return n + typicalHeaders*perFieldOverhead -} - -func http2fillNetHTTPConfig(conf *http2http2Config, h2 *HTTP2Config) { - if h2 == nil { - return - } - if h2.MaxConcurrentStreams != 0 { - conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) - } - if http2http2ConfigStrictMaxConcurrentRequests(h2) { - conf.StrictMaxConcurrentRequests = true - } - if h2.MaxEncoderHeaderTableSize != 0 { - conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize) - } - if h2.MaxDecoderHeaderTableSize != 0 { - conf.MaxDecoderHeaderTableSize = uint32(h2.MaxDecoderHeaderTableSize) - } - if h2.MaxConcurrentStreams != 0 { - conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) - } - if h2.MaxReadFrameSize != 0 { - conf.MaxReadFrameSize = uint32(h2.MaxReadFrameSize) - } - if h2.MaxReceiveBufferPerConnection != 0 { - conf.MaxUploadBufferPerConnection = int32(h2.MaxReceiveBufferPerConnection) - } - if h2.MaxReceiveBufferPerStream != 0 { - conf.MaxUploadBufferPerStream = int32(h2.MaxReceiveBufferPerStream) - } - if h2.SendPingTimeout != 0 { - conf.SendPingTimeout = h2.SendPingTimeout - } - if h2.PingTimeout != 0 { - conf.PingTimeout = h2.PingTimeout - } - if h2.WriteByteTimeout != 0 { - conf.WriteByteTimeout = h2.WriteByteTimeout - } - if h2.PermitProhibitedCipherSuites { - conf.PermitProhibitedCipherSuites = true - } - if h2.CountError != nil { - conf.CountError = h2.CountError - } -} - -func http2http2ConfigStrictMaxConcurrentRequests(h2 *HTTP2Config) bool { - return h2.StrictMaxConcurrentRequests -} - -// Buffer chunks are allocated from a pool to reduce pressure on GC. -// The maximum wasted space per dataBuffer is 2x the largest size class, -// which happens when the dataBuffer has multiple chunks and there is -// one unread byte in both the first and last chunks. We use a few size -// classes to minimize overheads for servers that typically receive very -// small request bodies. -// -// TODO: Benchmark to determine if the pools are necessary. The GC may have -// improved enough that we can instead allocate chunks like this: -// make([]byte, max(16<<10, expectedBytesRemaining)) -var http2dataChunkPools = [...]sync.Pool{ - {New: func() interface{} { return new([1 << 10]byte) }}, - {New: func() interface{} { return new([2 << 10]byte) }}, - {New: func() interface{} { return new([4 << 10]byte) }}, - {New: func() interface{} { return new([8 << 10]byte) }}, - {New: func() interface{} { return new([16 << 10]byte) }}, -} - -func http2getDataBufferChunk(size int64) []byte { - switch { - case size <= 1<<10: - return http2dataChunkPools[0].Get().(*[1 << 10]byte)[:] - case size <= 2<<10: - return http2dataChunkPools[1].Get().(*[2 << 10]byte)[:] - case size <= 4<<10: - return http2dataChunkPools[2].Get().(*[4 << 10]byte)[:] - case size <= 8<<10: - return http2dataChunkPools[3].Get().(*[8 << 10]byte)[:] - default: - return http2dataChunkPools[4].Get().(*[16 << 10]byte)[:] - } -} - -func http2putDataBufferChunk(p []byte) { - switch len(p) { - case 1 << 10: - http2dataChunkPools[0].Put((*[1 << 10]byte)(p)) - case 2 << 10: - http2dataChunkPools[1].Put((*[2 << 10]byte)(p)) - case 4 << 10: - http2dataChunkPools[2].Put((*[4 << 10]byte)(p)) - case 8 << 10: - http2dataChunkPools[3].Put((*[8 << 10]byte)(p)) - case 16 << 10: - http2dataChunkPools[4].Put((*[16 << 10]byte)(p)) - default: - panic(fmt.Sprintf("unexpected buffer len=%v", len(p))) - } -} - -// dataBuffer is an io.ReadWriter backed by a list of data chunks. -// Each dataBuffer is used to read DATA frames on a single stream. -// The buffer is divided into chunks so the server can limit the -// total memory used by a single connection without limiting the -// request body size on any single stream. -type http2dataBuffer struct { - chunks [][]byte - r int // next byte to read is chunks[0][r] - w int // next byte to write is chunks[len(chunks)-1][w] - size int // total buffered bytes - expected int64 // we expect at least this many bytes in future Write calls (ignored if <= 0) -} - -var http2errReadEmpty = errors.New("read from empty dataBuffer") - -// Read copies bytes from the buffer into p. -// It is an error to read when no data is available. -func (b *http2dataBuffer) Read(p []byte) (int, error) { - if b.size == 0 { - return 0, http2errReadEmpty - } - var ntotal int - for len(p) > 0 && b.size > 0 { - readFrom := b.bytesFromFirstChunk() - n := copy(p, readFrom) - p = p[n:] - ntotal += n - b.r += n - b.size -= n - // If the first chunk has been consumed, advance to the next chunk. - if b.r == len(b.chunks[0]) { - http2putDataBufferChunk(b.chunks[0]) - end := len(b.chunks) - 1 - copy(b.chunks[:end], b.chunks[1:]) - b.chunks[end] = nil - b.chunks = b.chunks[:end] - b.r = 0 - } - } - return ntotal, nil -} - -func (b *http2dataBuffer) bytesFromFirstChunk() []byte { - if len(b.chunks) == 1 { - return b.chunks[0][b.r:b.w] - } - return b.chunks[0][b.r:] -} - -// Len returns the number of bytes of the unread portion of the buffer. -func (b *http2dataBuffer) Len() int { - return b.size -} - -// Write appends p to the buffer. -func (b *http2dataBuffer) Write(p []byte) (int, error) { - ntotal := len(p) - for len(p) > 0 { - // If the last chunk is empty, allocate a new chunk. Try to allocate - // enough to fully copy p plus any additional bytes we expect to - // receive. However, this may allocate less than len(p). - want := int64(len(p)) - if b.expected > want { - want = b.expected - } - chunk := b.lastChunkOrAlloc(want) - n := copy(chunk[b.w:], p) - p = p[n:] - b.w += n - b.size += n - b.expected -= int64(n) - } - return ntotal, nil -} - -func (b *http2dataBuffer) lastChunkOrAlloc(want int64) []byte { - if len(b.chunks) != 0 { - last := b.chunks[len(b.chunks)-1] - if b.w < len(last) { - return last - } - } - chunk := http2getDataBufferChunk(want) - b.chunks = append(b.chunks, chunk) - b.w = 0 - return chunk -} - -// An ErrCode is an unsigned 32-bit error code as defined in the HTTP/2 spec. -type http2ErrCode uint32 - -const ( - http2ErrCodeNo http2ErrCode = 0x0 - http2ErrCodeProtocol http2ErrCode = 0x1 - http2ErrCodeInternal http2ErrCode = 0x2 - http2ErrCodeFlowControl http2ErrCode = 0x3 - http2ErrCodeSettingsTimeout http2ErrCode = 0x4 - http2ErrCodeStreamClosed http2ErrCode = 0x5 - http2ErrCodeFrameSize http2ErrCode = 0x6 - http2ErrCodeRefusedStream http2ErrCode = 0x7 - http2ErrCodeCancel http2ErrCode = 0x8 - http2ErrCodeCompression http2ErrCode = 0x9 - http2ErrCodeConnect http2ErrCode = 0xa - http2ErrCodeEnhanceYourCalm http2ErrCode = 0xb - http2ErrCodeInadequateSecurity http2ErrCode = 0xc - http2ErrCodeHTTP11Required http2ErrCode = 0xd -) - -var http2errCodeName = map[http2ErrCode]string{ - http2ErrCodeNo: "NO_ERROR", - http2ErrCodeProtocol: "PROTOCOL_ERROR", - http2ErrCodeInternal: "INTERNAL_ERROR", - http2ErrCodeFlowControl: "FLOW_CONTROL_ERROR", - http2ErrCodeSettingsTimeout: "SETTINGS_TIMEOUT", - http2ErrCodeStreamClosed: "STREAM_CLOSED", - http2ErrCodeFrameSize: "FRAME_SIZE_ERROR", - http2ErrCodeRefusedStream: "REFUSED_STREAM", - http2ErrCodeCancel: "CANCEL", - http2ErrCodeCompression: "COMPRESSION_ERROR", - http2ErrCodeConnect: "CONNECT_ERROR", - http2ErrCodeEnhanceYourCalm: "ENHANCE_YOUR_CALM", - http2ErrCodeInadequateSecurity: "INADEQUATE_SECURITY", - http2ErrCodeHTTP11Required: "HTTP_1_1_REQUIRED", -} - -func (e http2ErrCode) String() string { - if s, ok := http2errCodeName[e]; ok { - return s - } - return fmt.Sprintf("unknown error code 0x%x", uint32(e)) -} - -func (e http2ErrCode) stringToken() string { - if s, ok := http2errCodeName[e]; ok { - return s - } - return fmt.Sprintf("ERR_UNKNOWN_%d", uint32(e)) -} - -// ConnectionError is an error that results in the termination of the -// entire connection. -type http2ConnectionError http2ErrCode - -func (e http2ConnectionError) Error() string { - return fmt.Sprintf("connection error: %s", http2ErrCode(e)) -} - -// StreamError is an error that only affects one stream within an -// HTTP/2 connection. -type http2StreamError struct { - StreamID uint32 - Code http2ErrCode - Cause error // optional additional detail -} - -// errFromPeer is a sentinel error value for StreamError.Cause to -// indicate that the StreamError was sent from the peer over the wire -// and wasn't locally generated in the Transport. -var http2errFromPeer = errors.New("received from peer") - -func http2streamError(id uint32, code http2ErrCode) http2StreamError { - return http2StreamError{StreamID: id, Code: code} -} - -func (e http2StreamError) Error() string { - if e.Cause != nil { - return fmt.Sprintf("stream error: stream ID %d; %v; %v", e.StreamID, e.Code, e.Cause) - } - return fmt.Sprintf("stream error: stream ID %d; %v", e.StreamID, e.Code) -} - -// 6.9.1 The Flow Control Window -// "If a sender receives a WINDOW_UPDATE that causes a flow control -// window to exceed this maximum it MUST terminate either the stream -// or the connection, as appropriate. For streams, [...]; for the -// connection, a GOAWAY frame with a FLOW_CONTROL_ERROR code." -type http2goAwayFlowError struct{} - -func (http2goAwayFlowError) Error() string { return "connection exceeded flow control window size" } - -// connError represents an HTTP/2 ConnectionError error code, along -// with a string (for debugging) explaining why. -// -// Errors of this type are only returned by the frame parser functions -// and converted into ConnectionError(Code), after stashing away -// the Reason into the Framer's errDetail field, accessible via -// the (*Framer).ErrorDetail method. -type http2connError struct { - Code http2ErrCode // the ConnectionError error code - Reason string // additional reason -} - -func (e http2connError) Error() string { - return fmt.Sprintf("http2: connection error: %v: %v", e.Code, e.Reason) -} - -type http2pseudoHeaderError string - -func (e http2pseudoHeaderError) Error() string { - return fmt.Sprintf("invalid pseudo-header %q", string(e)) -} - -type http2duplicatePseudoHeaderError string - -func (e http2duplicatePseudoHeaderError) Error() string { - return fmt.Sprintf("duplicate pseudo-header %q", string(e)) -} - -type http2headerFieldNameError string - -func (e http2headerFieldNameError) Error() string { - return fmt.Sprintf("invalid header field name %q", string(e)) -} - -type http2headerFieldValueError string - -func (e http2headerFieldValueError) Error() string { - return fmt.Sprintf("invalid header field value for %q", string(e)) -} - -var ( - http2errMixPseudoHeaderTypes = errors.New("mix of request and response pseudo headers") - http2errPseudoAfterRegular = errors.New("pseudo header field after regular") -) - -// inflowMinRefresh is the minimum number of bytes we'll send for a -// flow control window update. -const http2inflowMinRefresh = 4 << 10 - -// inflow accounts for an inbound flow control window. -// It tracks both the latest window sent to the peer (used for enforcement) -// and the accumulated unsent window. -type http2inflow struct { - avail int32 - unsent int32 -} - -// init sets the initial window. -func (f *http2inflow) init(n int32) { - f.avail = n -} - -// add adds n bytes to the window, with a maximum window size of max, -// indicating that the peer can now send us more data. -// For example, the user read from a {Request,Response} body and consumed -// some of the buffered data, so the peer can now send more. -// It returns the number of bytes to send in a WINDOW_UPDATE frame to the peer. -// Window updates are accumulated and sent when the unsent capacity -// is at least inflowMinRefresh or will at least double the peer's available window. -func (f *http2inflow) add(n int) (connAdd int32) { - if n < 0 { - panic("negative update") - } - unsent := int64(f.unsent) + int64(n) - // "A sender MUST NOT allow a flow-control window to exceed 2^31-1 octets." - // RFC 7540 Section 6.9.1. - const maxWindow = 1<<31 - 1 - if unsent+int64(f.avail) > maxWindow { - panic("flow control update exceeds maximum window size") - } - f.unsent = int32(unsent) - if f.unsent < http2inflowMinRefresh && f.unsent < f.avail { - // If there aren't at least inflowMinRefresh bytes of window to send, - // and this update won't at least double the window, buffer the update for later. - return 0 - } - f.avail += f.unsent - f.unsent = 0 - return int32(unsent) -} - -// take attempts to take n bytes from the peer's flow control window. -// It reports whether the window has available capacity. -func (f *http2inflow) take(n uint32) bool { - if n > uint32(f.avail) { - return false - } - f.avail -= int32(n) - return true -} - -// takeInflows attempts to take n bytes from two inflows, -// typically connection-level and stream-level flows. -// It reports whether both windows have available capacity. -func http2takeInflows(f1, f2 *http2inflow, n uint32) bool { - if n > uint32(f1.avail) || n > uint32(f2.avail) { - return false - } - f1.avail -= int32(n) - f2.avail -= int32(n) - return true -} - -// outflow is the outbound flow control window's size. -type http2outflow struct { - _ http2incomparable - - // n is the number of DATA bytes we're allowed to send. - // An outflow is kept both on a conn and a per-stream. - n int32 - - // conn points to the shared connection-level outflow that is - // shared by all streams on that conn. It is nil for the outflow - // that's on the conn directly. - conn *http2outflow -} - -func (f *http2outflow) setConnFlow(cf *http2outflow) { f.conn = cf } - -func (f *http2outflow) available() int32 { - n := f.n - if f.conn != nil && f.conn.n < n { - n = f.conn.n - } - return n -} - -func (f *http2outflow) take(n int32) { - if n > f.available() { - panic("internal error: took too much") - } - f.n -= n - if f.conn != nil { - f.conn.n -= n - } -} - -// add adds n bytes (positive or negative) to the flow control window. -// It returns false if the sum would exceed 2^31-1. -func (f *http2outflow) add(n int32) bool { - sum := f.n + n - if (sum > n) == (f.n > 0) { - f.n = sum - return true - } - return false -} - -const http2frameHeaderLen = 9 - -var http2padZeros = make([]byte, 255) // zeros for padding - -// A FrameType is a registered frame type as defined in -// https://httpwg.org/specs/rfc7540.html#rfc.section.11.2 and other future -// RFCs. -type http2FrameType uint8 - -const ( - http2FrameData http2FrameType = 0x0 - http2FrameHeaders http2FrameType = 0x1 - http2FramePriority http2FrameType = 0x2 - http2FrameRSTStream http2FrameType = 0x3 - http2FrameSettings http2FrameType = 0x4 - http2FramePushPromise http2FrameType = 0x5 - http2FramePing http2FrameType = 0x6 - http2FrameGoAway http2FrameType = 0x7 - http2FrameWindowUpdate http2FrameType = 0x8 - http2FrameContinuation http2FrameType = 0x9 - http2FramePriorityUpdate http2FrameType = 0x10 -) - -var http2frameNames = [...]string{ - http2FrameData: "DATA", - http2FrameHeaders: "HEADERS", - http2FramePriority: "PRIORITY", - http2FrameRSTStream: "RST_STREAM", - http2FrameSettings: "SETTINGS", - http2FramePushPromise: "PUSH_PROMISE", - http2FramePing: "PING", - http2FrameGoAway: "GOAWAY", - http2FrameWindowUpdate: "WINDOW_UPDATE", - http2FrameContinuation: "CONTINUATION", - http2FramePriorityUpdate: "PRIORITY_UPDATE", -} - -func (t http2FrameType) String() string { - if int(t) < len(http2frameNames) { - return http2frameNames[t] - } - return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", t) -} - -// Flags is a bitmask of HTTP/2 flags. -// The meaning of flags varies depending on the frame type. -type http2Flags uint8 - -// Has reports whether f contains all (0 or more) flags in v. -func (f http2Flags) Has(v http2Flags) bool { - return (f & v) == v -} - -// Frame-specific FrameHeader flag bits. -const ( - // Data Frame - http2FlagDataEndStream http2Flags = 0x1 - http2FlagDataPadded http2Flags = 0x8 - - // Headers Frame - http2FlagHeadersEndStream http2Flags = 0x1 - http2FlagHeadersEndHeaders http2Flags = 0x4 - http2FlagHeadersPadded http2Flags = 0x8 - http2FlagHeadersPriority http2Flags = 0x20 - - // Settings Frame - http2FlagSettingsAck http2Flags = 0x1 - - // Ping Frame - http2FlagPingAck http2Flags = 0x1 - - // Continuation Frame - http2FlagContinuationEndHeaders http2Flags = 0x4 - - http2FlagPushPromiseEndHeaders http2Flags = 0x4 - http2FlagPushPromisePadded http2Flags = 0x8 -) - -var http2flagName = map[http2FrameType]map[http2Flags]string{ - http2FrameData: { - http2FlagDataEndStream: "END_STREAM", - http2FlagDataPadded: "PADDED", - }, - http2FrameHeaders: { - http2FlagHeadersEndStream: "END_STREAM", - http2FlagHeadersEndHeaders: "END_HEADERS", - http2FlagHeadersPadded: "PADDED", - http2FlagHeadersPriority: "PRIORITY", - }, - http2FrameSettings: { - http2FlagSettingsAck: "ACK", - }, - http2FramePing: { - http2FlagPingAck: "ACK", - }, - http2FrameContinuation: { - http2FlagContinuationEndHeaders: "END_HEADERS", - }, - http2FramePushPromise: { - http2FlagPushPromiseEndHeaders: "END_HEADERS", - http2FlagPushPromisePadded: "PADDED", - }, -} - -// a frameParser parses a frame given its FrameHeader and payload -// bytes. The length of payload will always equal fh.Length (which -// might be 0). -type http2frameParser func(fc *http2frameCache, fh http2FrameHeader, countError func(string), payload []byte) (http2Frame, error) - -var http2frameParsers = [...]http2frameParser{ - http2FrameData: http2parseDataFrame, - http2FrameHeaders: http2parseHeadersFrame, - http2FramePriority: http2parsePriorityFrame, - http2FrameRSTStream: http2parseRSTStreamFrame, - http2FrameSettings: http2parseSettingsFrame, - http2FramePushPromise: http2parsePushPromise, - http2FramePing: http2parsePingFrame, - http2FrameGoAway: http2parseGoAwayFrame, - http2FrameWindowUpdate: http2parseWindowUpdateFrame, - http2FrameContinuation: http2parseContinuationFrame, - http2FramePriorityUpdate: http2parsePriorityUpdateFrame, -} - -func http2typeFrameParser(t http2FrameType) http2frameParser { - if int(t) < len(http2frameParsers) { - return http2frameParsers[t] - } - return http2parseUnknownFrame -} - -// A FrameHeader is the 9 byte header of all HTTP/2 frames. -// -// See https://httpwg.org/specs/rfc7540.html#FrameHeader -type http2FrameHeader struct { - valid bool // caller can access []byte fields in the Frame - - // Type is the 1 byte frame type. There are ten standard frame - // types, but extension frame types may be written by WriteRawFrame - // and will be returned by ReadFrame (as UnknownFrame). - Type http2FrameType - - // Flags are the 1 byte of 8 potential bit flags per frame. - // They are specific to the frame type. - Flags http2Flags - - // Length is the length of the frame, not including the 9 byte header. - // The maximum size is one byte less than 16MB (uint24), but only - // frames up to 16KB are allowed without peer agreement. - Length uint32 - - // StreamID is which stream this frame is for. Certain frames - // are not stream-specific, in which case this field is 0. - StreamID uint32 -} - -// Header returns h. It exists so FrameHeaders can be embedded in other -// specific frame types and implement the Frame interface. -func (h http2FrameHeader) Header() http2FrameHeader { return h } - -func (h http2FrameHeader) String() string { - var buf bytes.Buffer - buf.WriteString("[FrameHeader ") - h.writeDebug(&buf) - buf.WriteByte(']') - return buf.String() -} - -func (h http2FrameHeader) writeDebug(buf *bytes.Buffer) { - buf.WriteString(h.Type.String()) - if h.Flags != 0 { - buf.WriteString(" flags=") - set := 0 - for i := uint8(0); i < 8; i++ { - if h.Flags&(1<<i) == 0 { - continue - } - set++ - if set > 1 { - buf.WriteByte('|') - } - name := http2flagName[h.Type][http2Flags(1<<i)] - if name != "" { - buf.WriteString(name) - } else { - fmt.Fprintf(buf, "0x%x", 1<<i) - } - } - } - if h.StreamID != 0 { - fmt.Fprintf(buf, " stream=%d", h.StreamID) - } - fmt.Fprintf(buf, " len=%d", h.Length) -} - -func (h *http2FrameHeader) checkValid() { - if !h.valid { - panic("Frame accessor called on non-owned Frame") - } -} - -func (h *http2FrameHeader) invalidate() { h.valid = false } - -// frame header bytes. -// Used only by ReadFrameHeader. -var http2fhBytes = sync.Pool{ - New: func() interface{} { - buf := make([]byte, http2frameHeaderLen) - return &buf - }, -} - -func http2invalidHTTP1LookingFrameHeader() http2FrameHeader { - fh, _ := http2readFrameHeader(make([]byte, http2frameHeaderLen), strings.NewReader("HTTP/1.1 ")) - return fh -} - -// ReadFrameHeader reads 9 bytes from r and returns a FrameHeader. -// Most users should use Framer.ReadFrame instead. -func http2ReadFrameHeader(r io.Reader) (http2FrameHeader, error) { - bufp := http2fhBytes.Get().(*[]byte) - defer http2fhBytes.Put(bufp) - return http2readFrameHeader(*bufp, r) -} - -func http2readFrameHeader(buf []byte, r io.Reader) (http2FrameHeader, error) { - _, err := io.ReadFull(r, buf[:http2frameHeaderLen]) - if err != nil { - return http2FrameHeader{}, err - } - return http2FrameHeader{ - Length: (uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])), - Type: http2FrameType(buf[3]), - Flags: http2Flags(buf[4]), - StreamID: binary.BigEndian.Uint32(buf[5:]) & (1<<31 - 1), - valid: true, - }, nil -} - -// A Frame is the base interface implemented by all frame types. -// Callers will generally type-assert the specific frame type: -// *HeadersFrame, *SettingsFrame, *WindowUpdateFrame, etc. -// -// Frames are only valid until the next call to Framer.ReadFrame. -type http2Frame interface { - Header() http2FrameHeader - - // invalidate is called by Framer.ReadFrame to make this - // frame's buffers as being invalid, since the subsequent - // frame will reuse them. - invalidate() -} - -// A Framer reads and writes Frames. -type http2Framer struct { - r io.Reader - lastFrame http2Frame - errDetail error - - // countError is a non-nil func that's called on a frame parse - // error with some unique error path token. It's initialized - // from Transport.CountError or Server.CountError. - countError func(errToken string) - - // lastHeaderStream is non-zero if the last frame was an - // unfinished HEADERS/CONTINUATION. - lastHeaderStream uint32 - // lastFrameType holds the type of the last frame for verifying frame order. - lastFrameType http2FrameType - - maxReadSize uint32 - headerBuf [http2frameHeaderLen]byte - - // TODO: let getReadBuf be configurable, and use a less memory-pinning - // allocator in server.go to minimize memory pinned for many idle conns. - // Will probably also need to make frame invalidation have a hook too. - getReadBuf func(size uint32) []byte - readBuf []byte // cache for default getReadBuf - - maxWriteSize uint32 // zero means unlimited; TODO: implement - - w io.Writer - wbuf []byte - - // AllowIllegalWrites permits the Framer's Write methods to - // write frames that do not conform to the HTTP/2 spec. This - // permits using the Framer to test other HTTP/2 - // implementations' conformance to the spec. - // If false, the Write methods will prefer to return an error - // rather than comply. - AllowIllegalWrites bool - - // AllowIllegalReads permits the Framer's ReadFrame method - // to return non-compliant frames or frame orders. - // This is for testing and permits using the Framer to test - // other HTTP/2 implementations' conformance to the spec. - // It is not compatible with ReadMetaHeaders. - AllowIllegalReads bool - - // ReadMetaHeaders if non-nil causes ReadFrame to merge - // HEADERS and CONTINUATION frames together and return - // MetaHeadersFrame instead. - ReadMetaHeaders *hpack.Decoder - - // MaxHeaderListSize is the http2 MAX_HEADER_LIST_SIZE. - // It's used only if ReadMetaHeaders is set; 0 means a sane default - // (currently 16MB) - // If the limit is hit, MetaHeadersFrame.Truncated is set true. - MaxHeaderListSize uint32 - - // TODO: track which type of frame & with which flags was sent - // last. Then return an error (unless AllowIllegalWrites) if - // we're in the middle of a header block and a - // non-Continuation or Continuation on a different stream is - // attempted to be written. - - logReads, logWrites bool - - debugFramer *http2Framer // only use for logging written writes - debugFramerBuf *bytes.Buffer - debugReadLoggerf func(string, ...interface{}) - debugWriteLoggerf func(string, ...interface{}) - - frameCache *http2frameCache // nil if frames aren't reused (default) -} - -func (fr *http2Framer) maxHeaderListSize() uint32 { - if fr.MaxHeaderListSize == 0 { - return 16 << 20 // sane default, per docs - } - return fr.MaxHeaderListSize -} - -func (f *http2Framer) startWrite(ftype http2FrameType, flags http2Flags, streamID uint32) { - // Write the FrameHeader. - f.wbuf = append(f.wbuf[:0], - 0, // 3 bytes of length, filled in endWrite - 0, - 0, - byte(ftype), - byte(flags), - byte(streamID>>24), - byte(streamID>>16), - byte(streamID>>8), - byte(streamID)) -} - -func (f *http2Framer) endWrite() error { - // Now that we know the final size, fill in the FrameHeader in - // the space previously reserved for it. Abuse append. - length := len(f.wbuf) - http2frameHeaderLen - if length >= (1 << 24) { - return http2ErrFrameTooLarge - } - _ = append(f.wbuf[:0], - byte(length>>16), - byte(length>>8), - byte(length)) - if f.logWrites { - f.logWrite() - } - - n, err := f.w.Write(f.wbuf) - if err == nil && n != len(f.wbuf) { - err = io.ErrShortWrite - } - return err -} - -func (f *http2Framer) logWrite() { - if f.debugFramer == nil { - f.debugFramerBuf = new(bytes.Buffer) - f.debugFramer = http2NewFramer(nil, f.debugFramerBuf) - f.debugFramer.logReads = false // we log it ourselves, saying "wrote" below - // Let us read anything, even if we accidentally wrote it - // in the wrong order: - f.debugFramer.AllowIllegalReads = true - } - f.debugFramerBuf.Write(f.wbuf) - fr, err := f.debugFramer.ReadFrame() - if err != nil { - f.debugWriteLoggerf("http2: Framer %p: failed to decode just-written frame", f) - return - } - f.debugWriteLoggerf("http2: Framer %p: wrote %v", f, http2summarizeFrame(fr)) -} - -func (f *http2Framer) writeByte(v byte) { f.wbuf = append(f.wbuf, v) } - -func (f *http2Framer) writeBytes(v []byte) { f.wbuf = append(f.wbuf, v...) } - -func (f *http2Framer) writeUint16(v uint16) { f.wbuf = append(f.wbuf, byte(v>>8), byte(v)) } - -func (f *http2Framer) writeUint32(v uint32) { - f.wbuf = append(f.wbuf, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) -} - -const ( - http2minMaxFrameSize = 1 << 14 - http2maxFrameSize = 1<<24 - 1 -) - -// SetReuseFrames allows the Framer to reuse Frames. -// If called on a Framer, Frames returned by calls to ReadFrame are only -// valid until the next call to ReadFrame. -func (fr *http2Framer) SetReuseFrames() { - if fr.frameCache != nil { - return - } - fr.frameCache = &http2frameCache{} -} - -type http2frameCache struct { - dataFrame http2DataFrame -} - -func (fc *http2frameCache) getDataFrame() *http2DataFrame { - if fc == nil { - return &http2DataFrame{} - } - return &fc.dataFrame -} - -// NewFramer returns a Framer that writes frames to w and reads them from r. -func http2NewFramer(w io.Writer, r io.Reader) *http2Framer { - fr := &http2Framer{ - w: w, - r: r, - countError: func(string) {}, - logReads: http2logFrameReads, - logWrites: http2logFrameWrites, - debugReadLoggerf: log.Printf, - debugWriteLoggerf: log.Printf, - } - fr.getReadBuf = func(size uint32) []byte { - if cap(fr.readBuf) >= int(size) { - return fr.readBuf[:size] - } - fr.readBuf = make([]byte, size) - return fr.readBuf - } - fr.SetMaxReadFrameSize(http2maxFrameSize) - return fr -} - -// SetMaxReadFrameSize sets the maximum size of a frame -// that will be read by a subsequent call to ReadFrame. -// It is the caller's responsibility to advertise this -// limit with a SETTINGS frame. -func (fr *http2Framer) SetMaxReadFrameSize(v uint32) { - if v > http2maxFrameSize { - v = http2maxFrameSize - } - fr.maxReadSize = v -} - -// ErrorDetail returns a more detailed error of the last error -// returned by Framer.ReadFrame. For instance, if ReadFrame -// returns a StreamError with code PROTOCOL_ERROR, ErrorDetail -// will say exactly what was invalid. ErrorDetail is not guaranteed -// to return a non-nil value and like the rest of the http2 package, -// its return value is not protected by an API compatibility promise. -// ErrorDetail is reset after the next call to ReadFrame. -func (fr *http2Framer) ErrorDetail() error { - return fr.errDetail -} - -// ErrFrameTooLarge is returned from Framer.ReadFrame when the peer -// sends a frame that is larger than declared with SetMaxReadFrameSize. -var http2ErrFrameTooLarge = errors.New("http2: frame too large") - -// terminalReadFrameError reports whether err is an unrecoverable -// error from ReadFrame and no other frames should be read. -func http2terminalReadFrameError(err error) bool { - if _, ok := err.(http2StreamError); ok { - return false - } - return err != nil -} - -// ReadFrameHeader reads the header of the next frame. -// It reads the 9-byte fixed frame header, and does not read any portion of the -// frame payload. The caller is responsible for consuming the payload, either -// with ReadFrameForHeader or directly from the Framer's io.Reader. -// -// If the frame is larger than previously set with SetMaxReadFrameSize, it -// returns the frame header and ErrFrameTooLarge. -// -// If the returned FrameHeader.StreamID is non-zero, it indicates the stream -// responsible for the error. -func (fr *http2Framer) ReadFrameHeader() (http2FrameHeader, error) { - fr.errDetail = nil - fh, err := http2readFrameHeader(fr.headerBuf[:], fr.r) - if err != nil { - return fh, err - } - if fh.Length > fr.maxReadSize { - if fh == http2invalidHTTP1LookingFrameHeader() { - return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", http2ErrFrameTooLarge) - } - return fh, http2ErrFrameTooLarge - } - if err := fr.checkFrameOrder(fh); err != nil { - return fh, err - } - return fh, nil -} - -// ReadFrameForHeader reads the payload for the frame with the given FrameHeader. -// -// It behaves identically to ReadFrame, other than not checking the maximum -// frame size. -func (fr *http2Framer) ReadFrameForHeader(fh http2FrameHeader) (http2Frame, error) { - if fr.lastFrame != nil { - fr.lastFrame.invalidate() - } - payload := fr.getReadBuf(fh.Length) - if _, err := io.ReadFull(fr.r, payload); err != nil { - if fh == http2invalidHTTP1LookingFrameHeader() { - return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err) - } - return nil, err - } - f, err := http2typeFrameParser(fh.Type)(fr.frameCache, fh, fr.countError, payload) - if err != nil { - if ce, ok := err.(http2connError); ok { - return nil, fr.connError(ce.Code, ce.Reason) - } - return nil, err - } - fr.lastFrame = f - if fr.logReads { - fr.debugReadLoggerf("http2: Framer %p: read %v", fr, http2summarizeFrame(f)) - } - if fh.Type == http2FrameHeaders && fr.ReadMetaHeaders != nil { - return fr.readMetaFrame(f.(*http2HeadersFrame)) - } - return f, nil -} - -// ReadFrame reads a single frame. The returned Frame is only valid -// until the next call to ReadFrame or ReadFrameBodyForHeader. -// -// If the frame is larger than previously set with SetMaxReadFrameSize, the -// returned error is ErrFrameTooLarge. Other errors may be of type -// ConnectionError, StreamError, or anything else from the underlying -// reader. -// -// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID -// indicates the stream responsible for the error. -func (fr *http2Framer) ReadFrame() (http2Frame, error) { - fh, err := fr.ReadFrameHeader() - if err != nil { - return nil, err - } - return fr.ReadFrameForHeader(fh) -} - -// connError returns ConnectionError(code) but first -// stashes away a public reason to the caller can optionally relay it -// to the peer before hanging up on them. This might help others debug -// their implementations. -func (fr *http2Framer) connError(code http2ErrCode, reason string) error { - fr.errDetail = errors.New(reason) - return http2ConnectionError(code) -} - -// checkFrameOrder reports an error if f is an invalid frame to return -// next from ReadFrame. Mostly it checks whether HEADERS and -// CONTINUATION frames are contiguous. -func (fr *http2Framer) checkFrameOrder(fh http2FrameHeader) error { - lastType := fr.lastFrameType - fr.lastFrameType = fh.Type - if fr.AllowIllegalReads { - return nil - } - - if fr.lastHeaderStream != 0 { - if fh.Type != http2FrameContinuation { - return fr.connError(http2ErrCodeProtocol, - fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d", - fh.Type, fh.StreamID, - lastType, fr.lastHeaderStream)) - } - if fh.StreamID != fr.lastHeaderStream { - return fr.connError(http2ErrCodeProtocol, - fmt.Sprintf("got CONTINUATION for stream %d; expected stream %d", - fh.StreamID, fr.lastHeaderStream)) - } - } else if fh.Type == http2FrameContinuation { - return fr.connError(http2ErrCodeProtocol, fmt.Sprintf("unexpected CONTINUATION for stream %d", fh.StreamID)) - } - - switch fh.Type { - case http2FrameHeaders, http2FrameContinuation: - if fh.Flags.Has(http2FlagHeadersEndHeaders) { - fr.lastHeaderStream = 0 - } else { - fr.lastHeaderStream = fh.StreamID - } - } - - return nil -} - -// A DataFrame conveys arbitrary, variable-length sequences of octets -// associated with a stream. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.1 -type http2DataFrame struct { - http2FrameHeader - data []byte -} - -func (f *http2DataFrame) StreamEnded() bool { - return f.http2FrameHeader.Flags.Has(http2FlagDataEndStream) -} - -// Data returns the frame's data octets, not including any padding -// size byte or padding suffix bytes. -// The caller must not retain the returned memory past the next -// call to ReadFrame. -func (f *http2DataFrame) Data() []byte { - f.checkValid() - return f.data -} - -func http2parseDataFrame(fc *http2frameCache, fh http2FrameHeader, countError func(string), payload []byte) (http2Frame, error) { - if fh.StreamID == 0 { - // DATA frames MUST be associated with a stream. If a - // DATA frame is received whose stream identifier - // field is 0x0, the recipient MUST respond with a - // connection error (Section 5.4.1) of type - // PROTOCOL_ERROR. - countError("frame_data_stream_0") - return nil, http2connError{http2ErrCodeProtocol, "DATA frame with stream ID 0"} - } - f := fc.getDataFrame() - f.http2FrameHeader = fh - - var padSize byte - if fh.Flags.Has(http2FlagDataPadded) { - var err error - payload, padSize, err = http2readByte(payload) - if err != nil { - countError("frame_data_pad_byte_short") - return nil, err - } - } - if int(padSize) > len(payload) { - // If the length of the padding is greater than the - // length of the frame payload, the recipient MUST - // treat this as a connection error. - // Filed: https://github.com/http2/http2-spec/issues/610 - countError("frame_data_pad_too_big") - return nil, http2connError{http2ErrCodeProtocol, "pad size larger than data payload"} - } - f.data = payload[:len(payload)-int(padSize)] - return f, nil -} - -var ( - http2errStreamID = errors.New("invalid stream ID") - http2errDepStreamID = errors.New("invalid dependent stream ID") - http2errPadLength = errors.New("pad length too large") - http2errPadBytes = errors.New("padding bytes must all be zeros unless AllowIllegalWrites is enabled") -) - -func http2validStreamIDOrZero(streamID uint32) bool { - return streamID&(1<<31) == 0 -} - -func http2validStreamID(streamID uint32) bool { - return streamID != 0 && streamID&(1<<31) == 0 -} - -// WriteData writes a DATA frame. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility not to violate the maximum frame size -// and to not call other Write methods concurrently. -func (f *http2Framer) WriteData(streamID uint32, endStream bool, data []byte) error { - return f.WriteDataPadded(streamID, endStream, data, nil) -} - -// WriteDataPadded writes a DATA frame with optional padding. -// -// If pad is nil, the padding bit is not sent. -// The length of pad must not exceed 255 bytes. -// The bytes of pad must all be zero, unless f.AllowIllegalWrites is set. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility not to violate the maximum frame size -// and to not call other Write methods concurrently. -func (f *http2Framer) WriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error { - if err := f.startWriteDataPadded(streamID, endStream, data, pad); err != nil { - return err - } - return f.endWrite() -} - -// startWriteDataPadded is WriteDataPadded, but only writes the frame to the Framer's internal buffer. -// The caller should call endWrite to flush the frame to the underlying writer. -func (f *http2Framer) startWriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error { - if !http2validStreamID(streamID) && !f.AllowIllegalWrites { - return http2errStreamID - } - if len(pad) > 0 { - if len(pad) > 255 { - return http2errPadLength - } - if !f.AllowIllegalWrites { - for _, b := range pad { - if b != 0 { - // "Padding octets MUST be set to zero when sending." - return http2errPadBytes - } - } - } - } - var flags http2Flags - if endStream { - flags |= http2FlagDataEndStream - } - if pad != nil { - flags |= http2FlagDataPadded - } - f.startWrite(http2FrameData, flags, streamID) - if pad != nil { - f.wbuf = append(f.wbuf, byte(len(pad))) - } - f.wbuf = append(f.wbuf, data...) - f.wbuf = append(f.wbuf, pad...) - return nil -} - -// A SettingsFrame conveys configuration parameters that affect how -// endpoints communicate, such as preferences and constraints on peer -// behavior. -// -// See https://httpwg.org/specs/rfc7540.html#SETTINGS -type http2SettingsFrame struct { - http2FrameHeader - p []byte -} - -func http2parseSettingsFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (http2Frame, error) { - if fh.Flags.Has(http2FlagSettingsAck) && fh.Length > 0 { - // When this (ACK 0x1) bit is set, the payload of the - // SETTINGS frame MUST be empty. Receipt of a - // SETTINGS frame with the ACK flag set and a length - // field value other than 0 MUST be treated as a - // connection error (Section 5.4.1) of type - // FRAME_SIZE_ERROR. - countError("frame_settings_ack_with_length") - return nil, http2ConnectionError(http2ErrCodeFrameSize) - } - if fh.StreamID != 0 { - // SETTINGS frames always apply to a connection, - // never a single stream. The stream identifier for a - // SETTINGS frame MUST be zero (0x0). If an endpoint - // receives a SETTINGS frame whose stream identifier - // field is anything other than 0x0, the endpoint MUST - // respond with a connection error (Section 5.4.1) of - // type PROTOCOL_ERROR. - countError("frame_settings_has_stream") - return nil, http2ConnectionError(http2ErrCodeProtocol) - } - if len(p)%6 != 0 { - countError("frame_settings_mod_6") - // Expecting even number of 6 byte settings. - return nil, http2ConnectionError(http2ErrCodeFrameSize) - } - f := &http2SettingsFrame{http2FrameHeader: fh, p: p} - if v, ok := f.Value(http2SettingInitialWindowSize); ok && v > (1<<31)-1 { - countError("frame_settings_window_size_too_big") - // Values above the maximum flow control window size of 2^31 - 1 MUST - // be treated as a connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR. - return nil, http2ConnectionError(http2ErrCodeFlowControl) - } - return f, nil -} - -func (f *http2SettingsFrame) IsAck() bool { - return f.http2FrameHeader.Flags.Has(http2FlagSettingsAck) -} - -func (f *http2SettingsFrame) Value(id http2SettingID) (v uint32, ok bool) { - f.checkValid() - for i := 0; i < f.NumSettings(); i++ { - if s := f.Setting(i); s.ID == id { - return s.Val, true - } - } - return 0, false -} - -// Setting returns the setting from the frame at the given 0-based index. -// The index must be >= 0 and less than f.NumSettings(). -func (f *http2SettingsFrame) Setting(i int) http2Setting { - buf := f.p - return http2Setting{ - ID: http2SettingID(binary.BigEndian.Uint16(buf[i*6 : i*6+2])), - Val: binary.BigEndian.Uint32(buf[i*6+2 : i*6+6]), - } -} - -func (f *http2SettingsFrame) NumSettings() int { return len(f.p) / 6 } - -// HasDuplicates reports whether f contains any duplicate setting IDs. -func (f *http2SettingsFrame) HasDuplicates() bool { - num := f.NumSettings() - if num == 0 { - return false - } - // If it's small enough (the common case), just do the n^2 - // thing and avoid a map allocation. - if num < 10 { - for i := 0; i < num; i++ { - idi := f.Setting(i).ID - for j := i + 1; j < num; j++ { - idj := f.Setting(j).ID - if idi == idj { - return true - } - } - } - return false - } - seen := map[http2SettingID]bool{} - for i := 0; i < num; i++ { - id := f.Setting(i).ID - if seen[id] { - return true - } - seen[id] = true - } - return false -} - -// ForeachSetting runs fn for each setting. -// It stops and returns the first error. -func (f *http2SettingsFrame) ForeachSetting(fn func(http2Setting) error) error { - f.checkValid() - for i := 0; i < f.NumSettings(); i++ { - if err := fn(f.Setting(i)); err != nil { - return err - } - } - return nil -} - -// WriteSettings writes a SETTINGS frame with zero or more settings -// specified and the ACK bit not set. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WriteSettings(settings ...http2Setting) error { - f.startWrite(http2FrameSettings, 0, 0) - for _, s := range settings { - f.writeUint16(uint16(s.ID)) - f.writeUint32(s.Val) - } - return f.endWrite() -} - -// WriteSettingsAck writes an empty SETTINGS frame with the ACK bit set. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WriteSettingsAck() error { - f.startWrite(http2FrameSettings, http2FlagSettingsAck, 0) - return f.endWrite() -} - -// A PingFrame is a mechanism for measuring a minimal round trip time -// from the sender, as well as determining whether an idle connection -// is still functional. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.7 -type http2PingFrame struct { - http2FrameHeader - Data [8]byte -} - -func (f *http2PingFrame) IsAck() bool { return f.Flags.Has(http2FlagPingAck) } - -func http2parsePingFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), payload []byte) (http2Frame, error) { - if len(payload) != 8 { - countError("frame_ping_length") - return nil, http2ConnectionError(http2ErrCodeFrameSize) - } - if fh.StreamID != 0 { - countError("frame_ping_has_stream") - return nil, http2ConnectionError(http2ErrCodeProtocol) - } - f := &http2PingFrame{http2FrameHeader: fh} - copy(f.Data[:], payload) - return f, nil -} - -func (f *http2Framer) WritePing(ack bool, data [8]byte) error { - var flags http2Flags - if ack { - flags = http2FlagPingAck - } - f.startWrite(http2FramePing, flags, 0) - f.writeBytes(data[:]) - return f.endWrite() -} - -// A GoAwayFrame informs the remote peer to stop creating streams on this connection. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.8 -type http2GoAwayFrame struct { - http2FrameHeader - LastStreamID uint32 - ErrCode http2ErrCode - debugData []byte -} - -// DebugData returns any debug data in the GOAWAY frame. Its contents -// are not defined. -// The caller must not retain the returned memory past the next -// call to ReadFrame. -func (f *http2GoAwayFrame) DebugData() []byte { - f.checkValid() - return f.debugData -} - -func http2parseGoAwayFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (http2Frame, error) { - if fh.StreamID != 0 { - countError("frame_goaway_has_stream") - return nil, http2ConnectionError(http2ErrCodeProtocol) - } - if len(p) < 8 { - countError("frame_goaway_short") - return nil, http2ConnectionError(http2ErrCodeFrameSize) - } - return &http2GoAwayFrame{ - http2FrameHeader: fh, - LastStreamID: binary.BigEndian.Uint32(p[:4]) & (1<<31 - 1), - ErrCode: http2ErrCode(binary.BigEndian.Uint32(p[4:8])), - debugData: p[8:], - }, nil -} - -func (f *http2Framer) WriteGoAway(maxStreamID uint32, code http2ErrCode, debugData []byte) error { - f.startWrite(http2FrameGoAway, 0, 0) - f.writeUint32(maxStreamID & (1<<31 - 1)) - f.writeUint32(uint32(code)) - f.writeBytes(debugData) - return f.endWrite() -} - -// An UnknownFrame is the frame type returned when the frame type is unknown -// or no specific frame type parser exists. -type http2UnknownFrame struct { - http2FrameHeader - p []byte -} - -// Payload returns the frame's payload (after the header). It is not -// valid to call this method after a subsequent call to -// Framer.ReadFrame, nor is it valid to retain the returned slice. -// The memory is owned by the Framer and is invalidated when the next -// frame is read. -func (f *http2UnknownFrame) Payload() []byte { - f.checkValid() - return f.p -} - -func http2parseUnknownFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (http2Frame, error) { - return &http2UnknownFrame{fh, p}, nil -} - -// A WindowUpdateFrame is used to implement flow control. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.9 -type http2WindowUpdateFrame struct { - http2FrameHeader - Increment uint32 // never read with high bit set -} - -func http2parseWindowUpdateFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (http2Frame, error) { - if len(p) != 4 { - countError("frame_windowupdate_bad_len") - return nil, http2ConnectionError(http2ErrCodeFrameSize) - } - inc := binary.BigEndian.Uint32(p[:4]) & 0x7fffffff // mask off high reserved bit - if inc == 0 { - // A receiver MUST treat the receipt of a - // WINDOW_UPDATE frame with an flow control window - // increment of 0 as a stream error (Section 5.4.2) of - // type PROTOCOL_ERROR; errors on the connection flow - // control window MUST be treated as a connection - // error (Section 5.4.1). - if fh.StreamID == 0 { - countError("frame_windowupdate_zero_inc_conn") - return nil, http2ConnectionError(http2ErrCodeProtocol) - } - countError("frame_windowupdate_zero_inc_stream") - return nil, http2streamError(fh.StreamID, http2ErrCodeProtocol) - } - return &http2WindowUpdateFrame{ - http2FrameHeader: fh, - Increment: inc, - }, nil -} - -// WriteWindowUpdate writes a WINDOW_UPDATE frame. -// The increment value must be between 1 and 2,147,483,647, inclusive. -// If the Stream ID is zero, the window update applies to the -// connection as a whole. -func (f *http2Framer) WriteWindowUpdate(streamID, incr uint32) error { - // "The legal range for the increment to the flow control window is 1 to 2^31-1 (2,147,483,647) octets." - if (incr < 1 || incr > 2147483647) && !f.AllowIllegalWrites { - return errors.New("illegal window increment value") - } - f.startWrite(http2FrameWindowUpdate, 0, streamID) - f.writeUint32(incr) - return f.endWrite() -} - -// A HeadersFrame is used to open a stream and additionally carries a -// header block fragment. -type http2HeadersFrame struct { - http2FrameHeader - - // Priority is set if FlagHeadersPriority is set in the FrameHeader. - Priority http2PriorityParam - - headerFragBuf []byte // not owned -} - -func (f *http2HeadersFrame) HeaderBlockFragment() []byte { - f.checkValid() - return f.headerFragBuf -} - -func (f *http2HeadersFrame) HeadersEnded() bool { - return f.http2FrameHeader.Flags.Has(http2FlagHeadersEndHeaders) -} - -func (f *http2HeadersFrame) StreamEnded() bool { - return f.http2FrameHeader.Flags.Has(http2FlagHeadersEndStream) -} - -func (f *http2HeadersFrame) HasPriority() bool { - return f.http2FrameHeader.Flags.Has(http2FlagHeadersPriority) -} - -func http2parseHeadersFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (_ http2Frame, err error) { - hf := &http2HeadersFrame{ - http2FrameHeader: fh, - } - if fh.StreamID == 0 { - // HEADERS frames MUST be associated with a stream. If a HEADERS frame - // is received whose stream identifier field is 0x0, the recipient MUST - // respond with a connection error (Section 5.4.1) of type - // PROTOCOL_ERROR. - countError("frame_headers_zero_stream") - return nil, http2connError{http2ErrCodeProtocol, "HEADERS frame with stream ID 0"} - } - var padLength uint8 - if fh.Flags.Has(http2FlagHeadersPadded) { - if p, padLength, err = http2readByte(p); err != nil { - countError("frame_headers_pad_short") - return - } - } - if fh.Flags.Has(http2FlagHeadersPriority) { - var v uint32 - p, v, err = http2readUint32(p) - if err != nil { - countError("frame_headers_prio_short") - return nil, err - } - hf.Priority.StreamDep = v & 0x7fffffff - hf.Priority.Exclusive = (v != hf.Priority.StreamDep) // high bit was set - p, hf.Priority.Weight, err = http2readByte(p) - if err != nil { - countError("frame_headers_prio_weight_short") - return nil, err - } - } - if len(p)-int(padLength) < 0 { - countError("frame_headers_pad_too_big") - return nil, http2streamError(fh.StreamID, http2ErrCodeProtocol) - } - hf.headerFragBuf = p[:len(p)-int(padLength)] - return hf, nil -} - -// HeadersFrameParam are the parameters for writing a HEADERS frame. -type http2HeadersFrameParam struct { - // StreamID is the required Stream ID to initiate. - StreamID uint32 - // BlockFragment is part (or all) of a Header Block. - BlockFragment []byte - - // EndStream indicates that the header block is the last that - // the endpoint will send for the identified stream. Setting - // this flag causes the stream to enter one of "half closed" - // states. - EndStream bool - - // EndHeaders indicates that this frame contains an entire - // header block and is not followed by any - // CONTINUATION frames. - EndHeaders bool - - // PadLength is the optional number of bytes of zeros to add - // to this frame. - PadLength uint8 - - // Priority, if non-zero, includes stream priority information - // in the HEADER frame. - Priority http2PriorityParam -} - -// WriteHeaders writes a single HEADERS frame. -// -// This is a low-level header writing method. Encoding headers and -// splitting them into any necessary CONTINUATION frames is handled -// elsewhere. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WriteHeaders(p http2HeadersFrameParam) error { - if !http2validStreamID(p.StreamID) && !f.AllowIllegalWrites { - return http2errStreamID - } - var flags http2Flags - if p.PadLength != 0 { - flags |= http2FlagHeadersPadded - } - if p.EndStream { - flags |= http2FlagHeadersEndStream - } - if p.EndHeaders { - flags |= http2FlagHeadersEndHeaders - } - if !p.Priority.IsZero() { - flags |= http2FlagHeadersPriority - } - f.startWrite(http2FrameHeaders, flags, p.StreamID) - if p.PadLength != 0 { - f.writeByte(p.PadLength) - } - if !p.Priority.IsZero() { - v := p.Priority.StreamDep - if !http2validStreamIDOrZero(v) && !f.AllowIllegalWrites { - return http2errDepStreamID - } - if p.Priority.Exclusive { - v |= 1 << 31 - } - f.writeUint32(v) - f.writeByte(p.Priority.Weight) - } - f.wbuf = append(f.wbuf, p.BlockFragment...) - f.wbuf = append(f.wbuf, http2padZeros[:p.PadLength]...) - return f.endWrite() -} - -// A PriorityFrame specifies the sender-advised priority of a stream. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.3 -type http2PriorityFrame struct { - http2FrameHeader - http2PriorityParam -} - -// defaultRFC9218Priority determines what priority we should use as the default -// value. -// -// According to RFC 9218, by default, streams should be given an urgency of 3 -// and should be non-incremental. However, making streams non-incremental by -// default would be a huge change to our historical behavior where we would -// round-robin writes across streams. When streams are non-incremental, we -// would process streams of the same urgency one-by-one to completion instead. -// -// To avoid such a sudden change which might break some HTTP/2 users, this -// function allows the caller to specify whether they can actually use the -// default value as specified in RFC 9218. If not, this function will return a -// priority value where streams are incremental by default instead: effectively -// a round-robin between stream of the same urgency. -// -// As an example, a server might not be able to use the RFC 9218 default value -// when it's not sure that the client it is serving is aware of RFC 9218. -func http2defaultRFC9218Priority(canUseDefault bool) http2PriorityParam { - if canUseDefault { - return http2PriorityParam{ - urgency: 3, - incremental: 0, - } - } - return http2PriorityParam{ - urgency: 3, - incremental: 1, - } -} - -// Note that HTTP/2 has had two different prioritization schemes, and -// PriorityParam struct below is a superset of both schemes. The exported -// symbols are from RFC 7540 and the non-exported ones are from RFC 9218. - -// PriorityParam are the stream prioritization parameters. -type http2PriorityParam struct { - // StreamDep is a 31-bit stream identifier for the - // stream that this stream depends on. Zero means no - // dependency. - StreamDep uint32 - - // Exclusive is whether the dependency is exclusive. - Exclusive bool - - // Weight is the stream's zero-indexed weight. It should be - // set together with StreamDep, or neither should be set. Per - // the spec, "Add one to the value to obtain a weight between - // 1 and 256." - Weight uint8 - - // "The urgency (u) parameter value is Integer (see Section 3.3.1 of - // [STRUCTURED-FIELDS]), between 0 and 7 inclusive, in descending order of - // priority. The default is 3." - urgency uint8 - - // "The incremental (i) parameter value is Boolean (see Section 3.3.6 of - // [STRUCTURED-FIELDS]). It indicates if an HTTP response can be processed - // incrementally, i.e., provide some meaningful output as chunks of the - // response arrive." - // - // We use uint8 (i.e. 0 is false, 1 is true) instead of bool so we can - // avoid unnecessary type conversions and because either type takes 1 byte. - incremental uint8 -} - -func (p http2PriorityParam) IsZero() bool { - return p == http2PriorityParam{} -} - -func http2parsePriorityFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), payload []byte) (http2Frame, error) { - if fh.StreamID == 0 { - countError("frame_priority_zero_stream") - return nil, http2connError{http2ErrCodeProtocol, "PRIORITY frame with stream ID 0"} - } - if len(payload) != 5 { - countError("frame_priority_bad_length") - return nil, http2connError{http2ErrCodeFrameSize, fmt.Sprintf("PRIORITY frame payload size was %d; want 5", len(payload))} - } - v := binary.BigEndian.Uint32(payload[:4]) - streamID := v & 0x7fffffff // mask off high bit - return &http2PriorityFrame{ - http2FrameHeader: fh, - http2PriorityParam: http2PriorityParam{ - Weight: payload[4], - StreamDep: streamID, - Exclusive: streamID != v, // was high bit set? - }, - }, nil -} - -// WritePriority writes a PRIORITY frame. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WritePriority(streamID uint32, p http2PriorityParam) error { - if !http2validStreamID(streamID) && !f.AllowIllegalWrites { - return http2errStreamID - } - if !http2validStreamIDOrZero(p.StreamDep) { - return http2errDepStreamID - } - f.startWrite(http2FramePriority, 0, streamID) - v := p.StreamDep - if p.Exclusive { - v |= 1 << 31 - } - f.writeUint32(v) - f.writeByte(p.Weight) - return f.endWrite() -} - -// PriorityUpdateFrame is a PRIORITY_UPDATE frame as described in -// https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame. -type http2PriorityUpdateFrame struct { - http2FrameHeader - Priority string - PrioritizedStreamID uint32 -} - -func http2parseRFC9218Priority(s string, canUseDefault bool) (p http2PriorityParam, ok bool) { - p = http2defaultRFC9218Priority(canUseDefault) - ok = httpsfv.ParseDictionary(s, func(key, val, _ string) { - switch key { - case "u": - if u, ok := httpsfv.ParseInteger(val); ok && u >= 0 && u <= 7 { - p.urgency = uint8(u) - } - case "i": - if i, ok := httpsfv.ParseBoolean(val); ok { - if i { - p.incremental = 1 - } else { - p.incremental = 0 - } - } - } - }) - if !ok { - return http2defaultRFC9218Priority(canUseDefault), ok - } - return p, true -} - -func http2parsePriorityUpdateFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), payload []byte) (http2Frame, error) { - if fh.StreamID != 0 { - countError("frame_priority_update_non_zero_stream") - return nil, http2connError{http2ErrCodeProtocol, "PRIORITY_UPDATE frame with non-zero stream ID"} - } - if len(payload) < 4 { - countError("frame_priority_update_bad_length") - return nil, http2connError{http2ErrCodeFrameSize, fmt.Sprintf("PRIORITY_UPDATE frame payload size was %d; want at least 4", len(payload))} - } - v := binary.BigEndian.Uint32(payload[:4]) - streamID := v & 0x7fffffff // mask off high bit - if streamID == 0 { - countError("frame_priority_update_prioritizing_zero_stream") - return nil, http2connError{http2ErrCodeProtocol, "PRIORITY_UPDATE frame with prioritized stream ID of zero"} - } - return &http2PriorityUpdateFrame{ - http2FrameHeader: fh, - PrioritizedStreamID: streamID, - Priority: string(payload[4:]), - }, nil -} - -// WritePriorityUpdate writes a PRIORITY_UPDATE frame. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WritePriorityUpdate(streamID uint32, priority string) error { - if !http2validStreamID(streamID) && !f.AllowIllegalWrites { - return http2errStreamID - } - f.startWrite(http2FramePriorityUpdate, 0, 0) - f.writeUint32(streamID) - f.writeBytes([]byte(priority)) - return f.endWrite() -} - -// A RSTStreamFrame allows for abnormal termination of a stream. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.4 -type http2RSTStreamFrame struct { - http2FrameHeader - ErrCode http2ErrCode -} - -func http2parseRSTStreamFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (http2Frame, error) { - if len(p) != 4 { - countError("frame_rststream_bad_len") - return nil, http2ConnectionError(http2ErrCodeFrameSize) - } - if fh.StreamID == 0 { - countError("frame_rststream_zero_stream") - return nil, http2ConnectionError(http2ErrCodeProtocol) - } - return &http2RSTStreamFrame{fh, http2ErrCode(binary.BigEndian.Uint32(p[:4]))}, nil -} - -// WriteRSTStream writes a RST_STREAM frame. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WriteRSTStream(streamID uint32, code http2ErrCode) error { - if !http2validStreamID(streamID) && !f.AllowIllegalWrites { - return http2errStreamID - } - f.startWrite(http2FrameRSTStream, 0, streamID) - f.writeUint32(uint32(code)) - return f.endWrite() -} - -// A ContinuationFrame is used to continue a sequence of header block fragments. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.10 -type http2ContinuationFrame struct { - http2FrameHeader - headerFragBuf []byte -} - -func http2parseContinuationFrame(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (http2Frame, error) { - if fh.StreamID == 0 { - countError("frame_continuation_zero_stream") - return nil, http2connError{http2ErrCodeProtocol, "CONTINUATION frame with stream ID 0"} - } - return &http2ContinuationFrame{fh, p}, nil -} - -func (f *http2ContinuationFrame) HeaderBlockFragment() []byte { - f.checkValid() - return f.headerFragBuf -} - -func (f *http2ContinuationFrame) HeadersEnded() bool { - return f.http2FrameHeader.Flags.Has(http2FlagContinuationEndHeaders) -} - -// WriteContinuation writes a CONTINUATION frame. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WriteContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) error { - if !http2validStreamID(streamID) && !f.AllowIllegalWrites { - return http2errStreamID - } - var flags http2Flags - if endHeaders { - flags |= http2FlagContinuationEndHeaders - } - f.startWrite(http2FrameContinuation, flags, streamID) - f.wbuf = append(f.wbuf, headerBlockFragment...) - return f.endWrite() -} - -// A PushPromiseFrame is used to initiate a server stream. -// See https://httpwg.org/specs/rfc7540.html#rfc.section.6.6 -type http2PushPromiseFrame struct { - http2FrameHeader - PromiseID uint32 - headerFragBuf []byte // not owned -} - -func (f *http2PushPromiseFrame) HeaderBlockFragment() []byte { - f.checkValid() - return f.headerFragBuf -} - -func (f *http2PushPromiseFrame) HeadersEnded() bool { - return f.http2FrameHeader.Flags.Has(http2FlagPushPromiseEndHeaders) -} - -func http2parsePushPromise(_ *http2frameCache, fh http2FrameHeader, countError func(string), p []byte) (_ http2Frame, err error) { - pp := &http2PushPromiseFrame{ - http2FrameHeader: fh, - } - if pp.StreamID == 0 { - // PUSH_PROMISE frames MUST be associated with an existing, - // peer-initiated stream. The stream identifier of a - // PUSH_PROMISE frame indicates the stream it is associated - // with. If the stream identifier field specifies the value - // 0x0, a recipient MUST respond with a connection error - // (Section 5.4.1) of type PROTOCOL_ERROR. - countError("frame_pushpromise_zero_stream") - return nil, http2ConnectionError(http2ErrCodeProtocol) - } - // The PUSH_PROMISE frame includes optional padding. - // Padding fields and flags are identical to those defined for DATA frames - var padLength uint8 - if fh.Flags.Has(http2FlagPushPromisePadded) { - if p, padLength, err = http2readByte(p); err != nil { - countError("frame_pushpromise_pad_short") - return - } - } - - p, pp.PromiseID, err = http2readUint32(p) - if err != nil { - countError("frame_pushpromise_promiseid_short") - return - } - pp.PromiseID = pp.PromiseID & (1<<31 - 1) - - if int(padLength) > len(p) { - // like the DATA frame, error out if padding is longer than the body. - countError("frame_pushpromise_pad_too_big") - return nil, http2ConnectionError(http2ErrCodeProtocol) - } - pp.headerFragBuf = p[:len(p)-int(padLength)] - return pp, nil -} - -// PushPromiseParam are the parameters for writing a PUSH_PROMISE frame. -type http2PushPromiseParam struct { - // StreamID is the required Stream ID to initiate. - StreamID uint32 - - // PromiseID is the required Stream ID which this - // Push Promises - PromiseID uint32 - - // BlockFragment is part (or all) of a Header Block. - BlockFragment []byte - - // EndHeaders indicates that this frame contains an entire - // header block and is not followed by any - // CONTINUATION frames. - EndHeaders bool - - // PadLength is the optional number of bytes of zeros to add - // to this frame. - PadLength uint8 -} - -// WritePushPromise writes a single PushPromise Frame. -// -// As with Header Frames, This is the low level call for writing -// individual frames. Continuation frames are handled elsewhere. -// -// It will perform exactly one Write to the underlying Writer. -// It is the caller's responsibility to not call other Write methods concurrently. -func (f *http2Framer) WritePushPromise(p http2PushPromiseParam) error { - if !http2validStreamID(p.StreamID) && !f.AllowIllegalWrites { - return http2errStreamID - } - var flags http2Flags - if p.PadLength != 0 { - flags |= http2FlagPushPromisePadded - } - if p.EndHeaders { - flags |= http2FlagPushPromiseEndHeaders - } - f.startWrite(http2FramePushPromise, flags, p.StreamID) - if p.PadLength != 0 { - f.writeByte(p.PadLength) - } - if !http2validStreamID(p.PromiseID) && !f.AllowIllegalWrites { - return http2errStreamID - } - f.writeUint32(p.PromiseID) - f.wbuf = append(f.wbuf, p.BlockFragment...) - f.wbuf = append(f.wbuf, http2padZeros[:p.PadLength]...) - return f.endWrite() -} - -// WriteRawFrame writes a raw frame. This can be used to write -// extension frames unknown to this package. -func (f *http2Framer) WriteRawFrame(t http2FrameType, flags http2Flags, streamID uint32, payload []byte) error { - f.startWrite(t, flags, streamID) - f.writeBytes(payload) - return f.endWrite() -} - -func http2readByte(p []byte) (remain []byte, b byte, err error) { - if len(p) == 0 { - return nil, 0, io.ErrUnexpectedEOF - } - return p[1:], p[0], nil -} - -func http2readUint32(p []byte) (remain []byte, v uint32, err error) { - if len(p) < 4 { - return nil, 0, io.ErrUnexpectedEOF - } - return p[4:], binary.BigEndian.Uint32(p[:4]), nil -} - -type http2streamEnder interface { - StreamEnded() bool -} - -type http2headersEnder interface { - HeadersEnded() bool -} - -type http2headersOrContinuation interface { - http2headersEnder - HeaderBlockFragment() []byte -} - -// A MetaHeadersFrame is the representation of one HEADERS frame and -// zero or more contiguous CONTINUATION frames and the decoding of -// their HPACK-encoded contents. -// -// This type of frame does not appear on the wire and is only returned -// by the Framer when Framer.ReadMetaHeaders is set. -type http2MetaHeadersFrame struct { - *http2HeadersFrame - - // Fields are the fields contained in the HEADERS and - // CONTINUATION frames. The underlying slice is owned by the - // Framer and must not be retained after the next call to - // ReadFrame. - // - // Fields are guaranteed to be in the correct http2 order and - // not have unknown pseudo header fields or invalid header - // field names or values. Required pseudo header fields may be - // missing, however. Use the MetaHeadersFrame.Pseudo accessor - // method access pseudo headers. - Fields []hpack.HeaderField - - // Truncated is whether the max header list size limit was hit - // and Fields is incomplete. The hpack decoder state is still - // valid, however. - Truncated bool -} - -// PseudoValue returns the given pseudo header field's value. -// The provided pseudo field should not contain the leading colon. -func (mh *http2MetaHeadersFrame) PseudoValue(pseudo string) string { - for _, hf := range mh.Fields { - if !hf.IsPseudo() { - return "" - } - if hf.Name[1:] == pseudo { - return hf.Value - } - } - return "" -} - -// RegularFields returns the regular (non-pseudo) header fields of mh. -// The caller does not own the returned slice. -func (mh *http2MetaHeadersFrame) RegularFields() []hpack.HeaderField { - for i, hf := range mh.Fields { - if !hf.IsPseudo() { - return mh.Fields[i:] - } - } - return nil -} - -// PseudoFields returns the pseudo header fields of mh. -// The caller does not own the returned slice. -func (mh *http2MetaHeadersFrame) PseudoFields() []hpack.HeaderField { - for i, hf := range mh.Fields { - if !hf.IsPseudo() { - return mh.Fields[:i] - } - } - return mh.Fields -} - -func (mh *http2MetaHeadersFrame) rfc9218Priority(priorityAware bool) (p http2PriorityParam, priorityAwareAfter, hasIntermediary bool) { - var s string - for _, field := range mh.Fields { - if field.Name == "priority" { - s = field.Value - priorityAware = true - } - if slices.Contains([]string{"via", "forwarded", "x-forwarded-for"}, field.Name) { - hasIntermediary = true - } - } - // No need to check for ok. parseRFC9218Priority will return a default - // value if there is no priority field or if the field cannot be parsed. - p, _ = http2parseRFC9218Priority(s, priorityAware && !hasIntermediary) - return p, priorityAware, hasIntermediary -} - -func (mh *http2MetaHeadersFrame) checkPseudos() error { - var isRequest, isResponse bool - pf := mh.PseudoFields() - for i, hf := range pf { - switch hf.Name { - case ":method", ":path", ":scheme", ":authority", ":protocol": - isRequest = true - case ":status": - isResponse = true - default: - return http2pseudoHeaderError(hf.Name) - } - // Check for duplicates. - // This would be a bad algorithm, but N is 5. - // And this doesn't allocate. - for _, hf2 := range pf[:i] { - if hf.Name == hf2.Name { - return http2duplicatePseudoHeaderError(hf.Name) - } - } - } - if isRequest && isResponse { - return http2errMixPseudoHeaderTypes - } - return nil -} - -func (fr *http2Framer) maxHeaderStringLen() int { - v := int(fr.maxHeaderListSize()) - if v < 0 { - // If maxHeaderListSize overflows an int, use no limit (0). - return 0 - } - return v -} - -// readMetaFrame returns 0 or more CONTINUATION frames from fr and -// merge them into the provided hf and returns a MetaHeadersFrame -// with the decoded hpack values. -func (fr *http2Framer) readMetaFrame(hf *http2HeadersFrame) (http2Frame, error) { - if fr.AllowIllegalReads { - return nil, errors.New("illegal use of AllowIllegalReads with ReadMetaHeaders") - } - mh := &http2MetaHeadersFrame{ - http2HeadersFrame: hf, - } - var remainSize = fr.maxHeaderListSize() - var sawRegular bool - - var invalid error // pseudo header field errors - hdec := fr.ReadMetaHeaders - hdec.SetEmitEnabled(true) - hdec.SetMaxStringLength(fr.maxHeaderStringLen()) - hdec.SetEmitFunc(func(hf hpack.HeaderField) { - if http2VerboseLogs && fr.logReads { - fr.debugReadLoggerf("http2: decoded hpack field %+v", hf) - } - if !httpguts.ValidHeaderFieldValue(hf.Value) { - // Don't include the value in the error, because it may be sensitive. - invalid = http2headerFieldValueError(hf.Name) - } - isPseudo := strings.HasPrefix(hf.Name, ":") - if isPseudo { - if sawRegular { - invalid = http2errPseudoAfterRegular - } - } else { - sawRegular = true - if !http2validWireHeaderFieldName(hf.Name) { - invalid = http2headerFieldNameError(hf.Name) - } - } - - if invalid != nil { - hdec.SetEmitEnabled(false) - return - } - - size := hf.Size() - if size > remainSize { - hdec.SetEmitEnabled(false) - mh.Truncated = true - remainSize = 0 - return - } - remainSize -= size - - mh.Fields = append(mh.Fields, hf) - }) - // Lose reference to MetaHeadersFrame: - defer hdec.SetEmitFunc(func(hf hpack.HeaderField) {}) - - var hc http2headersOrContinuation = hf - for { - frag := hc.HeaderBlockFragment() - - // Avoid parsing large amounts of headers that we will then discard. - // If the sender exceeds the max header list size by too much, - // skip parsing the fragment and close the connection. - // - // "Too much" is either any CONTINUATION frame after we've already - // exceeded the max header list size (in which case remainSize is 0), - // or a frame whose encoded size is more than twice the remaining - // header list bytes we're willing to accept. - if int64(len(frag)) > int64(2*remainSize) { - if http2VerboseLogs { - log.Printf("http2: header list too large") - } - // It would be nice to send a RST_STREAM before sending the GOAWAY, - // but the structure of the server's frame writer makes this difficult. - return mh, http2ConnectionError(http2ErrCodeProtocol) - } - - // Also close the connection after any CONTINUATION frame following an - // invalid header, since we stop tracking the size of the headers after - // an invalid one. - if invalid != nil { - if http2VerboseLogs { - log.Printf("http2: invalid header: %v", invalid) - } - // It would be nice to send a RST_STREAM before sending the GOAWAY, - // but the structure of the server's frame writer makes this difficult. - return mh, http2ConnectionError(http2ErrCodeProtocol) - } - - if _, err := hdec.Write(frag); err != nil { - return mh, http2ConnectionError(http2ErrCodeCompression) - } - - if hc.HeadersEnded() { - break - } - if f, err := fr.ReadFrame(); err != nil { - return nil, err - } else { - hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder - } - } - - mh.http2HeadersFrame.headerFragBuf = nil - mh.http2HeadersFrame.invalidate() - - if err := hdec.Close(); err != nil { - return mh, http2ConnectionError(http2ErrCodeCompression) - } - if invalid != nil { - fr.errDetail = invalid - if http2VerboseLogs { - log.Printf("http2: invalid header: %v", invalid) - } - return nil, http2StreamError{mh.StreamID, http2ErrCodeProtocol, invalid} - } - if err := mh.checkPseudos(); err != nil { - fr.errDetail = err - if http2VerboseLogs { - log.Printf("http2: invalid pseudo headers: %v", err) - } - return nil, http2StreamError{mh.StreamID, http2ErrCodeProtocol, err} - } - return mh, nil -} - -func http2summarizeFrame(f http2Frame) string { - var buf bytes.Buffer - f.Header().writeDebug(&buf) - switch f := f.(type) { - case *http2SettingsFrame: - n := 0 - f.ForeachSetting(func(s http2Setting) error { - n++ - if n == 1 { - buf.WriteString(", settings:") - } - fmt.Fprintf(&buf, " %v=%v,", s.ID, s.Val) - return nil - }) - if n > 0 { - buf.Truncate(buf.Len() - 1) // remove trailing comma - } - case *http2DataFrame: - data := f.Data() - const max = 256 - if len(data) > max { - data = data[:max] - } - fmt.Fprintf(&buf, " data=%q", data) - if len(f.Data()) > max { - fmt.Fprintf(&buf, " (%d bytes omitted)", len(f.Data())-max) - } - case *http2WindowUpdateFrame: - if f.StreamID == 0 { - buf.WriteString(" (conn)") - } - fmt.Fprintf(&buf, " incr=%v", f.Increment) - case *http2PingFrame: - fmt.Fprintf(&buf, " ping=%q", f.Data[:]) - case *http2GoAwayFrame: - fmt.Fprintf(&buf, " LastStreamID=%v ErrCode=%v Debug=%q", - f.LastStreamID, f.ErrCode, f.debugData) - case *http2RSTStreamFrame: - fmt.Fprintf(&buf, " ErrCode=%v", f.ErrCode) - } - return buf.String() -} - -var http2DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1" - -// Setting DebugGoroutines to false during a test to disable goroutine debugging -// results in race detector complaints when a test leaves goroutines running before -// returning. Tests shouldn't do this, of course, but when they do it generally shows -// up as infrequent, hard-to-debug flakes. (See #66519.) -// -// Disable goroutine debugging during individual tests with an atomic bool. -// (Note that it's safe to enable/disable debugging mid-test, so the actual race condition -// here is harmless.) -var http2disableDebugGoroutines atomic.Bool - -type http2goroutineLock uint64 - -func http2newGoroutineLock() http2goroutineLock { - if !http2DebugGoroutines || http2disableDebugGoroutines.Load() { - return 0 - } - return http2goroutineLock(http2curGoroutineID()) -} - -func (g http2goroutineLock) check() { - if !http2DebugGoroutines || http2disableDebugGoroutines.Load() { - return - } - if http2curGoroutineID() != uint64(g) { - panic("running on the wrong goroutine") - } -} - -func (g http2goroutineLock) checkNotOn() { - if !http2DebugGoroutines || http2disableDebugGoroutines.Load() { - return - } - if http2curGoroutineID() == uint64(g) { - panic("running on the wrong goroutine") - } -} - -var http2goroutineSpace = []byte("goroutine ") - -func http2curGoroutineID() uint64 { - bp := http2littleBuf.Get().(*[]byte) - defer http2littleBuf.Put(bp) - b := *bp - b = b[:runtime.Stack(b, false)] - // Parse the 4707 out of "goroutine 4707 [" - b = bytes.TrimPrefix(b, http2goroutineSpace) - i := bytes.IndexByte(b, ' ') - if i < 0 { - panic(fmt.Sprintf("No space found in %q", b)) - } - b = b[:i] - n, err := http2parseUintBytes(b, 10, 64) - if err != nil { - panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) - } - return n -} - -var http2littleBuf = sync.Pool{ - New: func() interface{} { - buf := make([]byte, 64) - return &buf - }, -} - -// parseUintBytes is like strconv.ParseUint, but using a []byte. -func http2parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) { - var cutoff, maxVal uint64 - - if bitSize == 0 { - bitSize = int(strconv.IntSize) - } - - s0 := s - switch { - case len(s) < 1: - err = strconv.ErrSyntax - goto Error - - case 2 <= base && base <= 36: - // valid base; nothing to do - - case base == 0: - // Look for octal, hex prefix. - switch { - case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): - base = 16 - s = s[2:] - if len(s) < 1 { - err = strconv.ErrSyntax - goto Error - } - case s[0] == '0': - base = 8 - default: - base = 10 - } - - default: - err = errors.New("invalid base " + strconv.Itoa(base)) - goto Error - } - - n = 0 - cutoff = http2cutoff64(base) - maxVal = 1<<uint(bitSize) - 1 - - for i := 0; i < len(s); i++ { - var v byte - d := s[i] - switch { - case '0' <= d && d <= '9': - v = d - '0' - case 'a' <= d && d <= 'z': - v = d - 'a' + 10 - case 'A' <= d && d <= 'Z': - v = d - 'A' + 10 - default: - n = 0 - err = strconv.ErrSyntax - goto Error - } - if int(v) >= base { - n = 0 - err = strconv.ErrSyntax - goto Error - } - - if n >= cutoff { - // n*base overflows - n = 1<<64 - 1 - err = strconv.ErrRange - goto Error - } - n *= uint64(base) - - n1 := n + uint64(v) - if n1 < n || n1 > maxVal { - // n+v overflows - n = 1<<64 - 1 - err = strconv.ErrRange - goto Error - } - n = n1 - } - - return n, nil - -Error: - return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} -} - -// Return the first number n such that n*base >= 1<<64. -func http2cutoff64(base int) uint64 { - if base < 2 { - return 0 - } - return (1<<64-1)/uint64(base) + 1 -} - -var ( - http2VerboseLogs bool - http2logFrameWrites bool - http2logFrameReads bool - - // Enabling extended CONNECT by causes browsers to attempt to use - // WebSockets-over-HTTP/2. This results in problems when the server's websocket - // package doesn't support extended CONNECT. - // - // Disable extended CONNECT by default for now. - // - // Issue #71128. - http2disableExtendedConnectProtocol = true -) - -func init() { - e := os.Getenv("GODEBUG") - if strings.Contains(e, "http2debug=1") { - http2VerboseLogs = true - } - if strings.Contains(e, "http2debug=2") { - http2VerboseLogs = true - http2logFrameWrites = true - http2logFrameReads = true - } - if strings.Contains(e, "http2xconnect=1") { - http2disableExtendedConnectProtocol = false - } -} - -const ( - // ClientPreface is the string that must be sent by new - // connections from clients. - http2ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" - - // SETTINGS_MAX_FRAME_SIZE default - // https://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2 - http2initialMaxFrameSize = 16384 - - // NextProtoTLS is the NPN/ALPN protocol negotiated during - // HTTP/2's TLS setup. - http2NextProtoTLS = "h2" - - // https://httpwg.org/specs/rfc7540.html#SettingValues - http2initialHeaderTableSize = 4096 - - http2initialWindowSize = 65535 // 6.9.2 Initial Flow Control Window Size - - http2defaultMaxReadFrameSize = 1 << 20 -) - -var ( - http2clientPreface = []byte(http2ClientPreface) -) - -type http2streamState int - -// HTTP/2 stream states. -// -// See http://tools.ietf.org/html/rfc7540#section-5.1. -// -// For simplicity, the server code merges "reserved (local)" into -// "half-closed (remote)". This is one less state transition to track. -// The only downside is that we send PUSH_PROMISEs slightly less -// liberally than allowable. More discussion here: -// https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0599.html -// -// "reserved (remote)" is omitted since the client code does not -// support server push. -const ( - http2stateIdle http2streamState = iota - http2stateOpen - http2stateHalfClosedLocal - http2stateHalfClosedRemote - http2stateClosed -) - -var http2stateName = [...]string{ - http2stateIdle: "Idle", - http2stateOpen: "Open", - http2stateHalfClosedLocal: "HalfClosedLocal", - http2stateHalfClosedRemote: "HalfClosedRemote", - http2stateClosed: "Closed", -} - -func (st http2streamState) String() string { - return http2stateName[st] -} - -// Setting is a setting parameter: which setting it is, and its value. -type http2Setting struct { - // ID is which setting is being set. - // See https://httpwg.org/specs/rfc7540.html#SettingFormat - ID http2SettingID - - // Val is the value. - Val uint32 -} - -func (s http2Setting) String() string { - return fmt.Sprintf("[%v = %d]", s.ID, s.Val) -} - -// Valid reports whether the setting is valid. -func (s http2Setting) Valid() error { - // Limits and error codes from 6.5.2 Defined SETTINGS Parameters - switch s.ID { - case http2SettingEnablePush: - if s.Val != 1 && s.Val != 0 { - return http2ConnectionError(http2ErrCodeProtocol) - } - case http2SettingInitialWindowSize: - if s.Val > 1<<31-1 { - return http2ConnectionError(http2ErrCodeFlowControl) - } - case http2SettingMaxFrameSize: - if s.Val < 16384 || s.Val > 1<<24-1 { - return http2ConnectionError(http2ErrCodeProtocol) - } - case http2SettingEnableConnectProtocol: - if s.Val != 1 && s.Val != 0 { - return http2ConnectionError(http2ErrCodeProtocol) - } - } - return nil -} - -// A SettingID is an HTTP/2 setting as defined in -// https://httpwg.org/specs/rfc7540.html#iana-settings -type http2SettingID uint16 - -const ( - http2SettingHeaderTableSize http2SettingID = 0x1 - http2SettingEnablePush http2SettingID = 0x2 - http2SettingMaxConcurrentStreams http2SettingID = 0x3 - http2SettingInitialWindowSize http2SettingID = 0x4 - http2SettingMaxFrameSize http2SettingID = 0x5 - http2SettingMaxHeaderListSize http2SettingID = 0x6 - http2SettingEnableConnectProtocol http2SettingID = 0x8 - http2SettingNoRFC7540Priorities http2SettingID = 0x9 -) - -var http2settingName = map[http2SettingID]string{ - http2SettingHeaderTableSize: "HEADER_TABLE_SIZE", - http2SettingEnablePush: "ENABLE_PUSH", - http2SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS", - http2SettingInitialWindowSize: "INITIAL_WINDOW_SIZE", - http2SettingMaxFrameSize: "MAX_FRAME_SIZE", - http2SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE", - http2SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL", - http2SettingNoRFC7540Priorities: "NO_RFC7540_PRIORITIES", -} - -func (s http2SettingID) String() string { - if v, ok := http2settingName[s]; ok { - return v - } - return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s)) -} - -// validWireHeaderFieldName reports whether v is a valid header field -// name (key). See httpguts.ValidHeaderName for the base rules. -// -// Further, http2 says: -// -// "Just as in HTTP/1.x, header field names are strings of ASCII -// characters that are compared in a case-insensitive -// fashion. However, header field names MUST be converted to -// lowercase prior to their encoding in HTTP/2. " -func http2validWireHeaderFieldName(v string) bool { - if len(v) == 0 { - return false - } - for _, r := range v { - if !httpguts.IsTokenRune(r) { - return false - } - if 'A' <= r && r <= 'Z' { - return false - } - } - return true -} - -func http2httpCodeString(code int) string { - switch code { - case 200: - return "200" - case 404: - return "404" - } - return strconv.Itoa(code) -} - -// from pkg io -type http2stringWriter interface { - WriteString(s string) (n int, err error) -} - -// A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed). -type http2closeWaiter chan struct{} - -// Init makes a closeWaiter usable. -// It exists because so a closeWaiter value can be placed inside a -// larger struct and have the Mutex and Cond's memory in the same -// allocation. -func (cw *http2closeWaiter) Init() { - *cw = make(chan struct{}) -} - -// Close marks the closeWaiter as closed and unblocks any waiters. -func (cw http2closeWaiter) Close() { - close(cw) -} - -// Wait waits for the closeWaiter to become closed. -func (cw http2closeWaiter) Wait() { - <-cw -} - -// bufferedWriter is a buffered writer that writes to w. -// Its buffered writer is lazily allocated as needed, to minimize -// idle memory usage with many connections. -type http2bufferedWriter struct { - _ http2incomparable - conn net.Conn // immutable - bw *bufio.Writer // non-nil when data is buffered - byteTimeout time.Duration // immutable, WriteByteTimeout -} - -func http2newBufferedWriter(conn net.Conn, timeout time.Duration) *http2bufferedWriter { - return &http2bufferedWriter{ - conn: conn, - byteTimeout: timeout, - } -} - -// bufWriterPoolBufferSize is the size of bufio.Writer's -// buffers created using bufWriterPool. -// -// TODO: pick a less arbitrary value? this is a bit under -// (3 x typical 1500 byte MTU) at least. Other than that, -// not much thought went into it. -const http2bufWriterPoolBufferSize = 4 << 10 - -var http2bufWriterPool = sync.Pool{ - New: func() interface{} { - return bufio.NewWriterSize(nil, http2bufWriterPoolBufferSize) - }, -} - -func (w *http2bufferedWriter) Available() int { - if w.bw == nil { - return http2bufWriterPoolBufferSize - } - return w.bw.Available() -} - -func (w *http2bufferedWriter) Write(p []byte) (n int, err error) { - if w.bw == nil { - bw := http2bufWriterPool.Get().(*bufio.Writer) - bw.Reset((*http2bufferedWriterTimeoutWriter)(w)) - w.bw = bw - } - return w.bw.Write(p) -} - -func (w *http2bufferedWriter) Flush() error { - bw := w.bw - if bw == nil { - return nil - } - err := bw.Flush() - bw.Reset(nil) - http2bufWriterPool.Put(bw) - w.bw = nil - return err -} - -type http2bufferedWriterTimeoutWriter http2bufferedWriter - -func (w *http2bufferedWriterTimeoutWriter) Write(p []byte) (n int, err error) { - return http2writeWithByteTimeout(w.conn, w.byteTimeout, p) -} - -// writeWithByteTimeout writes to conn. -// If more than timeout passes without any bytes being written to the connection, -// the write fails. -func http2writeWithByteTimeout(conn net.Conn, timeout time.Duration, p []byte) (n int, err error) { - if timeout <= 0 { - return conn.Write(p) - } - for { - conn.SetWriteDeadline(time.Now().Add(timeout)) - nn, err := conn.Write(p[n:]) - n += nn - if n == len(p) || nn == 0 || !errors.Is(err, os.ErrDeadlineExceeded) { - // Either we finished the write, made no progress, or hit the deadline. - // Whichever it is, we're done now. - conn.SetWriteDeadline(time.Time{}) - return n, err - } - } -} - -func http2mustUint31(v int32) uint32 { - if v < 0 || v > 2147483647 { - panic("out of range") - } - return uint32(v) -} - -// bodyAllowedForStatus reports whether a given response status code -// permits a body. See RFC 7230, section 3.3. -func http2bodyAllowedForStatus(status int) bool { - switch { - case status >= 100 && status <= 199: - return false - case status == 204: - return false - case status == 304: - return false - } - return true -} - -type http2httpError struct { - _ http2incomparable - msg string - timeout bool -} - -func (e *http2httpError) Error() string { return e.msg } - -func (e *http2httpError) Timeout() bool { return e.timeout } - -func (e *http2httpError) Temporary() bool { return true } - -var http2errTimeout error = &http2httpError{msg: "http2: timeout awaiting response headers", timeout: true} - -type http2connectionStater interface { - ConnectionState() tls.ConnectionState -} - -var http2sorterPool = sync.Pool{New: func() interface{} { return new(http2sorter) }} - -type http2sorter struct { - v []string // owned by sorter -} - -func (s *http2sorter) Len() int { return len(s.v) } - -func (s *http2sorter) Swap(i, j int) { s.v[i], s.v[j] = s.v[j], s.v[i] } - -func (s *http2sorter) Less(i, j int) bool { return s.v[i] < s.v[j] } - -// Keys returns the sorted keys of h. -// -// The returned slice is only valid until s used again or returned to -// its pool. -func (s *http2sorter) Keys(h Header) []string { - keys := s.v[:0] - for k := range h { - keys = append(keys, k) - } - s.v = keys - sort.Sort(s) - return keys -} - -func (s *http2sorter) SortStrings(ss []string) { - // Our sorter works on s.v, which sorter owns, so - // stash it away while we sort the user's buffer. - save := s.v - s.v = ss - sort.Sort(s) - s.v = save -} - -// incomparable is a zero-width, non-comparable type. Adding it to a struct -// makes that struct also non-comparable, and generally doesn't add -// any size (as long as it's first). -type http2incomparable [0]func() - -// pipe is a goroutine-safe io.Reader/io.Writer pair. It's like -// io.Pipe except there are no PipeReader/PipeWriter halves, and the -// underlying buffer is an interface. (io.Pipe is always unbuffered) -type http2pipe struct { - mu sync.Mutex - c sync.Cond // c.L lazily initialized to &p.mu - b http2pipeBuffer // nil when done reading - unread int // bytes unread when done - err error // read error once empty. non-nil means closed. - breakErr error // immediate read error (caller doesn't see rest of b) - donec chan struct{} // closed on error - readFn func() // optional code to run in Read before error -} - -type http2pipeBuffer interface { - Len() int - io.Writer - io.Reader -} - -// setBuffer initializes the pipe buffer. -// It has no effect if the pipe is already closed. -func (p *http2pipe) setBuffer(b http2pipeBuffer) { - p.mu.Lock() - defer p.mu.Unlock() - if p.err != nil || p.breakErr != nil { - return - } - p.b = b -} - -func (p *http2pipe) Len() int { - p.mu.Lock() - defer p.mu.Unlock() - if p.b == nil { - return p.unread - } - return p.b.Len() -} - -// Read waits until data is available and copies bytes -// from the buffer into p. -func (p *http2pipe) Read(d []byte) (n int, err error) { - p.mu.Lock() - defer p.mu.Unlock() - if p.c.L == nil { - p.c.L = &p.mu - } - for { - if p.breakErr != nil { - return 0, p.breakErr - } - if p.b != nil && p.b.Len() > 0 { - return p.b.Read(d) - } - if p.err != nil { - if p.readFn != nil { - p.readFn() // e.g. copy trailers - p.readFn = nil // not sticky like p.err - } - p.b = nil - return 0, p.err - } - p.c.Wait() - } -} - -var ( - http2errClosedPipeWrite = errors.New("write on closed buffer") - http2errUninitializedPipeWrite = errors.New("write on uninitialized buffer") -) - -// Write copies bytes from p into the buffer and wakes a reader. -// It is an error to write more data than the buffer can hold. -func (p *http2pipe) Write(d []byte) (n int, err error) { - p.mu.Lock() - defer p.mu.Unlock() - if p.c.L == nil { - p.c.L = &p.mu - } - defer p.c.Signal() - if p.err != nil || p.breakErr != nil { - return 0, http2errClosedPipeWrite - } - // pipe.setBuffer is never invoked, leaving the buffer uninitialized. - // We shouldn't try to write to an uninitialized pipe, - // but returning an error is better than panicking. - if p.b == nil { - return 0, http2errUninitializedPipeWrite - } - return p.b.Write(d) -} - -// CloseWithError causes the next Read (waking up a current blocked -// Read if needed) to return the provided err after all data has been -// read. -// -// The error must be non-nil. -func (p *http2pipe) CloseWithError(err error) { p.closeWithError(&p.err, err, nil) } - -// BreakWithError causes the next Read (waking up a current blocked -// Read if needed) to return the provided err immediately, without -// waiting for unread data. -func (p *http2pipe) BreakWithError(err error) { p.closeWithError(&p.breakErr, err, nil) } - -// closeWithErrorAndCode is like CloseWithError but also sets some code to run -// in the caller's goroutine before returning the error. -func (p *http2pipe) closeWithErrorAndCode(err error, fn func()) { p.closeWithError(&p.err, err, fn) } - -func (p *http2pipe) closeWithError(dst *error, err error, fn func()) { - if err == nil { - panic("err must be non-nil") - } - p.mu.Lock() - defer p.mu.Unlock() - if p.c.L == nil { - p.c.L = &p.mu - } - defer p.c.Signal() - if *dst != nil { - // Already been done. - return - } - p.readFn = fn - if dst == &p.breakErr { - if p.b != nil { - p.unread += p.b.Len() - } - p.b = nil - } - *dst = err - p.closeDoneLocked() -} - -// requires p.mu be held. -func (p *http2pipe) closeDoneLocked() { - if p.donec == nil { - return - } - // Close if unclosed. This isn't racy since we always - // hold p.mu while closing. - select { - case <-p.donec: - default: - close(p.donec) - } -} - -// Err returns the error (if any) first set by BreakWithError or CloseWithError. -func (p *http2pipe) Err() error { - p.mu.Lock() - defer p.mu.Unlock() - if p.breakErr != nil { - return p.breakErr - } - return p.err -} - -// Done returns a channel which is closed if and when this pipe is closed -// with CloseWithError. -func (p *http2pipe) Done() <-chan struct{} { - p.mu.Lock() - defer p.mu.Unlock() - if p.donec == nil { - p.donec = make(chan struct{}) - if p.err != nil || p.breakErr != nil { - // Already hit an error. - p.closeDoneLocked() - } - } - return p.donec -} - -const ( - http2prefaceTimeout = 10 * time.Second - http2firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway - http2handlerChunkWriteSize = 4 << 10 - http2defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to? - - // maxQueuedControlFrames is the maximum number of control frames like - // SETTINGS, PING and RST_STREAM that will be queued for writing before - // the connection is closed to prevent memory exhaustion attacks. - http2maxQueuedControlFrames = 10000 -) - -var ( - http2errClientDisconnected = errors.New("client disconnected") - http2errClosedBody = errors.New("body closed by handler") - http2errHandlerComplete = errors.New("http2: request body closed due to handler exiting") - http2errStreamClosed = errors.New("http2: stream closed") -) - -var http2responseWriterStatePool = sync.Pool{ - New: func() interface{} { - rws := &http2responseWriterState{} - rws.bw = bufio.NewWriterSize(http2chunkWriter{rws}, http2handlerChunkWriteSize) - return rws - }, -} - -// Test hooks. -var ( - http2testHookOnConn func() - http2testHookGetServerConn func(*http2serverConn) - http2testHookOnPanicMu *sync.Mutex // nil except in tests - http2testHookOnPanic func(sc *http2serverConn, panicVal interface{}) (rePanic bool) -) - -// Server is an HTTP/2 server. -type http2Server struct { - // MaxHandlers limits the number of http.Handler ServeHTTP goroutines - // which may run at a time over all connections. - // Negative or zero no limit. - // TODO: implement - MaxHandlers int - - // MaxConcurrentStreams optionally specifies the number of - // concurrent streams that each client may have open at a - // time. This is unrelated to the number of http.Handler goroutines - // which may be active globally, which is MaxHandlers. - // If zero, MaxConcurrentStreams defaults to at least 100, per - // the HTTP/2 spec's recommendations. - MaxConcurrentStreams uint32 - - // MaxDecoderHeaderTableSize optionally specifies the http2 - // SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It - // informs the remote endpoint of the maximum size of the header compression - // table used to decode header blocks, in octets. If zero, the default value - // of 4096 is used. - MaxDecoderHeaderTableSize uint32 - - // MaxEncoderHeaderTableSize optionally specifies an upper limit for the - // header compression table used for encoding request headers. Received - // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero, - // the default value of 4096 is used. - MaxEncoderHeaderTableSize uint32 - - // MaxReadFrameSize optionally specifies the largest frame - // this server is willing to read. A valid value is between - // 16k and 16M, inclusive. If zero or otherwise invalid, a - // default value is used. - MaxReadFrameSize uint32 - - // PermitProhibitedCipherSuites, if true, permits the use of - // cipher suites prohibited by the HTTP/2 spec. - PermitProhibitedCipherSuites bool - - // IdleTimeout specifies how long until idle clients should be - // closed with a GOAWAY frame. PING frames are not considered - // activity for the purposes of IdleTimeout. - // If zero or negative, there is no timeout. - IdleTimeout time.Duration - - // ReadIdleTimeout is the timeout after which a health check using a ping - // frame will be carried out if no frame is received on the connection. - // If zero, no health check is performed. - ReadIdleTimeout time.Duration - - // PingTimeout is the timeout after which the connection will be closed - // if a response to a ping is not received. - // If zero, a default of 15 seconds is used. - PingTimeout time.Duration - - // WriteByteTimeout is the timeout after which a connection will be - // closed if no data can be written to it. The timeout begins when data is - // available to write, and is extended whenever any bytes are written. - // If zero or negative, there is no timeout. - WriteByteTimeout time.Duration - - // MaxUploadBufferPerConnection is the size of the initial flow - // control window for each connections. The HTTP/2 spec does not - // allow this to be smaller than 65535 or larger than 2^32-1. - // If the value is outside this range, a default value will be - // used instead. - MaxUploadBufferPerConnection int32 - - // MaxUploadBufferPerStream is the size of the initial flow control - // window for each stream. The HTTP/2 spec does not allow this to - // be larger than 2^32-1. If the value is zero or larger than the - // maximum, a default value will be used instead. - MaxUploadBufferPerStream int32 - - // NewWriteScheduler constructs a write scheduler for a connection. - // If nil, a default scheduler is chosen. - NewWriteScheduler func() http2WriteScheduler - - // CountError, if non-nil, is called on HTTP/2 server errors. - // It's intended to increment a metric for monitoring, such - // as an expvar or Prometheus metric. - // The errType consists of only ASCII word characters. - CountError func(errType string) - - // Internal state. This is a pointer (rather than embedded directly) - // so that we don't embed a Mutex in this struct, which will make the - // struct non-copyable, which might break some callers. - state *http2serverInternalState -} - -type http2serverInternalState struct { - mu sync.Mutex - activeConns map[*http2serverConn]struct{} - - // Pool of error channels. This is per-Server rather than global - // because channels can't be reused across synctest bubbles. - errChanPool sync.Pool -} - -func (s *http2serverInternalState) registerConn(sc *http2serverConn) { - if s == nil { - return // if the Server was used without calling ConfigureServer - } - s.mu.Lock() - s.activeConns[sc] = struct{}{} - s.mu.Unlock() -} - -func (s *http2serverInternalState) unregisterConn(sc *http2serverConn) { - if s == nil { - return // if the Server was used without calling ConfigureServer - } - s.mu.Lock() - delete(s.activeConns, sc) - s.mu.Unlock() -} - -func (s *http2serverInternalState) startGracefulShutdown() { - if s == nil { - return // if the Server was used without calling ConfigureServer - } - s.mu.Lock() - for sc := range s.activeConns { - sc.startGracefulShutdown() - } - s.mu.Unlock() -} - -// Global error channel pool used for uninitialized Servers. -// We use a per-Server pool when possible to avoid using channels across synctest bubbles. -var http2errChanPool = sync.Pool{ - New: func() any { return make(chan error, 1) }, -} - -func (s *http2serverInternalState) getErrChan() chan error { - if s == nil { - return http2errChanPool.Get().(chan error) // Server used without calling ConfigureServer - } - return s.errChanPool.Get().(chan error) -} - -func (s *http2serverInternalState) putErrChan(ch chan error) { - if s == nil { - http2errChanPool.Put(ch) // Server used without calling ConfigureServer - return - } - s.errChanPool.Put(ch) -} - -// ConfigureServer adds HTTP/2 support to a net/http Server. -// -// The configuration conf may be nil. -// -// ConfigureServer must be called before s begins serving. -func http2ConfigureServer(s *Server, conf *http2Server) error { - if s == nil { - panic("nil *http.Server") - } - if conf == nil { - conf = new(http2Server) - } - conf.state = &http2serverInternalState{ - activeConns: make(map[*http2serverConn]struct{}), - errChanPool: sync.Pool{New: func() any { return make(chan error, 1) }}, - } - if h1, h2 := s, conf; h2.IdleTimeout == 0 { - if h1.IdleTimeout != 0 { - h2.IdleTimeout = h1.IdleTimeout - } else { - h2.IdleTimeout = h1.ReadTimeout - } - } - s.RegisterOnShutdown(conf.state.startGracefulShutdown) - - if s.TLSConfig == nil { - s.TLSConfig = new(tls.Config) - } else if s.TLSConfig.CipherSuites != nil && s.TLSConfig.MinVersion < tls.VersionTLS13 { - // If they already provided a TLS 1.0–1.2 CipherSuite list, return an - // error if it is missing ECDHE_RSA_WITH_AES_128_GCM_SHA256 or - // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256. - haveRequired := false - for _, cs := range s.TLSConfig.CipherSuites { - switch cs { - case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - // Alternative MTI cipher to not discourage ECDSA-only servers. - // See http://golang.org/cl/30721 for further information. - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: - haveRequired = true - } - } - if !haveRequired { - return fmt.Errorf("http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher (need at least one of TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)") - } - } - - // Note: not setting MinVersion to tls.VersionTLS12, - // as we don't want to interfere with HTTP/1.1 traffic - // on the user's server. We enforce TLS 1.2 later once - // we accept a connection. Ideally this should be done - // during next-proto selection, but using TLS <1.2 with - // HTTP/2 is still the client's bug. - - s.TLSConfig.PreferServerCipherSuites = true - - if !http2strSliceContains(s.TLSConfig.NextProtos, http2NextProtoTLS) { - s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, http2NextProtoTLS) - } - if !http2strSliceContains(s.TLSConfig.NextProtos, "http/1.1") { - s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "http/1.1") - } - - if s.TLSNextProto == nil { - s.TLSNextProto = map[string]func(*Server, *tls.Conn, Handler){} - } - protoHandler := func(hs *Server, c net.Conn, h Handler, sawClientPreface bool) { - if http2testHookOnConn != nil { - http2testHookOnConn() - } - // The TLSNextProto interface predates contexts, so - // the net/http package passes down its per-connection - // base context via an exported but unadvertised - // method on the Handler. This is for internal - // net/http<=>http2 use only. - var ctx context.Context - type baseContexter interface { - BaseContext() context.Context - } - if bc, ok := h.(baseContexter); ok { - ctx = bc.BaseContext() - } - conf.ServeConn(c, &http2ServeConnOpts{ - Context: ctx, - Handler: h, - BaseConfig: hs, - SawClientPreface: sawClientPreface, - }) - } - s.TLSNextProto[http2NextProtoTLS] = func(hs *Server, c *tls.Conn, h Handler) { - protoHandler(hs, c, h, false) - } - // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns. - // - // A connection passed in this method has already had the HTTP/2 preface read from it. - s.TLSNextProto[http2nextProtoUnencryptedHTTP2] = func(hs *Server, c *tls.Conn, h Handler) { - nc, err := http2unencryptedNetConnFromTLSConn(c) - if err != nil { - if lg := hs.ErrorLog; lg != nil { - lg.Print(err) - } else { - log.Print(err) - } - go c.Close() - return - } - protoHandler(hs, nc, h, true) - } - return nil -} - -// ServeConnOpts are options for the Server.ServeConn method. -type http2ServeConnOpts struct { - // Context is the base context to use. - // If nil, context.Background is used. - Context context.Context - - // BaseConfig optionally sets the base configuration - // for values. If nil, defaults are used. - BaseConfig *Server - - // Handler specifies which handler to use for processing - // requests. If nil, BaseConfig.Handler is used. If BaseConfig - // or BaseConfig.Handler is nil, http.DefaultServeMux is used. - Handler Handler - - // UpgradeRequest is an initial request received on a connection - // undergoing an h2c upgrade. The request body must have been - // completely read from the connection before calling ServeConn, - // and the 101 Switching Protocols response written. - UpgradeRequest *Request - - // Settings is the decoded contents of the HTTP2-Settings header - // in an h2c upgrade request. - Settings []byte - - // SawClientPreface is set if the HTTP/2 connection preface - // has already been read from the connection. - SawClientPreface bool -} - -func (o *http2ServeConnOpts) context() context.Context { - if o != nil && o.Context != nil { - return o.Context - } - return context.Background() -} - -func (o *http2ServeConnOpts) baseConfig() *Server { - if o != nil && o.BaseConfig != nil { - return o.BaseConfig - } - return new(Server) -} - -func (o *http2ServeConnOpts) handler() Handler { - if o != nil { - if o.Handler != nil { - return o.Handler - } - if o.BaseConfig != nil && o.BaseConfig.Handler != nil { - return o.BaseConfig.Handler - } - } - return DefaultServeMux -} - -// ServeConn serves HTTP/2 requests on the provided connection and -// blocks until the connection is no longer readable. -// -// ServeConn starts speaking HTTP/2 assuming that c has not had any -// reads or writes. It writes its initial settings frame and expects -// to be able to read the preface and settings frame from the -// client. If c has a ConnectionState method like a *tls.Conn, the -// ConnectionState is used to verify the TLS ciphersuite and to set -// the Request.TLS field in Handlers. -// -// ServeConn does not support h2c by itself. Any h2c support must be -// implemented in terms of providing a suitably-behaving net.Conn. -// -// The opts parameter is optional. If nil, default values are used. -func (s *http2Server) ServeConn(c net.Conn, opts *http2ServeConnOpts) { - if opts == nil { - opts = &http2ServeConnOpts{} - } - s.serveConn(c, opts, nil) -} - -func (s *http2Server) serveConn(c net.Conn, opts *http2ServeConnOpts, newf func(*http2serverConn)) { - baseCtx, cancel := http2serverConnBaseContext(c, opts) - defer cancel() - - http1srv := opts.baseConfig() - conf := http2configFromServer(http1srv, s) - sc := &http2serverConn{ - srv: s, - hs: http1srv, - conn: c, - baseCtx: baseCtx, - remoteAddrStr: c.RemoteAddr().String(), - bw: http2newBufferedWriter(c, conf.WriteByteTimeout), - handler: opts.handler(), - streams: make(map[uint32]*http2stream), - readFrameCh: make(chan http2readFrameResult), - wantWriteFrameCh: make(chan http2FrameWriteRequest, 8), - serveMsgCh: make(chan interface{}, 8), - wroteFrameCh: make(chan http2frameWriteResult, 1), // buffered; one send in writeFrameAsync - bodyReadCh: make(chan http2bodyReadMsg), // buffering doesn't matter either way - doneServing: make(chan struct{}), - clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value" - advMaxStreams: conf.MaxConcurrentStreams, - initialStreamSendWindowSize: http2initialWindowSize, - initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, - maxFrameSize: http2initialMaxFrameSize, - pingTimeout: conf.PingTimeout, - countErrorFunc: conf.CountError, - serveG: http2newGoroutineLock(), - pushEnabled: true, - sawClientPreface: opts.SawClientPreface, - } - if newf != nil { - newf(sc) - } - - s.state.registerConn(sc) - defer s.state.unregisterConn(sc) - - // The net/http package sets the write deadline from the - // http.Server.WriteTimeout during the TLS handshake, but then - // passes the connection off to us with the deadline already set. - // Write deadlines are set per stream in serverConn.newStream. - // Disarm the net.Conn write deadline here. - if sc.hs.WriteTimeout > 0 { - sc.conn.SetWriteDeadline(time.Time{}) - } - - switch { - case s.NewWriteScheduler != nil: - sc.writeSched = s.NewWriteScheduler() - case http2clientPriorityDisabled(http1srv): - sc.writeSched = http2newRoundRobinWriteScheduler() - default: - sc.writeSched = http2newPriorityWriteSchedulerRFC9218() - } - - // These start at the RFC-specified defaults. If there is a higher - // configured value for inflow, that will be updated when we send a - // WINDOW_UPDATE shortly after sending SETTINGS. - sc.flow.add(http2initialWindowSize) - sc.inflow.init(http2initialWindowSize) - sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf) - sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) - - fr := http2NewFramer(sc.bw, c) - if conf.CountError != nil { - fr.countError = conf.CountError - } - fr.ReadMetaHeaders = hpack.NewDecoder(conf.MaxDecoderHeaderTableSize, nil) - fr.MaxHeaderListSize = sc.maxHeaderListSize() - fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) - sc.framer = fr - - if tc, ok := c.(http2connectionStater); ok { - sc.tlsState = new(tls.ConnectionState) - *sc.tlsState = tc.ConnectionState() - // 9.2 Use of TLS Features - // An implementation of HTTP/2 over TLS MUST use TLS - // 1.2 or higher with the restrictions on feature set - // and cipher suite described in this section. Due to - // implementation limitations, it might not be - // possible to fail TLS negotiation. An endpoint MUST - // immediately terminate an HTTP/2 connection that - // does not meet the TLS requirements described in - // this section with a connection error (Section - // 5.4.1) of type INADEQUATE_SECURITY. - if sc.tlsState.Version < tls.VersionTLS12 { - sc.rejectConn(http2ErrCodeInadequateSecurity, "TLS version too low") - return - } - - if sc.tlsState.ServerName == "" { - // Client must use SNI, but we don't enforce that anymore, - // since it was causing problems when connecting to bare IP - // addresses during development. - // - // TODO: optionally enforce? Or enforce at the time we receive - // a new request, and verify the ServerName matches the :authority? - // But that precludes proxy situations, perhaps. - // - // So for now, do nothing here again. - } - - if !conf.PermitProhibitedCipherSuites && http2isBadCipher(sc.tlsState.CipherSuite) { - // "Endpoints MAY choose to generate a connection error - // (Section 5.4.1) of type INADEQUATE_SECURITY if one of - // the prohibited cipher suites are negotiated." - // - // We choose that. In my opinion, the spec is weak - // here. It also says both parties must support at least - // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 so there's no - // excuses here. If we really must, we could allow an - // "AllowInsecureWeakCiphers" option on the server later. - // Let's see how it plays out first. - sc.rejectConn(http2ErrCodeInadequateSecurity, fmt.Sprintf("Prohibited TLS 1.2 Cipher Suite: %x", sc.tlsState.CipherSuite)) - return - } - } - - if opts.Settings != nil { - fr := &http2SettingsFrame{ - http2FrameHeader: http2FrameHeader{valid: true}, - p: opts.Settings, - } - if err := fr.ForeachSetting(sc.processSetting); err != nil { - sc.rejectConn(http2ErrCodeProtocol, "invalid settings") - return - } - opts.Settings = nil - } - - if hook := http2testHookGetServerConn; hook != nil { - hook(sc) - } - - if opts.UpgradeRequest != nil { - sc.upgradeRequest(opts.UpgradeRequest) - opts.UpgradeRequest = nil - } - - sc.serve(conf) -} - -func http2serverConnBaseContext(c net.Conn, opts *http2ServeConnOpts) (ctx context.Context, cancel func()) { - ctx, cancel = context.WithCancel(opts.context()) - ctx = context.WithValue(ctx, LocalAddrContextKey, c.LocalAddr()) - if hs := opts.baseConfig(); hs != nil { - ctx = context.WithValue(ctx, ServerContextKey, hs) - } - return -} - -func (sc *http2serverConn) rejectConn(err http2ErrCode, debug string) { - sc.vlogf("http2: server rejecting conn: %v, %s", err, debug) - // ignoring errors. hanging up anyway. - sc.framer.WriteGoAway(0, err, []byte(debug)) - sc.bw.Flush() - sc.conn.Close() -} - -type http2serverConn struct { - // Immutable: - srv *http2Server - hs *Server - conn net.Conn - bw *http2bufferedWriter // writing to conn - handler Handler - baseCtx context.Context - framer *http2Framer - doneServing chan struct{} // closed when serverConn.serve ends - readFrameCh chan http2readFrameResult // written by serverConn.readFrames - wantWriteFrameCh chan http2FrameWriteRequest // from handlers -> serve - wroteFrameCh chan http2frameWriteResult // from writeFrameAsync -> serve, tickles more frame writes - bodyReadCh chan http2bodyReadMsg // from handlers -> serve - serveMsgCh chan interface{} // misc messages & code to send to / run on the serve loop - flow http2outflow // conn-wide (not stream-specific) outbound flow control - inflow http2inflow // conn-wide inbound flow control - tlsState *tls.ConnectionState // shared by all handlers, like net/http - remoteAddrStr string - writeSched http2WriteScheduler - countErrorFunc func(errType string) - - // Everything following is owned by the serve loop; use serveG.check(): - serveG http2goroutineLock // used to verify funcs are on serve() - pushEnabled bool - sawClientPreface bool // preface has already been read, used in h2c upgrade - sawFirstSettings bool // got the initial SETTINGS frame after the preface - needToSendSettingsAck bool - unackedSettings int // how many SETTINGS have we sent without ACKs? - queuedControlFrames int // control frames in the writeSched queue - clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit) - advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client - curClientStreams uint32 // number of open streams initiated by the client - curPushedStreams uint32 // number of open streams initiated by server push - curHandlers uint32 // number of running handler goroutines - maxClientStreamID uint32 // max ever seen from client (odd), or 0 if there have been no client requests - maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes - streams map[uint32]*http2stream - unstartedHandlers []http2unstartedHandler - initialStreamSendWindowSize int32 - initialStreamRecvWindowSize int32 - maxFrameSize int32 - peerMaxHeaderListSize uint32 // zero means unknown (default) - canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case - canonHeaderKeysSize int // canonHeader keys size in bytes - writingFrame bool // started writing a frame (on serve goroutine or separate) - writingFrameAsync bool // started a frame on its own goroutine but haven't heard back on wroteFrameCh - needsFrameFlush bool // last frame write wasn't a flush - inGoAway bool // we've started to or sent GOAWAY - inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop - needToSendGoAway bool // we need to schedule a GOAWAY frame write - pingSent bool - sentPingData [8]byte - goAwayCode http2ErrCode - shutdownTimer *time.Timer // nil until used - idleTimer *time.Timer // nil if unused - readIdleTimeout time.Duration - pingTimeout time.Duration - readIdleTimer *time.Timer // nil if unused - - // Owned by the writeFrameAsync goroutine: - headerWriteBuf bytes.Buffer - hpackEncoder *hpack.Encoder - - // Used by startGracefulShutdown. - shutdownOnce sync.Once - - // Used for RFC 9218 prioritization. - hasIntermediary bool // connection is done via an intermediary / proxy - priorityAware bool // the client has sent priority signal, meaning that it is aware of it. -} - -func (sc *http2serverConn) writeSchedIgnoresRFC7540() bool { - switch sc.writeSched.(type) { - case *http2priorityWriteSchedulerRFC9218: - return true - case *http2randomWriteScheduler: - return true - case *http2roundRobinWriteScheduler: - return true - default: - return false - } -} - -func (sc *http2serverConn) maxHeaderListSize() uint32 { - n := sc.hs.MaxHeaderBytes - if n <= 0 { - n = DefaultMaxHeaderBytes - } - return uint32(http2adjustHTTP1MaxHeaderSize(int64(n))) -} - -func (sc *http2serverConn) curOpenStreams() uint32 { - sc.serveG.check() - return sc.curClientStreams + sc.curPushedStreams -} - -// stream represents a stream. This is the minimal metadata needed by -// the serve goroutine. Most of the actual stream state is owned by -// the http.Handler's goroutine in the responseWriter. Because the -// responseWriter's responseWriterState is recycled at the end of a -// handler, this struct intentionally has no pointer to the -// *responseWriter{,State} itself, as the Handler ending nils out the -// responseWriter's state field. -type http2stream struct { - // immutable: - sc *http2serverConn - id uint32 - body *http2pipe // non-nil if expecting DATA frames - cw http2closeWaiter // closed wait stream transitions to closed state - ctx context.Context - cancelCtx func() - - // owned by serverConn's serve loop: - bodyBytes int64 // body bytes seen so far - declBodyBytes int64 // or -1 if undeclared - flow http2outflow // limits writing from Handler to client - inflow http2inflow // what the client is allowed to POST/etc to us - state http2streamState - resetQueued bool // RST_STREAM queued for write; set by sc.resetStream - gotTrailerHeader bool // HEADER frame for trailers was seen - wroteHeaders bool // whether we wrote headers (not status 100) - readDeadline *time.Timer // nil if unused - writeDeadline *time.Timer // nil if unused - closeErr error // set before cw is closed - - trailer Header // accumulated trailers - reqTrailer Header // handler's Request.Trailer -} - -func (sc *http2serverConn) Framer() *http2Framer { return sc.framer } - -func (sc *http2serverConn) CloseConn() error { return sc.conn.Close() } - -func (sc *http2serverConn) Flush() error { return sc.bw.Flush() } - -func (sc *http2serverConn) HeaderEncoder() (*hpack.Encoder, *bytes.Buffer) { - return sc.hpackEncoder, &sc.headerWriteBuf -} - -func (sc *http2serverConn) state(streamID uint32) (http2streamState, *http2stream) { - sc.serveG.check() - // http://tools.ietf.org/html/rfc7540#section-5.1 - if st, ok := sc.streams[streamID]; ok { - return st.state, st - } - // "The first use of a new stream identifier implicitly closes all - // streams in the "idle" state that might have been initiated by - // that peer with a lower-valued stream identifier. For example, if - // a client sends a HEADERS frame on stream 7 without ever sending a - // frame on stream 5, then stream 5 transitions to the "closed" - // state when the first frame for stream 7 is sent or received." - if streamID%2 == 1 { - if streamID <= sc.maxClientStreamID { - return http2stateClosed, nil - } - } else { - if streamID <= sc.maxPushPromiseID { - return http2stateClosed, nil - } - } - return http2stateIdle, nil -} - -// setConnState calls the net/http ConnState hook for this connection, if configured. -// Note that the net/http package does StateNew and StateClosed for us. -// There is currently no plan for StateHijacked or hijacking HTTP/2 connections. -func (sc *http2serverConn) setConnState(state ConnState) { - if sc.hs.ConnState != nil { - sc.hs.ConnState(sc.conn, state) - } -} - -func (sc *http2serverConn) vlogf(format string, args ...interface{}) { - if http2VerboseLogs { - sc.logf(format, args...) - } -} - -func (sc *http2serverConn) logf(format string, args ...interface{}) { - if lg := sc.hs.ErrorLog; lg != nil { - lg.Printf(format, args...) - } else { - log.Printf(format, args...) - } -} - -// errno returns v's underlying uintptr, else 0. -// -// TODO: remove this helper function once http2 can use build -// tags. See comment in isClosedConnError. -func http2errno(v error) uintptr { - if rv := reflect.ValueOf(v); rv.Kind() == reflect.Uintptr { - return uintptr(rv.Uint()) - } - return 0 -} - -// isClosedConnError reports whether err is an error from use of a closed -// network connection. -func http2isClosedConnError(err error) bool { - if err == nil { - return false - } - - if errors.Is(err, net.ErrClosed) { - return true - } - - // TODO(bradfitz): x/tools/cmd/bundle doesn't really support - // build tags, so I can't make an http2_windows.go file with - // Windows-specific stuff. Fix that and move this, once we - // have a way to bundle this into std's net/http somehow. - if runtime.GOOS == "windows" { - if oe, ok := err.(*net.OpError); ok && oe.Op == "read" { - if se, ok := oe.Err.(*os.SyscallError); ok && se.Syscall == "wsarecv" { - const WSAECONNABORTED = 10053 - const WSAECONNRESET = 10054 - if n := http2errno(se.Err); n == WSAECONNRESET || n == WSAECONNABORTED { - return true - } - } - } - } - return false -} - -func (sc *http2serverConn) condlogf(err error, format string, args ...interface{}) { - if err == nil { - return - } - if err == io.EOF || err == io.ErrUnexpectedEOF || http2isClosedConnError(err) || err == http2errPrefaceTimeout { - // Boring, expected errors. - sc.vlogf(format, args...) - } else { - sc.logf(format, args...) - } -} - -// maxCachedCanonicalHeadersKeysSize is an arbitrarily-chosen limit on the size -// of the entries in the canonHeader cache. -// This should be larger than the size of unique, uncommon header keys likely to -// be sent by the peer, while not so high as to permit unreasonable memory usage -// if the peer sends an unbounded number of unique header keys. -const http2maxCachedCanonicalHeadersKeysSize = 2048 - -func (sc *http2serverConn) canonicalHeader(v string) string { - sc.serveG.check() - cv, ok := httpcommon.CachedCanonicalHeader(v) - if ok { - return cv - } - cv, ok = sc.canonHeader[v] - if ok { - return cv - } - if sc.canonHeader == nil { - sc.canonHeader = make(map[string]string) - } - cv = CanonicalHeaderKey(v) - size := 100 + len(v)*2 // 100 bytes of map overhead + key + value - if sc.canonHeaderKeysSize+size <= http2maxCachedCanonicalHeadersKeysSize { - sc.canonHeader[v] = cv - sc.canonHeaderKeysSize += size - } - return cv -} - -type http2readFrameResult struct { - f http2Frame // valid until readMore is called - err error - - // readMore should be called once the consumer no longer needs or - // retains f. After readMore, f is invalid and more frames can be - // read. - readMore func() -} - -// readFrames is the loop that reads incoming frames. -// It takes care to only read one frame at a time, blocking until the -// consumer is done with the frame. -// It's run on its own goroutine. -func (sc *http2serverConn) readFrames() { - gate := make(chan struct{}) - gateDone := func() { gate <- struct{}{} } - for { - f, err := sc.framer.ReadFrame() - select { - case sc.readFrameCh <- http2readFrameResult{f, err, gateDone}: - case <-sc.doneServing: - return - } - select { - case <-gate: - case <-sc.doneServing: - return - } - if http2terminalReadFrameError(err) { - return - } - } -} - -// frameWriteResult is the message passed from writeFrameAsync to the serve goroutine. -type http2frameWriteResult struct { - _ http2incomparable - wr http2FrameWriteRequest // what was written (or attempted) - err error // result of the writeFrame call -} - -// writeFrameAsync runs in its own goroutine and writes a single frame -// and then reports when it's done. -// At most one goroutine can be running writeFrameAsync at a time per -// serverConn. -func (sc *http2serverConn) writeFrameAsync(wr http2FrameWriteRequest, wd *http2writeData) { - var err error - if wd == nil { - err = wr.write.writeFrame(sc) - } else { - err = sc.framer.endWrite() - } - sc.wroteFrameCh <- http2frameWriteResult{wr: wr, err: err} -} - -func (sc *http2serverConn) closeAllStreamsOnConnClose() { - sc.serveG.check() - for _, st := range sc.streams { - sc.closeStream(st, http2errClientDisconnected) - } -} - -func (sc *http2serverConn) stopShutdownTimer() { - sc.serveG.check() - if t := sc.shutdownTimer; t != nil { - t.Stop() - } -} - -func (sc *http2serverConn) notePanic() { - // Note: this is for serverConn.serve panicking, not http.Handler code. - if http2testHookOnPanicMu != nil { - http2testHookOnPanicMu.Lock() - defer http2testHookOnPanicMu.Unlock() - } - if http2testHookOnPanic != nil { - if e := recover(); e != nil { - if http2testHookOnPanic(sc, e) { - panic(e) - } - } - } -} - -func (sc *http2serverConn) serve(conf http2http2Config) { - sc.serveG.check() - defer sc.notePanic() - defer sc.conn.Close() - defer sc.closeAllStreamsOnConnClose() - defer sc.stopShutdownTimer() - defer close(sc.doneServing) // unblocks handlers trying to send - - if http2VerboseLogs { - sc.vlogf("http2: server connection from %v on %p", sc.conn.RemoteAddr(), sc.hs) - } - - settings := http2writeSettings{ - {http2SettingMaxFrameSize, conf.MaxReadFrameSize}, - {http2SettingMaxConcurrentStreams, sc.advMaxStreams}, - {http2SettingMaxHeaderListSize, sc.maxHeaderListSize()}, - {http2SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize}, - {http2SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)}, - } - if !http2disableExtendedConnectProtocol { - settings = append(settings, http2Setting{http2SettingEnableConnectProtocol, 1}) - } - if sc.writeSchedIgnoresRFC7540() { - settings = append(settings, http2Setting{http2SettingNoRFC7540Priorities, 1}) - } - sc.writeFrame(http2FrameWriteRequest{ - write: settings, - }) - sc.unackedSettings++ - - // Each connection starts with initialWindowSize inflow tokens. - // If a higher value is configured, we add more tokens. - if diff := conf.MaxUploadBufferPerConnection - http2initialWindowSize; diff > 0 { - sc.sendWindowUpdate(nil, int(diff)) - } - - if err := sc.readPreface(); err != nil { - sc.condlogf(err, "http2: server: error reading preface from client %v: %v", sc.conn.RemoteAddr(), err) - return - } - // Now that we've got the preface, get us out of the - // "StateNew" state. We can't go directly to idle, though. - // Active means we read some data and anticipate a request. We'll - // do another Active when we get a HEADERS frame. - sc.setConnState(StateActive) - sc.setConnState(StateIdle) - - if sc.srv.IdleTimeout > 0 { - sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer) - defer sc.idleTimer.Stop() - } - - if conf.SendPingTimeout > 0 { - sc.readIdleTimeout = conf.SendPingTimeout - sc.readIdleTimer = time.AfterFunc(conf.SendPingTimeout, sc.onReadIdleTimer) - defer sc.readIdleTimer.Stop() - } - - go sc.readFrames() // closed by defer sc.conn.Close above - - settingsTimer := time.AfterFunc(http2firstSettingsTimeout, sc.onSettingsTimer) - defer settingsTimer.Stop() - - lastFrameTime := time.Now() - loopNum := 0 - for { - loopNum++ - select { - case wr := <-sc.wantWriteFrameCh: - if se, ok := wr.write.(http2StreamError); ok { - sc.resetStream(se) - break - } - sc.writeFrame(wr) - case res := <-sc.wroteFrameCh: - sc.wroteFrame(res) - case res := <-sc.readFrameCh: - lastFrameTime = time.Now() - // Process any written frames before reading new frames from the client since a - // written frame could have triggered a new stream to be started. - if sc.writingFrameAsync { - select { - case wroteRes := <-sc.wroteFrameCh: - sc.wroteFrame(wroteRes) - default: - } - } - if !sc.processFrameFromReader(res) { - return - } - res.readMore() - if settingsTimer != nil { - settingsTimer.Stop() - settingsTimer = nil - } - case m := <-sc.bodyReadCh: - sc.noteBodyRead(m.st, m.n) - case msg := <-sc.serveMsgCh: - switch v := msg.(type) { - case func(int): - v(loopNum) // for testing - case *http2serverMessage: - switch v { - case http2settingsTimerMsg: - sc.logf("timeout waiting for SETTINGS frames from %v", sc.conn.RemoteAddr()) - return - case http2idleTimerMsg: - sc.vlogf("connection is idle") - sc.goAway(http2ErrCodeNo) - case http2readIdleTimerMsg: - sc.handlePingTimer(lastFrameTime) - case http2shutdownTimerMsg: - sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr()) - return - case http2gracefulShutdownMsg: - sc.startGracefulShutdownInternal() - case http2handlerDoneMsg: - sc.handlerDone() - default: - panic("unknown timer") - } - case *http2startPushRequest: - sc.startPush(v) - case func(*http2serverConn): - v(sc) - default: - panic(fmt.Sprintf("unexpected type %T", v)) - } - } - - // If the peer is causing us to generate a lot of control frames, - // but not reading them from us, assume they are trying to make us - // run out of memory. - if sc.queuedControlFrames > http2maxQueuedControlFrames { - sc.vlogf("http2: too many control frames in send queue, closing connection") - return - } - - // Start the shutdown timer after sending a GOAWAY. When sending GOAWAY - // with no error code (graceful shutdown), don't start the timer until - // all open streams have been completed. - sentGoAway := sc.inGoAway && !sc.needToSendGoAway && !sc.writingFrame - gracefulShutdownComplete := sc.goAwayCode == http2ErrCodeNo && sc.curOpenStreams() == 0 - if sentGoAway && sc.shutdownTimer == nil && (sc.goAwayCode != http2ErrCodeNo || gracefulShutdownComplete) { - sc.shutDownIn(http2goAwayTimeout) - } - } -} - -func (sc *http2serverConn) handlePingTimer(lastFrameReadTime time.Time) { - if sc.pingSent { - sc.logf("timeout waiting for PING response") - if f := sc.countErrorFunc; f != nil { - f("conn_close_lost_ping") - } - sc.conn.Close() - return - } - - pingAt := lastFrameReadTime.Add(sc.readIdleTimeout) - now := time.Now() - if pingAt.After(now) { - // We received frames since arming the ping timer. - // Reset it for the next possible timeout. - sc.readIdleTimer.Reset(pingAt.Sub(now)) - return - } - - sc.pingSent = true - // Ignore crypto/rand.Read errors: It generally can't fail, and worse case if it does - // is we send a PING frame containing 0s. - _, _ = rand.Read(sc.sentPingData[:]) - sc.writeFrame(http2FrameWriteRequest{ - write: &http2writePing{data: sc.sentPingData}, - }) - sc.readIdleTimer.Reset(sc.pingTimeout) -} - -type http2serverMessage int - -// Message values sent to serveMsgCh. -var ( - http2settingsTimerMsg = new(http2serverMessage) - http2idleTimerMsg = new(http2serverMessage) - http2readIdleTimerMsg = new(http2serverMessage) - http2shutdownTimerMsg = new(http2serverMessage) - http2gracefulShutdownMsg = new(http2serverMessage) - http2handlerDoneMsg = new(http2serverMessage) -) - -func (sc *http2serverConn) onSettingsTimer() { sc.sendServeMsg(http2settingsTimerMsg) } - -func (sc *http2serverConn) onIdleTimer() { sc.sendServeMsg(http2idleTimerMsg) } - -func (sc *http2serverConn) onReadIdleTimer() { sc.sendServeMsg(http2readIdleTimerMsg) } - -func (sc *http2serverConn) onShutdownTimer() { sc.sendServeMsg(http2shutdownTimerMsg) } - -func (sc *http2serverConn) sendServeMsg(msg interface{}) { - sc.serveG.checkNotOn() // NOT - select { - case sc.serveMsgCh <- msg: - case <-sc.doneServing: - } -} - -var http2errPrefaceTimeout = errors.New("timeout waiting for client preface") - -// readPreface reads the ClientPreface greeting from the peer or -// returns errPrefaceTimeout on timeout, or an error if the greeting -// is invalid. -func (sc *http2serverConn) readPreface() error { - if sc.sawClientPreface { - return nil - } - errc := make(chan error, 1) - go func() { - // Read the client preface - buf := make([]byte, len(http2ClientPreface)) - if _, err := io.ReadFull(sc.conn, buf); err != nil { - errc <- err - } else if !bytes.Equal(buf, http2clientPreface) { - errc <- fmt.Errorf("bogus greeting %q", buf) - } else { - errc <- nil - } - }() - timer := time.NewTimer(http2prefaceTimeout) // TODO: configurable on *Server? - defer timer.Stop() - select { - case <-timer.C: - return http2errPrefaceTimeout - case err := <-errc: - if err == nil { - if http2VerboseLogs { - sc.vlogf("http2: server: client %v said hello", sc.conn.RemoteAddr()) - } - } - return err - } -} - -var http2writeDataPool = sync.Pool{ - New: func() interface{} { return new(http2writeData) }, -} - -// writeDataFromHandler writes DATA response frames from a handler on -// the given stream. -func (sc *http2serverConn) writeDataFromHandler(stream *http2stream, data []byte, endStream bool) error { - ch := sc.srv.state.getErrChan() - writeArg := http2writeDataPool.Get().(*http2writeData) - *writeArg = http2writeData{stream.id, data, endStream} - err := sc.writeFrameFromHandler(http2FrameWriteRequest{ - write: writeArg, - stream: stream, - done: ch, - }) - if err != nil { - return err - } - var frameWriteDone bool // the frame write is done (successfully or not) - select { - case err = <-ch: - frameWriteDone = true - case <-sc.doneServing: - return http2errClientDisconnected - case <-stream.cw: - // If both ch and stream.cw were ready (as might - // happen on the final Write after an http.Handler - // ends), prefer the write result. Otherwise this - // might just be us successfully closing the stream. - // The writeFrameAsync and serve goroutines guarantee - // that the ch send will happen before the stream.cw - // close. - select { - case err = <-ch: - frameWriteDone = true - default: - return http2errStreamClosed - } - } - sc.srv.state.putErrChan(ch) - if frameWriteDone { - http2writeDataPool.Put(writeArg) - } - return err -} - -// writeFrameFromHandler sends wr to sc.wantWriteFrameCh, but aborts -// if the connection has gone away. -// -// This must not be run from the serve goroutine itself, else it might -// deadlock writing to sc.wantWriteFrameCh (which is only mildly -// buffered and is read by serve itself). If you're on the serve -// goroutine, call writeFrame instead. -func (sc *http2serverConn) writeFrameFromHandler(wr http2FrameWriteRequest) error { - sc.serveG.checkNotOn() // NOT - select { - case sc.wantWriteFrameCh <- wr: - return nil - case <-sc.doneServing: - // Serve loop is gone. - // Client has closed their connection to the server. - return http2errClientDisconnected - } -} - -// writeFrame schedules a frame to write and sends it if there's nothing -// already being written. -// -// There is no pushback here (the serve goroutine never blocks). It's -// the http.Handlers that block, waiting for their previous frames to -// make it onto the wire -// -// If you're not on the serve goroutine, use writeFrameFromHandler instead. -func (sc *http2serverConn) writeFrame(wr http2FrameWriteRequest) { - sc.serveG.check() - - // If true, wr will not be written and wr.done will not be signaled. - var ignoreWrite bool - - // We are not allowed to write frames on closed streams. RFC 7540 Section - // 5.1.1 says: "An endpoint MUST NOT send frames other than PRIORITY on - // a closed stream." Our server never sends PRIORITY, so that exception - // does not apply. - // - // The serverConn might close an open stream while the stream's handler - // is still running. For example, the server might close a stream when it - // receives bad data from the client. If this happens, the handler might - // attempt to write a frame after the stream has been closed (since the - // handler hasn't yet been notified of the close). In this case, we simply - // ignore the frame. The handler will notice that the stream is closed when - // it waits for the frame to be written. - // - // As an exception to this rule, we allow sending RST_STREAM after close. - // This allows us to immediately reject new streams without tracking any - // state for those streams (except for the queued RST_STREAM frame). This - // may result in duplicate RST_STREAMs in some cases, but the client should - // ignore those. - if wr.StreamID() != 0 { - _, isReset := wr.write.(http2StreamError) - if state, _ := sc.state(wr.StreamID()); state == http2stateClosed && !isReset { - ignoreWrite = true - } - } - - // Don't send a 100-continue response if we've already sent headers. - // See golang.org/issue/14030. - switch wr.write.(type) { - case *http2writeResHeaders: - wr.stream.wroteHeaders = true - case http2write100ContinueHeadersFrame: - if wr.stream.wroteHeaders { - // We do not need to notify wr.done because this frame is - // never written with wr.done != nil. - if wr.done != nil { - panic("wr.done != nil for write100ContinueHeadersFrame") - } - ignoreWrite = true - } - } - - if !ignoreWrite { - if wr.isControl() { - sc.queuedControlFrames++ - // For extra safety, detect wraparounds, which should not happen, - // and pull the plug. - if sc.queuedControlFrames < 0 { - sc.conn.Close() - } - } - sc.writeSched.Push(wr) - } - sc.scheduleFrameWrite() -} - -// startFrameWrite starts a goroutine to write wr (in a separate -// goroutine since that might block on the network), and updates the -// serve goroutine's state about the world, updated from info in wr. -func (sc *http2serverConn) startFrameWrite(wr http2FrameWriteRequest) { - sc.serveG.check() - if sc.writingFrame { - panic("internal error: can only be writing one frame at a time") - } - - st := wr.stream - if st != nil { - switch st.state { - case http2stateHalfClosedLocal: - switch wr.write.(type) { - case http2StreamError, http2handlerPanicRST, http2writeWindowUpdate: - // RFC 7540 Section 5.1 allows sending RST_STREAM, PRIORITY, and WINDOW_UPDATE - // in this state. (We never send PRIORITY from the server, so that is not checked.) - default: - panic(fmt.Sprintf("internal error: attempt to send frame on a half-closed-local stream: %v", wr)) - } - case http2stateClosed: - panic(fmt.Sprintf("internal error: attempt to send frame on a closed stream: %v", wr)) - } - } - if wpp, ok := wr.write.(*http2writePushPromise); ok { - var err error - wpp.promisedID, err = wpp.allocatePromisedID() - if err != nil { - sc.writingFrameAsync = false - wr.replyToWriter(err) - return - } - } - - sc.writingFrame = true - sc.needsFrameFlush = true - if wr.write.staysWithinBuffer(sc.bw.Available()) { - sc.writingFrameAsync = false - err := wr.write.writeFrame(sc) - sc.wroteFrame(http2frameWriteResult{wr: wr, err: err}) - } else if wd, ok := wr.write.(*http2writeData); ok { - // Encode the frame in the serve goroutine, to ensure we don't have - // any lingering asynchronous references to data passed to Write. - // See https://go.dev/issue/58446. - sc.framer.startWriteDataPadded(wd.streamID, wd.endStream, wd.p, nil) - sc.writingFrameAsync = true - go sc.writeFrameAsync(wr, wd) - } else { - sc.writingFrameAsync = true - go sc.writeFrameAsync(wr, nil) - } -} - -// errHandlerPanicked is the error given to any callers blocked in a read from -// Request.Body when the main goroutine panics. Since most handlers read in the -// main ServeHTTP goroutine, this will show up rarely. -var http2errHandlerPanicked = errors.New("http2: handler panicked") - -// wroteFrame is called on the serve goroutine with the result of -// whatever happened on writeFrameAsync. -func (sc *http2serverConn) wroteFrame(res http2frameWriteResult) { - sc.serveG.check() - if !sc.writingFrame { - panic("internal error: expected to be already writing a frame") - } - sc.writingFrame = false - sc.writingFrameAsync = false - - if res.err != nil { - sc.conn.Close() - } - - wr := res.wr - - if http2writeEndsStream(wr.write) { - st := wr.stream - if st == nil { - panic("internal error: expecting non-nil stream") - } - switch st.state { - case http2stateOpen: - // Here we would go to stateHalfClosedLocal in - // theory, but since our handler is done and - // the net/http package provides no mechanism - // for closing a ResponseWriter while still - // reading data (see possible TODO at top of - // this file), we go into closed state here - // anyway, after telling the peer we're - // hanging up on them. We'll transition to - // stateClosed after the RST_STREAM frame is - // written. - st.state = http2stateHalfClosedLocal - // Section 8.1: a server MAY request that the client abort - // transmission of a request without error by sending a - // RST_STREAM with an error code of NO_ERROR after sending - // a complete response. - sc.resetStream(http2streamError(st.id, http2ErrCodeNo)) - case http2stateHalfClosedRemote: - sc.closeStream(st, http2errHandlerComplete) - } - } else { - switch v := wr.write.(type) { - case http2StreamError: - // st may be unknown if the RST_STREAM was generated to reject bad input. - if st, ok := sc.streams[v.StreamID]; ok { - sc.closeStream(st, v) - } - case http2handlerPanicRST: - sc.closeStream(wr.stream, http2errHandlerPanicked) - } - } - - // Reply (if requested) to unblock the ServeHTTP goroutine. - wr.replyToWriter(res.err) - - sc.scheduleFrameWrite() -} - -// scheduleFrameWrite tickles the frame writing scheduler. -// -// If a frame is already being written, nothing happens. This will be called again -// when the frame is done being written. -// -// If a frame isn't being written and we need to send one, the best frame -// to send is selected by writeSched. -// -// If a frame isn't being written and there's nothing else to send, we -// flush the write buffer. -func (sc *http2serverConn) scheduleFrameWrite() { - sc.serveG.check() - if sc.writingFrame || sc.inFrameScheduleLoop { - return - } - sc.inFrameScheduleLoop = true - for !sc.writingFrameAsync { - if sc.needToSendGoAway { - sc.needToSendGoAway = false - sc.startFrameWrite(http2FrameWriteRequest{ - write: &http2writeGoAway{ - maxStreamID: sc.maxClientStreamID, - code: sc.goAwayCode, - }, - }) - continue - } - if sc.needToSendSettingsAck { - sc.needToSendSettingsAck = false - sc.startFrameWrite(http2FrameWriteRequest{write: http2writeSettingsAck{}}) - continue - } - if !sc.inGoAway || sc.goAwayCode == http2ErrCodeNo { - if wr, ok := sc.writeSched.Pop(); ok { - if wr.isControl() { - sc.queuedControlFrames-- - } - sc.startFrameWrite(wr) - continue - } - } - if sc.needsFrameFlush { - sc.startFrameWrite(http2FrameWriteRequest{write: http2flushFrameWriter{}}) - sc.needsFrameFlush = false // after startFrameWrite, since it sets this true - continue - } - break - } - sc.inFrameScheduleLoop = false -} - -// startGracefulShutdown gracefully shuts down a connection. This -// sends GOAWAY with ErrCodeNo to tell the client we're gracefully -// shutting down. The connection isn't closed until all current -// streams are done. -// -// startGracefulShutdown returns immediately; it does not wait until -// the connection has shut down. -func (sc *http2serverConn) startGracefulShutdown() { - sc.serveG.checkNotOn() // NOT - sc.shutdownOnce.Do(func() { sc.sendServeMsg(http2gracefulShutdownMsg) }) -} - -// After sending GOAWAY with an error code (non-graceful shutdown), the -// connection will close after goAwayTimeout. -// -// If we close the connection immediately after sending GOAWAY, there may -// be unsent data in our kernel receive buffer, which will cause the kernel -// to send a TCP RST on close() instead of a FIN. This RST will abort the -// connection immediately, whether or not the client had received the GOAWAY. -// -// Ideally we should delay for at least 1 RTT + epsilon so the client has -// a chance to read the GOAWAY and stop sending messages. Measuring RTT -// is hard, so we approximate with 1 second. See golang.org/issue/18701. -// -// This is a var so it can be shorter in tests, where all requests uses the -// loopback interface making the expected RTT very small. -// -// TODO: configurable? -var http2goAwayTimeout = 1 * time.Second - -func (sc *http2serverConn) startGracefulShutdownInternal() { - sc.goAway(http2ErrCodeNo) -} - -func (sc *http2serverConn) goAway(code http2ErrCode) { - sc.serveG.check() - if sc.inGoAway { - if sc.goAwayCode == http2ErrCodeNo { - sc.goAwayCode = code - } - return - } - sc.inGoAway = true - sc.needToSendGoAway = true - sc.goAwayCode = code - sc.scheduleFrameWrite() -} - -func (sc *http2serverConn) shutDownIn(d time.Duration) { - sc.serveG.check() - sc.shutdownTimer = time.AfterFunc(d, sc.onShutdownTimer) -} - -func (sc *http2serverConn) resetStream(se http2StreamError) { - sc.serveG.check() - sc.writeFrame(http2FrameWriteRequest{write: se}) - if st, ok := sc.streams[se.StreamID]; ok { - st.resetQueued = true - } -} - -// processFrameFromReader processes the serve loop's read from readFrameCh from the -// frame-reading goroutine. -// processFrameFromReader returns whether the connection should be kept open. -func (sc *http2serverConn) processFrameFromReader(res http2readFrameResult) bool { - sc.serveG.check() - err := res.err - if err != nil { - if err == http2ErrFrameTooLarge { - sc.goAway(http2ErrCodeFrameSize) - return true // goAway will close the loop - } - clientGone := err == io.EOF || err == io.ErrUnexpectedEOF || http2isClosedConnError(err) - if clientGone { - // TODO: could we also get into this state if - // the peer does a half close - // (e.g. CloseWrite) because they're done - // sending frames but they're still wanting - // our open replies? Investigate. - // TODO: add CloseWrite to crypto/tls.Conn first - // so we have a way to test this? I suppose - // just for testing we could have a non-TLS mode. - return false - } - } else { - f := res.f - if http2VerboseLogs { - sc.vlogf("http2: server read frame %v", http2summarizeFrame(f)) - } - err = sc.processFrame(f) - if err == nil { - return true - } - } - - switch ev := err.(type) { - case http2StreamError: - sc.resetStream(ev) - return true - case http2goAwayFlowError: - sc.goAway(http2ErrCodeFlowControl) - return true - case http2ConnectionError: - if res.f != nil { - if id := res.f.Header().StreamID; id > sc.maxClientStreamID { - sc.maxClientStreamID = id - } - } - sc.logf("http2: server connection error from %v: %v", sc.conn.RemoteAddr(), ev) - sc.goAway(http2ErrCode(ev)) - return true // goAway will handle shutdown - default: - if res.err != nil { - sc.vlogf("http2: server closing client connection; error reading frame from client %s: %v", sc.conn.RemoteAddr(), err) - } else { - sc.logf("http2: server closing client connection: %v", err) - } - return false - } -} - -func (sc *http2serverConn) processFrame(f http2Frame) error { - sc.serveG.check() - - // First frame received must be SETTINGS. - if !sc.sawFirstSettings { - if _, ok := f.(*http2SettingsFrame); !ok { - return sc.countError("first_settings", http2ConnectionError(http2ErrCodeProtocol)) - } - sc.sawFirstSettings = true - } - - // Discard frames for streams initiated after the identified last - // stream sent in a GOAWAY, or all frames after sending an error. - // We still need to return connection-level flow control for DATA frames. - // RFC 9113 Section 6.8. - if sc.inGoAway && (sc.goAwayCode != http2ErrCodeNo || f.Header().StreamID > sc.maxClientStreamID) { - - if f, ok := f.(*http2DataFrame); ok { - if !sc.inflow.take(f.Length) { - return sc.countError("data_flow", http2streamError(f.Header().StreamID, http2ErrCodeFlowControl)) - } - sc.sendWindowUpdate(nil, int(f.Length)) // conn-level - } - return nil - } - - switch f := f.(type) { - case *http2SettingsFrame: - return sc.processSettings(f) - case *http2MetaHeadersFrame: - return sc.processHeaders(f) - case *http2WindowUpdateFrame: - return sc.processWindowUpdate(f) - case *http2PingFrame: - return sc.processPing(f) - case *http2DataFrame: - return sc.processData(f) - case *http2RSTStreamFrame: - return sc.processResetStream(f) - case *http2PriorityFrame: - return sc.processPriority(f) - case *http2GoAwayFrame: - return sc.processGoAway(f) - case *http2PushPromiseFrame: - // A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE - // frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. - return sc.countError("push_promise", http2ConnectionError(http2ErrCodeProtocol)) - case *http2PriorityUpdateFrame: - return sc.processPriorityUpdate(f) - default: - sc.vlogf("http2: server ignoring frame: %v", f.Header()) - return nil - } -} - -func (sc *http2serverConn) processPing(f *http2PingFrame) error { - sc.serveG.check() - if f.IsAck() { - if sc.pingSent && sc.sentPingData == f.Data { - // This is a response to a PING we sent. - sc.pingSent = false - sc.readIdleTimer.Reset(sc.readIdleTimeout) - } - // 6.7 PING: " An endpoint MUST NOT respond to PING frames - // containing this flag." - return nil - } - if f.StreamID != 0 { - // "PING frames are not associated with any individual - // stream. If a PING frame is received with a stream - // identifier field value other than 0x0, the recipient MUST - // respond with a connection error (Section 5.4.1) of type - // PROTOCOL_ERROR." - return sc.countError("ping_on_stream", http2ConnectionError(http2ErrCodeProtocol)) - } - sc.writeFrame(http2FrameWriteRequest{write: http2writePingAck{f}}) - return nil -} - -func (sc *http2serverConn) processWindowUpdate(f *http2WindowUpdateFrame) error { - sc.serveG.check() - switch { - case f.StreamID != 0: // stream-level flow control - state, st := sc.state(f.StreamID) - if state == http2stateIdle { - // Section 5.1: "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." - return sc.countError("stream_idle", http2ConnectionError(http2ErrCodeProtocol)) - } - if st == nil { - // "WINDOW_UPDATE can be sent by a peer that has sent a - // frame bearing the END_STREAM flag. This means that a - // receiver could receive a WINDOW_UPDATE frame on a "half - // closed (remote)" or "closed" stream. A receiver MUST - // NOT treat this as an error, see Section 5.1." - return nil - } - if !st.flow.add(int32(f.Increment)) { - return sc.countError("bad_flow", http2streamError(f.StreamID, http2ErrCodeFlowControl)) - } - default: // connection-level flow control - if !sc.flow.add(int32(f.Increment)) { - return http2goAwayFlowError{} - } - } - sc.scheduleFrameWrite() - return nil -} - -func (sc *http2serverConn) processResetStream(f *http2RSTStreamFrame) error { - sc.serveG.check() - - state, st := sc.state(f.StreamID) - if state == http2stateIdle { - // 6.4 "RST_STREAM frames MUST NOT be sent for a - // stream in the "idle" state. If a RST_STREAM frame - // identifying an idle stream is received, the - // recipient MUST treat this as a connection error - // (Section 5.4.1) of type PROTOCOL_ERROR. - return sc.countError("reset_idle_stream", http2ConnectionError(http2ErrCodeProtocol)) - } - if st != nil { - st.cancelCtx() - sc.closeStream(st, http2streamError(f.StreamID, f.ErrCode)) - } - return nil -} - -func (sc *http2serverConn) closeStream(st *http2stream, err error) { - sc.serveG.check() - if st.state == http2stateIdle || st.state == http2stateClosed { - panic(fmt.Sprintf("invariant; can't close stream in state %v", st.state)) - } - st.state = http2stateClosed - if st.readDeadline != nil { - st.readDeadline.Stop() - } - if st.writeDeadline != nil { - st.writeDeadline.Stop() - } - if st.isPushed() { - sc.curPushedStreams-- - } else { - sc.curClientStreams-- - } - delete(sc.streams, st.id) - if len(sc.streams) == 0 { - sc.setConnState(StateIdle) - if sc.srv.IdleTimeout > 0 && sc.idleTimer != nil { - sc.idleTimer.Reset(sc.srv.IdleTimeout) - } - if http2h1ServerKeepAlivesDisabled(sc.hs) { - sc.startGracefulShutdownInternal() - } - } - if p := st.body; p != nil { - // Return any buffered unread bytes worth of conn-level flow control. - // See golang.org/issue/16481 - sc.sendWindowUpdate(nil, p.Len()) - - p.CloseWithError(err) - } - if e, ok := err.(http2StreamError); ok { - if e.Cause != nil { - err = e.Cause - } else { - err = http2errStreamClosed - } - } - st.closeErr = err - st.cancelCtx() - st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc - sc.writeSched.CloseStream(st.id) -} - -func (sc *http2serverConn) processSettings(f *http2SettingsFrame) error { - sc.serveG.check() - if f.IsAck() { - sc.unackedSettings-- - if sc.unackedSettings < 0 { - // Why is the peer ACKing settings we never sent? - // The spec doesn't mention this case, but - // hang up on them anyway. - return sc.countError("ack_mystery", http2ConnectionError(http2ErrCodeProtocol)) - } - return nil - } - if f.NumSettings() > 100 || f.HasDuplicates() { - // This isn't actually in the spec, but hang up on - // suspiciously large settings frames or those with - // duplicate entries. - return sc.countError("settings_big_or_dups", http2ConnectionError(http2ErrCodeProtocol)) - } - if err := f.ForeachSetting(sc.processSetting); err != nil { - return err - } - // TODO: judging by RFC 7540, Section 6.5.3 each SETTINGS frame should be - // acknowledged individually, even if multiple are received before the ACK. - sc.needToSendSettingsAck = true - sc.scheduleFrameWrite() - return nil -} - -func (sc *http2serverConn) processSetting(s http2Setting) error { - sc.serveG.check() - if err := s.Valid(); err != nil { - return err - } - if http2VerboseLogs { - sc.vlogf("http2: server processing setting %v", s) - } - switch s.ID { - case http2SettingHeaderTableSize: - sc.hpackEncoder.SetMaxDynamicTableSize(s.Val) - case http2SettingEnablePush: - sc.pushEnabled = s.Val != 0 - case http2SettingMaxConcurrentStreams: - sc.clientMaxStreams = s.Val - case http2SettingInitialWindowSize: - return sc.processSettingInitialWindowSize(s.Val) - case http2SettingMaxFrameSize: - sc.maxFrameSize = int32(s.Val) // the maximum valid s.Val is < 2^31 - case http2SettingMaxHeaderListSize: - sc.peerMaxHeaderListSize = s.Val - case http2SettingEnableConnectProtocol: - // Receipt of this parameter by a server does not - // have any impact - case http2SettingNoRFC7540Priorities: - if s.Val > 1 { - return http2ConnectionError(http2ErrCodeProtocol) - } - default: - // Unknown setting: "An endpoint that receives a SETTINGS - // frame with any unknown or unsupported identifier MUST - // ignore that setting." - if http2VerboseLogs { - sc.vlogf("http2: server ignoring unknown setting %v", s) - } - } - return nil -} - -func (sc *http2serverConn) processSettingInitialWindowSize(val uint32) error { - sc.serveG.check() - // Note: val already validated to be within range by - // processSetting's Valid call. - - // "A SETTINGS frame can alter the initial flow control window - // size for all current streams. When the value of - // SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST - // adjust the size of all stream flow control windows that it - // maintains by the difference between the new value and the - // old value." - old := sc.initialStreamSendWindowSize - sc.initialStreamSendWindowSize = int32(val) - growth := int32(val) - old // may be negative - for _, st := range sc.streams { - if !st.flow.add(growth) { - // 6.9.2 Initial Flow Control Window Size - // "An endpoint MUST treat a change to - // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow - // control window to exceed the maximum size as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR." - return sc.countError("setting_win_size", http2ConnectionError(http2ErrCodeFlowControl)) - } - } - return nil -} - -func (sc *http2serverConn) processData(f *http2DataFrame) error { - sc.serveG.check() - id := f.Header().StreamID - - data := f.Data() - state, st := sc.state(id) - if id == 0 || state == http2stateIdle { - // Section 6.1: "DATA frames MUST be associated with a - // stream. If a DATA frame is received whose stream - // identifier field is 0x0, the recipient MUST respond - // with a connection error (Section 5.4.1) of type - // PROTOCOL_ERROR." - // - // Section 5.1: "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." - return sc.countError("data_on_idle", http2ConnectionError(http2ErrCodeProtocol)) - } - - // "If a DATA frame is received whose stream is not in "open" - // or "half closed (local)" state, the recipient MUST respond - // with a stream error (Section 5.4.2) of type STREAM_CLOSED." - if st == nil || state != http2stateOpen || st.gotTrailerHeader || st.resetQueued { - // This includes sending a RST_STREAM if the stream is - // in stateHalfClosedLocal (which currently means that - // the http.Handler returned, so it's done reading & - // done writing). Try to stop the client from sending - // more DATA. - - // But still enforce their connection-level flow control, - // and return any flow control bytes since we're not going - // to consume them. - if !sc.inflow.take(f.Length) { - return sc.countError("data_flow", http2streamError(id, http2ErrCodeFlowControl)) - } - sc.sendWindowUpdate(nil, int(f.Length)) // conn-level - - if st != nil && st.resetQueued { - // Already have a stream error in flight. Don't send another. - return nil - } - return sc.countError("closed", http2streamError(id, http2ErrCodeStreamClosed)) - } - if st.body == nil { - panic("internal error: should have a body in this state") - } - - // Sender sending more than they'd declared? - if st.declBodyBytes != -1 && st.bodyBytes+int64(len(data)) > st.declBodyBytes { - if !sc.inflow.take(f.Length) { - return sc.countError("data_flow", http2streamError(id, http2ErrCodeFlowControl)) - } - sc.sendWindowUpdate(nil, int(f.Length)) // conn-level - - st.body.CloseWithError(fmt.Errorf("sender tried to send more than declared Content-Length of %d bytes", st.declBodyBytes)) - // RFC 7540, sec 8.1.2.6: A request or response is also malformed if the - // value of a content-length header field does not equal the sum of the - // DATA frame payload lengths that form the body. - return sc.countError("send_too_much", http2streamError(id, http2ErrCodeProtocol)) - } - if f.Length > 0 { - // Check whether the client has flow control quota. - if !http2takeInflows(&sc.inflow, &st.inflow, f.Length) { - return sc.countError("flow_on_data_length", http2streamError(id, http2ErrCodeFlowControl)) - } - - if len(data) > 0 { - st.bodyBytes += int64(len(data)) - wrote, err := st.body.Write(data) - if err != nil { - // The handler has closed the request body. - // Return the connection-level flow control for the discarded data, - // but not the stream-level flow control. - sc.sendWindowUpdate(nil, int(f.Length)-wrote) - return nil - } - if wrote != len(data) { - panic("internal error: bad Writer") - } - } - - // Return any padded flow control now, since we won't - // refund it later on body reads. - // Call sendWindowUpdate even if there is no padding, - // to return buffered flow control credit if the sent - // window has shrunk. - pad := int32(f.Length) - int32(len(data)) - sc.sendWindowUpdate32(nil, pad) - sc.sendWindowUpdate32(st, pad) - } - if f.StreamEnded() { - st.endStream() - } - return nil -} - -func (sc *http2serverConn) processGoAway(f *http2GoAwayFrame) error { - sc.serveG.check() - if f.ErrCode != http2ErrCodeNo { - sc.logf("http2: received GOAWAY %+v, starting graceful shutdown", f) - } else { - sc.vlogf("http2: received GOAWAY %+v, starting graceful shutdown", f) - } - sc.startGracefulShutdownInternal() - // http://tools.ietf.org/html/rfc7540#section-6.8 - // We should not create any new streams, which means we should disable push. - sc.pushEnabled = false - return nil -} - -// isPushed reports whether the stream is server-initiated. -func (st *http2stream) isPushed() bool { - return st.id%2 == 0 -} - -// endStream closes a Request.Body's pipe. It is called when a DATA -// frame says a request body is over (or after trailers). -func (st *http2stream) endStream() { - sc := st.sc - sc.serveG.check() - - if st.declBodyBytes != -1 && st.declBodyBytes != st.bodyBytes { - st.body.CloseWithError(fmt.Errorf("request declared a Content-Length of %d but only wrote %d bytes", - st.declBodyBytes, st.bodyBytes)) - } else { - st.body.closeWithErrorAndCode(io.EOF, st.copyTrailersToHandlerRequest) - st.body.CloseWithError(io.EOF) - } - st.state = http2stateHalfClosedRemote -} - -// copyTrailersToHandlerRequest is run in the Handler's goroutine in -// its Request.Body.Read just before it gets io.EOF. -func (st *http2stream) copyTrailersToHandlerRequest() { - for k, vv := range st.trailer { - if _, ok := st.reqTrailer[k]; ok { - // Only copy it over it was pre-declared. - st.reqTrailer[k] = vv - } - } -} - -// onReadTimeout is run on its own goroutine (from time.AfterFunc) -// when the stream's ReadTimeout has fired. -func (st *http2stream) onReadTimeout() { - if st.body != nil { - // Wrap the ErrDeadlineExceeded to avoid callers depending on us - // returning the bare error. - st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded)) - } -} - -// onWriteTimeout is run on its own goroutine (from time.AfterFunc) -// when the stream's WriteTimeout has fired. -func (st *http2stream) onWriteTimeout() { - st.sc.writeFrameFromHandler(http2FrameWriteRequest{write: http2StreamError{ - StreamID: st.id, - Code: http2ErrCodeInternal, - Cause: os.ErrDeadlineExceeded, - }}) -} - -func (sc *http2serverConn) processHeaders(f *http2MetaHeadersFrame) error { - sc.serveG.check() - id := f.StreamID - // http://tools.ietf.org/html/rfc7540#section-5.1.1 - // Streams initiated by a client MUST use odd-numbered stream - // identifiers. [...] An endpoint that receives an unexpected - // stream identifier MUST respond with a connection error - // (Section 5.4.1) of type PROTOCOL_ERROR. - if id%2 != 1 { - return sc.countError("headers_even", http2ConnectionError(http2ErrCodeProtocol)) - } - // A HEADERS frame can be used to create a new stream or - // send a trailer for an open one. If we already have a stream - // open, let it process its own HEADERS frame (trailers at this - // point, if it's valid). - if st := sc.streams[f.StreamID]; st != nil { - if st.resetQueued { - // We're sending RST_STREAM to close the stream, so don't bother - // processing this frame. - return nil - } - // RFC 7540, sec 5.1: If an endpoint receives additional frames, other than - // WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in - // this state, it MUST respond with a stream error (Section 5.4.2) of - // type STREAM_CLOSED. - if st.state == http2stateHalfClosedRemote { - return sc.countError("headers_half_closed", http2streamError(id, http2ErrCodeStreamClosed)) - } - return st.processTrailerHeaders(f) - } - - // [...] The identifier of a newly established stream MUST be - // numerically greater than all streams that the initiating - // endpoint has opened or reserved. [...] An endpoint that - // receives an unexpected stream identifier MUST respond with - // a connection error (Section 5.4.1) of type PROTOCOL_ERROR. - if id <= sc.maxClientStreamID { - return sc.countError("stream_went_down", http2ConnectionError(http2ErrCodeProtocol)) - } - sc.maxClientStreamID = id - - if sc.idleTimer != nil { - sc.idleTimer.Stop() - } - - // http://tools.ietf.org/html/rfc7540#section-5.1.2 - // [...] Endpoints MUST NOT exceed the limit set by their peer. An - // endpoint that receives a HEADERS frame that causes their - // advertised concurrent stream limit to be exceeded MUST treat - // this as a stream error (Section 5.4.2) of type PROTOCOL_ERROR - // or REFUSED_STREAM. - if sc.curClientStreams+1 > sc.advMaxStreams { - if sc.unackedSettings == 0 { - // They should know better. - return sc.countError("over_max_streams", http2streamError(id, http2ErrCodeProtocol)) - } - // Assume it's a network race, where they just haven't - // received our last SETTINGS update. But actually - // this can't happen yet, because we don't yet provide - // a way for users to adjust server parameters at - // runtime. - return sc.countError("over_max_streams_race", http2streamError(id, http2ErrCodeRefusedStream)) - } - - initialState := http2stateOpen - if f.StreamEnded() { - initialState = http2stateHalfClosedRemote - } - - // We are handling two special cases here: - // 1. When a request is sent via an intermediary, we force priority to be - // u=3,i. This is essentially a round-robin behavior, and is done to ensure - // fairness between, for example, multiple clients using the same proxy. - // 2. Until a client has shown that it is aware of RFC 9218, we make its - // streams non-incremental by default. This is done to preserve the - // historical behavior of handling streams in a round-robin manner, rather - // than one-by-one to completion. - initialPriority := http2defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary) - if _, ok := sc.writeSched.(*http2priorityWriteSchedulerRFC9218); ok && !sc.hasIntermediary { - headerPriority, priorityAware, hasIntermediary := f.rfc9218Priority(sc.priorityAware) - initialPriority = headerPriority - sc.hasIntermediary = hasIntermediary - if priorityAware { - sc.priorityAware = true - } - } - st := sc.newStream(id, 0, initialState, initialPriority) - - if f.HasPriority() { - if err := sc.checkPriority(f.StreamID, f.Priority); err != nil { - return err - } - if !sc.writeSchedIgnoresRFC7540() { - sc.writeSched.AdjustStream(st.id, f.Priority) - } - } - - rw, req, err := sc.newWriterAndRequest(st, f) - if err != nil { - return err - } - st.reqTrailer = req.Trailer - if st.reqTrailer != nil { - st.trailer = make(Header) - } - st.body = req.Body.(*http2requestBody).pipe // may be nil - st.declBodyBytes = req.ContentLength - - handler := sc.handler.ServeHTTP - if f.Truncated { - // Their header list was too long. Send a 431 error. - handler = http2handleHeaderListTooLong - } else if err := http2checkValidHTTP2RequestHeaders(req.Header); err != nil { - handler = http2new400Handler(err) - } - - // The net/http package sets the read deadline from the - // http.Server.ReadTimeout during the TLS handshake, but then - // passes the connection off to us with the deadline already - // set. Disarm it here after the request headers are read, - // similar to how the http1 server works. Here it's - // technically more like the http1 Server's ReadHeaderTimeout - // (in Go 1.8), though. That's a more sane option anyway. - if sc.hs.ReadTimeout > 0 { - sc.conn.SetReadDeadline(time.Time{}) - st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) - } - - return sc.scheduleHandler(id, rw, req, handler) -} - -func (sc *http2serverConn) upgradeRequest(req *Request) { - sc.serveG.check() - id := uint32(1) - sc.maxClientStreamID = id - st := sc.newStream(id, 0, http2stateHalfClosedRemote, http2defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary)) - st.reqTrailer = req.Trailer - if st.reqTrailer != nil { - st.trailer = make(Header) - } - rw := sc.newResponseWriter(st, req) - - // Disable any read deadline set by the net/http package - // prior to the upgrade. - if sc.hs.ReadTimeout > 0 { - sc.conn.SetReadDeadline(time.Time{}) - } - - // This is the first request on the connection, - // so start the handler directly rather than going - // through scheduleHandler. - sc.curHandlers++ - go sc.runHandler(rw, req, sc.handler.ServeHTTP) -} - -func (st *http2stream) processTrailerHeaders(f *http2MetaHeadersFrame) error { - sc := st.sc - sc.serveG.check() - if st.gotTrailerHeader { - return sc.countError("dup_trailers", http2ConnectionError(http2ErrCodeProtocol)) - } - st.gotTrailerHeader = true - if !f.StreamEnded() { - return sc.countError("trailers_not_ended", http2streamError(st.id, http2ErrCodeProtocol)) - } - - if len(f.PseudoFields()) > 0 { - return sc.countError("trailers_pseudo", http2streamError(st.id, http2ErrCodeProtocol)) - } - if st.trailer != nil { - for _, hf := range f.RegularFields() { - key := sc.canonicalHeader(hf.Name) - if !httpguts.ValidTrailerHeader(key) { - // TODO: send more details to the peer somehow. But http2 has - // no way to send debug data at a stream level. Discuss with - // HTTP folk. - return sc.countError("trailers_bogus", http2streamError(st.id, http2ErrCodeProtocol)) - } - st.trailer[key] = append(st.trailer[key], hf.Value) - } - } - st.endStream() - return nil -} - -func (sc *http2serverConn) checkPriority(streamID uint32, p http2PriorityParam) error { - if streamID == p.StreamDep { - // Section 5.3.1: "A stream cannot depend on itself. An endpoint MUST treat - // this as a stream error (Section 5.4.2) of type PROTOCOL_ERROR." - // Section 5.3.3 says that a stream can depend on one of its dependencies, - // so it's only self-dependencies that are forbidden. - return sc.countError("priority", http2streamError(streamID, http2ErrCodeProtocol)) - } - return nil -} - -func (sc *http2serverConn) processPriority(f *http2PriorityFrame) error { - if err := sc.checkPriority(f.StreamID, f.http2PriorityParam); err != nil { - return err - } - // We need to avoid calling AdjustStream when using the RFC 9218 write - // scheduler. Otherwise, incremental's zero value in PriorityParam will - // unexpectedly make all streams non-incremental. This causes us to process - // streams one-by-one to completion rather than doing it in a round-robin - // manner (the historical behavior), which might be unexpected to users. - if sc.writeSchedIgnoresRFC7540() { - return nil - } - sc.writeSched.AdjustStream(f.StreamID, f.http2PriorityParam) - return nil -} - -func (sc *http2serverConn) processPriorityUpdate(f *http2PriorityUpdateFrame) error { - sc.priorityAware = true - if _, ok := sc.writeSched.(*http2priorityWriteSchedulerRFC9218); !ok { - return nil - } - p, ok := http2parseRFC9218Priority(f.Priority, sc.priorityAware) - if !ok { - return sc.countError("unparsable_priority_update", http2streamError(f.PrioritizedStreamID, http2ErrCodeProtocol)) - } - sc.writeSched.AdjustStream(f.PrioritizedStreamID, p) - return nil -} - -func (sc *http2serverConn) newStream(id, pusherID uint32, state http2streamState, priority http2PriorityParam) *http2stream { - sc.serveG.check() - if id == 0 { - panic("internal error: cannot create stream with id 0") - } - - ctx, cancelCtx := context.WithCancel(sc.baseCtx) - st := &http2stream{ - sc: sc, - id: id, - state: state, - ctx: ctx, - cancelCtx: cancelCtx, - } - st.cw.Init() - st.flow.conn = &sc.flow // link to conn-level counter - st.flow.add(sc.initialStreamSendWindowSize) - st.inflow.init(sc.initialStreamRecvWindowSize) - if sc.hs.WriteTimeout > 0 { - st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) - } - - sc.streams[id] = st - sc.writeSched.OpenStream(st.id, http2OpenStreamOptions{PusherID: pusherID, priority: priority}) - if st.isPushed() { - sc.curPushedStreams++ - } else { - sc.curClientStreams++ - } - if sc.curOpenStreams() == 1 { - sc.setConnState(StateActive) - } - - return st -} - -func (sc *http2serverConn) newWriterAndRequest(st *http2stream, f *http2MetaHeadersFrame) (*http2responseWriter, *Request, error) { - sc.serveG.check() - - rp := httpcommon.ServerRequestParam{ - Method: f.PseudoValue("method"), - Scheme: f.PseudoValue("scheme"), - Authority: f.PseudoValue("authority"), - Path: f.PseudoValue("path"), - Protocol: f.PseudoValue("protocol"), - } - - // extended connect is disabled, so we should not see :protocol - if http2disableExtendedConnectProtocol && rp.Protocol != "" { - return nil, nil, sc.countError("bad_connect", http2streamError(f.StreamID, http2ErrCodeProtocol)) - } - - isConnect := rp.Method == "CONNECT" - if isConnect { - if rp.Protocol == "" && (rp.Path != "" || rp.Scheme != "" || rp.Authority == "") { - return nil, nil, sc.countError("bad_connect", http2streamError(f.StreamID, http2ErrCodeProtocol)) - } - } else if rp.Method == "" || rp.Path == "" || (rp.Scheme != "https" && rp.Scheme != "http") { - // See 8.1.2.6 Malformed Requests and Responses: - // - // Malformed requests or responses that are detected - // MUST be treated as a stream error (Section 5.4.2) - // of type PROTOCOL_ERROR." - // - // 8.1.2.3 Request Pseudo-Header Fields - // "All HTTP/2 requests MUST include exactly one valid - // value for the :method, :scheme, and :path - // pseudo-header fields" - return nil, nil, sc.countError("bad_path_method", http2streamError(f.StreamID, http2ErrCodeProtocol)) - } - - header := make(Header) - rp.Header = header - for _, hf := range f.RegularFields() { - header.Add(sc.canonicalHeader(hf.Name), hf.Value) - } - if rp.Authority == "" { - rp.Authority = header.Get("Host") - } - if rp.Protocol != "" { - header.Set(":protocol", rp.Protocol) - } - - rw, req, err := sc.newWriterAndRequestNoBody(st, rp) - if err != nil { - return nil, nil, err - } - bodyOpen := !f.StreamEnded() - if bodyOpen { - if vv, ok := rp.Header["Content-Length"]; ok { - if cl, err := strconv.ParseUint(vv[0], 10, 63); err == nil { - req.ContentLength = int64(cl) - } else { - req.ContentLength = 0 - } - } else { - req.ContentLength = -1 - } - req.Body.(*http2requestBody).pipe = &http2pipe{ - b: &http2dataBuffer{expected: req.ContentLength}, - } - } - return rw, req, nil -} - -func (sc *http2serverConn) newWriterAndRequestNoBody(st *http2stream, rp httpcommon.ServerRequestParam) (*http2responseWriter, *Request, error) { - sc.serveG.check() - - var tlsState *tls.ConnectionState // nil if not scheme https - if rp.Scheme == "https" { - tlsState = sc.tlsState - } - - res := httpcommon.NewServerRequest(rp) - if res.InvalidReason != "" { - return nil, nil, sc.countError(res.InvalidReason, http2streamError(st.id, http2ErrCodeProtocol)) - } - - body := &http2requestBody{ - conn: sc, - stream: st, - needsContinue: res.NeedsContinue, - } - req := (&Request{ - Method: rp.Method, - URL: res.URL, - RemoteAddr: sc.remoteAddrStr, - Header: rp.Header, - RequestURI: res.RequestURI, - Proto: "HTTP/2.0", - ProtoMajor: 2, - ProtoMinor: 0, - TLS: tlsState, - Host: rp.Authority, - Body: body, - Trailer: res.Trailer, - }).WithContext(st.ctx) - rw := sc.newResponseWriter(st, req) - return rw, req, nil -} - -func (sc *http2serverConn) newResponseWriter(st *http2stream, req *Request) *http2responseWriter { - rws := http2responseWriterStatePool.Get().(*http2responseWriterState) - bwSave := rws.bw - *rws = http2responseWriterState{} // zero all the fields - rws.conn = sc - rws.bw = bwSave - rws.bw.Reset(http2chunkWriter{rws}) - rws.stream = st - rws.req = req - return &http2responseWriter{rws: rws} -} - -type http2unstartedHandler struct { - streamID uint32 - rw *http2responseWriter - req *Request - handler func(ResponseWriter, *Request) -} - -// scheduleHandler starts a handler goroutine, -// or schedules one to start as soon as an existing handler finishes. -func (sc *http2serverConn) scheduleHandler(streamID uint32, rw *http2responseWriter, req *Request, handler func(ResponseWriter, *Request)) error { - sc.serveG.check() - maxHandlers := sc.advMaxStreams - if sc.curHandlers < maxHandlers { - sc.curHandlers++ - go sc.runHandler(rw, req, handler) - return nil - } - if len(sc.unstartedHandlers) > int(4*sc.advMaxStreams) { - return sc.countError("too_many_early_resets", http2ConnectionError(http2ErrCodeEnhanceYourCalm)) - } - sc.unstartedHandlers = append(sc.unstartedHandlers, http2unstartedHandler{ - streamID: streamID, - rw: rw, - req: req, - handler: handler, - }) - return nil -} - -func (sc *http2serverConn) handlerDone() { - sc.serveG.check() - sc.curHandlers-- - i := 0 - maxHandlers := sc.advMaxStreams - for ; i < len(sc.unstartedHandlers); i++ { - u := sc.unstartedHandlers[i] - if sc.streams[u.streamID] == nil { - // This stream was reset before its goroutine had a chance to start. - continue - } - if sc.curHandlers >= maxHandlers { - break - } - sc.curHandlers++ - go sc.runHandler(u.rw, u.req, u.handler) - sc.unstartedHandlers[i] = http2unstartedHandler{} // don't retain references - } - sc.unstartedHandlers = sc.unstartedHandlers[i:] - if len(sc.unstartedHandlers) == 0 { - sc.unstartedHandlers = nil - } -} - -// Run on its own goroutine. -func (sc *http2serverConn) runHandler(rw *http2responseWriter, req *Request, handler func(ResponseWriter, *Request)) { - defer sc.sendServeMsg(http2handlerDoneMsg) - didPanic := true - defer func() { - rw.rws.stream.cancelCtx() - if req.MultipartForm != nil { - req.MultipartForm.RemoveAll() - } - if didPanic { - e := recover() - sc.writeFrameFromHandler(http2FrameWriteRequest{ - write: http2handlerPanicRST{rw.rws.stream.id}, - stream: rw.rws.stream, - }) - // Same as net/http: - if e != nil && e != ErrAbortHandler { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - sc.logf("http2: panic serving %v: %v\n%s", sc.conn.RemoteAddr(), e, buf) - } - return - } - rw.handlerDone() - }() - handler(rw, req) - didPanic = false -} - -func http2handleHeaderListTooLong(w ResponseWriter, r *Request) { - // 10.5.1 Limits on Header Block Size: - // .. "A server that receives a larger header block than it is - // willing to handle can send an HTTP 431 (Request Header Fields Too - // Large) status code" - const statusRequestHeaderFieldsTooLarge = 431 // only in Go 1.6+ - w.WriteHeader(statusRequestHeaderFieldsTooLarge) - io.WriteString(w, "<h1>HTTP Error 431</h1><p>Request Header Field(s) Too Large</p>") -} - -// called from handler goroutines. -// h may be nil. -func (sc *http2serverConn) writeHeaders(st *http2stream, headerData *http2writeResHeaders) error { - sc.serveG.checkNotOn() // NOT on - var errc chan error - if headerData.h != nil { - // If there's a header map (which we don't own), so we have to block on - // waiting for this frame to be written, so an http.Flush mid-handler - // writes out the correct value of keys, before a handler later potentially - // mutates it. - errc = sc.srv.state.getErrChan() - } - if err := sc.writeFrameFromHandler(http2FrameWriteRequest{ - write: headerData, - stream: st, - done: errc, - }); err != nil { - return err - } - if errc != nil { - select { - case err := <-errc: - sc.srv.state.putErrChan(errc) - return err - case <-sc.doneServing: - return http2errClientDisconnected - case <-st.cw: - return http2errStreamClosed - } - } - return nil -} - -// called from handler goroutines. -func (sc *http2serverConn) write100ContinueHeaders(st *http2stream) { - sc.writeFrameFromHandler(http2FrameWriteRequest{ - write: http2write100ContinueHeadersFrame{st.id}, - stream: st, - }) -} - -// A bodyReadMsg tells the server loop that the http.Handler read n -// bytes of the DATA from the client on the given stream. -type http2bodyReadMsg struct { - st *http2stream - n int -} - -// called from handler goroutines. -// Notes that the handler for the given stream ID read n bytes of its body -// and schedules flow control tokens to be sent. -func (sc *http2serverConn) noteBodyReadFromHandler(st *http2stream, n int, err error) { - sc.serveG.checkNotOn() // NOT on - if n > 0 { - select { - case sc.bodyReadCh <- http2bodyReadMsg{st, n}: - case <-sc.doneServing: - } - } -} - -func (sc *http2serverConn) noteBodyRead(st *http2stream, n int) { - sc.serveG.check() - sc.sendWindowUpdate(nil, n) // conn-level - if st.state != http2stateHalfClosedRemote && st.state != http2stateClosed { - // Don't send this WINDOW_UPDATE if the stream is closed - // remotely. - sc.sendWindowUpdate(st, n) - } -} - -// st may be nil for conn-level -func (sc *http2serverConn) sendWindowUpdate32(st *http2stream, n int32) { - sc.sendWindowUpdate(st, int(n)) -} - -// st may be nil for conn-level -func (sc *http2serverConn) sendWindowUpdate(st *http2stream, n int) { - sc.serveG.check() - var streamID uint32 - var send int32 - if st == nil { - send = sc.inflow.add(n) - } else { - streamID = st.id - send = st.inflow.add(n) - } - if send == 0 { - return - } - sc.writeFrame(http2FrameWriteRequest{ - write: http2writeWindowUpdate{streamID: streamID, n: uint32(send)}, - stream: st, - }) -} - -// requestBody is the Handler's Request.Body type. -// Read and Close may be called concurrently. -type http2requestBody struct { - _ http2incomparable - stream *http2stream - conn *http2serverConn - closeOnce sync.Once // for use by Close only - sawEOF bool // for use by Read only - pipe *http2pipe // non-nil if we have an HTTP entity message body - needsContinue bool // need to send a 100-continue -} - -func (b *http2requestBody) Close() error { - b.closeOnce.Do(func() { - if b.pipe != nil { - b.pipe.BreakWithError(http2errClosedBody) - } - }) - return nil -} - -func (b *http2requestBody) Read(p []byte) (n int, err error) { - if b.needsContinue { - b.needsContinue = false - b.conn.write100ContinueHeaders(b.stream) - } - if b.pipe == nil || b.sawEOF { - return 0, io.EOF - } - n, err = b.pipe.Read(p) - if err == io.EOF { - b.sawEOF = true - } - if b.conn == nil { - return - } - b.conn.noteBodyReadFromHandler(b.stream, n, err) - return -} - -// responseWriter is the http.ResponseWriter implementation. It's -// intentionally small (1 pointer wide) to minimize garbage. The -// responseWriterState pointer inside is zeroed at the end of a -// request (in handlerDone) and calls on the responseWriter thereafter -// simply crash (caller's mistake), but the much larger responseWriterState -// and buffers are reused between multiple requests. -type http2responseWriter struct { - rws *http2responseWriterState -} - -// Optional http.ResponseWriter interfaces implemented. -var ( - _ CloseNotifier = (*http2responseWriter)(nil) - _ Flusher = (*http2responseWriter)(nil) - _ http2stringWriter = (*http2responseWriter)(nil) -) - -type http2responseWriterState struct { - // immutable within a request: - stream *http2stream - req *Request - conn *http2serverConn - - // TODO: adjust buffer writing sizes based on server config, frame size updates from peer, etc - bw *bufio.Writer // writing to a chunkWriter{this *responseWriterState} - - // mutated by http.Handler goroutine: - handlerHeader Header // nil until called - snapHeader Header // snapshot of handlerHeader at WriteHeader time - trailers []string // set in writeChunk - status int // status code passed to WriteHeader - wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet. - sentHeader bool // have we sent the header frame? - handlerDone bool // handler has finished - - sentContentLen int64 // non-zero if handler set a Content-Length header - wroteBytes int64 - - closeNotifierMu sync.Mutex // guards closeNotifierCh - closeNotifierCh chan bool // nil until first used -} - -type http2chunkWriter struct{ rws *http2responseWriterState } - -func (cw http2chunkWriter) Write(p []byte) (n int, err error) { - n, err = cw.rws.writeChunk(p) - if err == http2errStreamClosed { - // If writing failed because the stream has been closed, - // return the reason it was closed. - err = cw.rws.stream.closeErr - } - return n, err -} - -func (rws *http2responseWriterState) hasTrailers() bool { return len(rws.trailers) > 0 } - -func (rws *http2responseWriterState) hasNonemptyTrailers() bool { - for _, trailer := range rws.trailers { - if _, ok := rws.handlerHeader[trailer]; ok { - return true - } - } - return false -} - -// declareTrailer is called for each Trailer header when the -// response header is written. It notes that a header will need to be -// written in the trailers at the end of the response. -func (rws *http2responseWriterState) declareTrailer(k string) { - k = CanonicalHeaderKey(k) - if !httpguts.ValidTrailerHeader(k) { - // Forbidden by RFC 7230, section 4.1.2. - rws.conn.logf("ignoring invalid trailer %q", k) - return - } - if !http2strSliceContains(rws.trailers, k) { - rws.trailers = append(rws.trailers, k) - } -} - -// writeChunk writes chunks from the bufio.Writer. But because -// bufio.Writer may bypass its chunking, sometimes p may be -// arbitrarily large. -// -// writeChunk is also responsible (on the first chunk) for sending the -// HEADER response. -func (rws *http2responseWriterState) writeChunk(p []byte) (n int, err error) { - if !rws.wroteHeader { - rws.writeHeader(200) - } - - if rws.handlerDone { - rws.promoteUndeclaredTrailers() - } - - isHeadResp := rws.req.Method == "HEAD" - if !rws.sentHeader { - rws.sentHeader = true - var ctype, clen string - if clen = rws.snapHeader.Get("Content-Length"); clen != "" { - rws.snapHeader.Del("Content-Length") - if cl, err := strconv.ParseUint(clen, 10, 63); err == nil { - rws.sentContentLen = int64(cl) - } else { - clen = "" - } - } - _, hasContentLength := rws.snapHeader["Content-Length"] - if !hasContentLength && clen == "" && rws.handlerDone && http2bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) { - clen = strconv.Itoa(len(p)) - } - _, hasContentType := rws.snapHeader["Content-Type"] - // If the Content-Encoding is non-blank, we shouldn't - // sniff the body. See Issue golang.org/issue/31753. - ce := rws.snapHeader.Get("Content-Encoding") - hasCE := len(ce) > 0 - if !hasCE && !hasContentType && http2bodyAllowedForStatus(rws.status) && len(p) > 0 { - ctype = DetectContentType(p) - } - var date string - if _, ok := rws.snapHeader["Date"]; !ok { - // TODO(bradfitz): be faster here, like net/http? measure. - date = time.Now().UTC().Format(TimeFormat) - } - - for _, v := range rws.snapHeader["Trailer"] { - http2foreachHeaderElement(v, rws.declareTrailer) - } - - // "Connection" headers aren't allowed in HTTP/2 (RFC 7540, 8.1.2.2), - // but respect "Connection" == "close" to mean sending a GOAWAY and tearing - // down the TCP connection when idle, like we do for HTTP/1. - // TODO: remove more Connection-specific header fields here, in addition - // to "Connection". - if _, ok := rws.snapHeader["Connection"]; ok { - v := rws.snapHeader.Get("Connection") - delete(rws.snapHeader, "Connection") - if v == "close" { - rws.conn.startGracefulShutdown() - } - } - - endStream := (rws.handlerDone && !rws.hasTrailers() && len(p) == 0) || isHeadResp - err = rws.conn.writeHeaders(rws.stream, &http2writeResHeaders{ - streamID: rws.stream.id, - httpResCode: rws.status, - h: rws.snapHeader, - endStream: endStream, - contentType: ctype, - contentLength: clen, - date: date, - }) - if err != nil { - return 0, err - } - if endStream { - return 0, nil - } - } - if isHeadResp { - return len(p), nil - } - if len(p) == 0 && !rws.handlerDone { - return 0, nil - } - - // only send trailers if they have actually been defined by the - // server handler. - hasNonemptyTrailers := rws.hasNonemptyTrailers() - endStream := rws.handlerDone && !hasNonemptyTrailers - if len(p) > 0 || endStream { - // only send a 0 byte DATA frame if we're ending the stream. - if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil { - return 0, err - } - } - - if rws.handlerDone && hasNonemptyTrailers { - err = rws.conn.writeHeaders(rws.stream, &http2writeResHeaders{ - streamID: rws.stream.id, - h: rws.handlerHeader, - trailers: rws.trailers, - endStream: true, - }) - return len(p), err - } - return len(p), nil -} - -// TrailerPrefix is a magic prefix for ResponseWriter.Header map keys -// that, if present, signals that the map entry is actually for -// the response trailers, and not the response headers. The prefix -// is stripped after the ServeHTTP call finishes and the values are -// sent in the trailers. -// -// This mechanism is intended only for trailers that are not known -// prior to the headers being written. If the set of trailers is fixed -// or known before the header is written, the normal Go trailers mechanism -// is preferred: -// -// https://golang.org/pkg/net/http/#ResponseWriter -// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers -const http2TrailerPrefix = "Trailer:" - -// promoteUndeclaredTrailers permits http.Handlers to set trailers -// after the header has already been flushed. Because the Go -// ResponseWriter interface has no way to set Trailers (only the -// Header), and because we didn't want to expand the ResponseWriter -// interface, and because nobody used trailers, and because RFC 7230 -// says you SHOULD (but not must) predeclare any trailers in the -// header, the official ResponseWriter rules said trailers in Go must -// be predeclared, and then we reuse the same ResponseWriter.Header() -// map to mean both Headers and Trailers. When it's time to write the -// Trailers, we pick out the fields of Headers that were declared as -// trailers. That worked for a while, until we found the first major -// user of Trailers in the wild: gRPC (using them only over http2), -// and gRPC libraries permit setting trailers mid-stream without -// predeclaring them. So: change of plans. We still permit the old -// way, but we also permit this hack: if a Header() key begins with -// "Trailer:", the suffix of that key is a Trailer. Because ':' is an -// invalid token byte anyway, there is no ambiguity. (And it's already -// filtered out) It's mildly hacky, but not terrible. -// -// This method runs after the Handler is done and promotes any Header -// fields to be trailers. -func (rws *http2responseWriterState) promoteUndeclaredTrailers() { - for k, vv := range rws.handlerHeader { - if !strings.HasPrefix(k, http2TrailerPrefix) { - continue - } - trailerKey := strings.TrimPrefix(k, http2TrailerPrefix) - rws.declareTrailer(trailerKey) - rws.handlerHeader[CanonicalHeaderKey(trailerKey)] = vv - } - - if len(rws.trailers) > 1 { - sorter := http2sorterPool.Get().(*http2sorter) - sorter.SortStrings(rws.trailers) - http2sorterPool.Put(sorter) - } -} - -func (w *http2responseWriter) SetReadDeadline(deadline time.Time) error { - st := w.rws.stream - if !deadline.IsZero() && deadline.Before(time.Now()) { - // If we're setting a deadline in the past, reset the stream immediately - // so writes after SetWriteDeadline returns will fail. - st.onReadTimeout() - return nil - } - w.rws.conn.sendServeMsg(func(sc *http2serverConn) { - if st.readDeadline != nil { - if !st.readDeadline.Stop() { - // Deadline already exceeded, or stream has been closed. - return - } - } - if deadline.IsZero() { - st.readDeadline = nil - } else if st.readDeadline == nil { - st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout) - } else { - st.readDeadline.Reset(deadline.Sub(time.Now())) - } - }) - return nil -} - -func (w *http2responseWriter) SetWriteDeadline(deadline time.Time) error { - st := w.rws.stream - if !deadline.IsZero() && deadline.Before(time.Now()) { - // If we're setting a deadline in the past, reset the stream immediately - // so writes after SetWriteDeadline returns will fail. - st.onWriteTimeout() - return nil - } - w.rws.conn.sendServeMsg(func(sc *http2serverConn) { - if st.writeDeadline != nil { - if !st.writeDeadline.Stop() { - // Deadline already exceeded, or stream has been closed. - return - } - } - if deadline.IsZero() { - st.writeDeadline = nil - } else if st.writeDeadline == nil { - st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout) - } else { - st.writeDeadline.Reset(deadline.Sub(time.Now())) - } - }) - return nil -} - -func (w *http2responseWriter) EnableFullDuplex() error { - // We always support full duplex responses, so this is a no-op. - return nil -} - -func (w *http2responseWriter) Flush() { - w.FlushError() -} - -func (w *http2responseWriter) FlushError() error { - rws := w.rws - if rws == nil { - panic("Header called after Handler finished") - } - var err error - if rws.bw.Buffered() > 0 { - err = rws.bw.Flush() - } else { - // The bufio.Writer won't call chunkWriter.Write - // (writeChunk with zero bytes), so we have to do it - // ourselves to force the HTTP response header and/or - // final DATA frame (with END_STREAM) to be sent. - _, err = http2chunkWriter{rws}.Write(nil) - if err == nil { - select { - case <-rws.stream.cw: - err = rws.stream.closeErr - default: - } - } - } - return err -} - -func (w *http2responseWriter) CloseNotify() <-chan bool { - rws := w.rws - if rws == nil { - panic("CloseNotify called after Handler finished") - } - rws.closeNotifierMu.Lock() - ch := rws.closeNotifierCh - if ch == nil { - ch = make(chan bool, 1) - rws.closeNotifierCh = ch - cw := rws.stream.cw - go func() { - cw.Wait() // wait for close - ch <- true - }() - } - rws.closeNotifierMu.Unlock() - return ch -} - -func (w *http2responseWriter) Header() Header { - rws := w.rws - if rws == nil { - panic("Header called after Handler finished") - } - if rws.handlerHeader == nil { - rws.handlerHeader = make(Header) - } - return rws.handlerHeader -} - -// checkWriteHeaderCode is a copy of net/http's checkWriteHeaderCode. -func http2checkWriteHeaderCode(code int) { - // Issue 22880: require valid WriteHeader status codes. - // For now we only enforce that it's three digits. - // In the future we might block things over 599 (600 and above aren't defined - // at http://httpwg.org/specs/rfc7231.html#status.codes). - // But for now any three digits. - // - // We used to send "HTTP/1.1 000 0" on the wire in responses but there's - // no equivalent bogus thing we can realistically send in HTTP/2, - // so we'll consistently panic instead and help people find their bugs - // early. (We can't return an error from WriteHeader even if we wanted to.) - if code < 100 || code > 999 { - panic(fmt.Sprintf("invalid WriteHeader code %v", code)) - } -} - -func (w *http2responseWriter) WriteHeader(code int) { - rws := w.rws - if rws == nil { - panic("WriteHeader called after Handler finished") - } - rws.writeHeader(code) -} - -func (rws *http2responseWriterState) writeHeader(code int) { - if rws.wroteHeader { - return - } - - http2checkWriteHeaderCode(code) - - // Handle informational headers - if code >= 100 && code <= 199 { - // Per RFC 8297 we must not clear the current header map - h := rws.handlerHeader - - _, cl := h["Content-Length"] - _, te := h["Transfer-Encoding"] - if cl || te { - h = h.Clone() - h.Del("Content-Length") - h.Del("Transfer-Encoding") - } - - rws.conn.writeHeaders(rws.stream, &http2writeResHeaders{ - streamID: rws.stream.id, - httpResCode: code, - h: h, - endStream: rws.handlerDone && !rws.hasTrailers(), - }) - - return - } - - rws.wroteHeader = true - rws.status = code - if len(rws.handlerHeader) > 0 { - rws.snapHeader = http2cloneHeader(rws.handlerHeader) - } -} - -func http2cloneHeader(h Header) Header { - h2 := make(Header, len(h)) - for k, vv := range h { - vv2 := make([]string, len(vv)) - copy(vv2, vv) - h2[k] = vv2 - } - return h2 -} - -// The Life Of A Write is like this: -// -// * Handler calls w.Write or w.WriteString -> -// * -> rws.bw (*bufio.Writer) -> -// * (Handler might call Flush) -// * -> chunkWriter{rws} -// * -> responseWriterState.writeChunk(p []byte) -// * -> responseWriterState.writeChunk (most of the magic; see comment there) -func (w *http2responseWriter) Write(p []byte) (n int, err error) { - return w.write(len(p), p, "") -} - -func (w *http2responseWriter) WriteString(s string) (n int, err error) { - return w.write(len(s), nil, s) -} - -// either dataB or dataS is non-zero. -func (w *http2responseWriter) write(lenData int, dataB []byte, dataS string) (n int, err error) { - rws := w.rws - if rws == nil { - panic("Write called after Handler finished") - } - if !rws.wroteHeader { - w.WriteHeader(200) - } - if !http2bodyAllowedForStatus(rws.status) { - return 0, ErrBodyNotAllowed - } - rws.wroteBytes += int64(len(dataB)) + int64(len(dataS)) // only one can be set - if rws.sentContentLen != 0 && rws.wroteBytes > rws.sentContentLen { - // TODO: send a RST_STREAM - return 0, errors.New("http2: handler wrote more than declared Content-Length") - } - - if dataB != nil { - return rws.bw.Write(dataB) - } else { - return rws.bw.WriteString(dataS) - } -} - -func (w *http2responseWriter) handlerDone() { - rws := w.rws - rws.handlerDone = true - w.Flush() - w.rws = nil - http2responseWriterStatePool.Put(rws) -} - -// Push errors. -var ( - http2ErrRecursivePush = errors.New("http2: recursive push not allowed") - http2ErrPushLimitReached = errors.New("http2: push would exceed peer's SETTINGS_MAX_CONCURRENT_STREAMS") -) - -var _ Pusher = (*http2responseWriter)(nil) - -func (w *http2responseWriter) Push(target string, opts *PushOptions) error { - st := w.rws.stream - sc := st.sc - sc.serveG.checkNotOn() - - // No recursive pushes: "PUSH_PROMISE frames MUST only be sent on a peer-initiated stream." - // http://tools.ietf.org/html/rfc7540#section-6.6 - if st.isPushed() { - return http2ErrRecursivePush - } - - if opts == nil { - opts = new(PushOptions) - } - - // Default options. - if opts.Method == "" { - opts.Method = "GET" - } - if opts.Header == nil { - opts.Header = Header{} - } - wantScheme := "http" - if w.rws.req.TLS != nil { - wantScheme = "https" - } - - // Validate the request. - u, err := url.Parse(target) - if err != nil { - return err - } - if u.Scheme == "" { - if !strings.HasPrefix(target, "/") { - return fmt.Errorf("target must be an absolute URL or an absolute path: %q", target) - } - u.Scheme = wantScheme - u.Host = w.rws.req.Host - } else { - if u.Scheme != wantScheme { - return fmt.Errorf("cannot push URL with scheme %q from request with scheme %q", u.Scheme, wantScheme) - } - if u.Host == "" { - return errors.New("URL must have a host") - } - } - for k := range opts.Header { - if strings.HasPrefix(k, ":") { - return fmt.Errorf("promised request headers cannot include pseudo header %q", k) - } - // These headers are meaningful only if the request has a body, - // but PUSH_PROMISE requests cannot have a body. - // http://tools.ietf.org/html/rfc7540#section-8.2 - // Also disallow Host, since the promised URL must be absolute. - if http2asciiEqualFold(k, "content-length") || - http2asciiEqualFold(k, "content-encoding") || - http2asciiEqualFold(k, "trailer") || - http2asciiEqualFold(k, "te") || - http2asciiEqualFold(k, "expect") || - http2asciiEqualFold(k, "host") { - return fmt.Errorf("promised request headers cannot include %q", k) - } - } - if err := http2checkValidHTTP2RequestHeaders(opts.Header); err != nil { - return err - } - - // The RFC effectively limits promised requests to GET and HEAD: - // "Promised requests MUST be cacheable [GET, HEAD, or POST], and MUST be safe [GET or HEAD]" - // http://tools.ietf.org/html/rfc7540#section-8.2 - if opts.Method != "GET" && opts.Method != "HEAD" { - return fmt.Errorf("method %q must be GET or HEAD", opts.Method) - } - - msg := &http2startPushRequest{ - parent: st, - method: opts.Method, - url: u, - header: http2cloneHeader(opts.Header), - done: sc.srv.state.getErrChan(), - } - - select { - case <-sc.doneServing: - return http2errClientDisconnected - case <-st.cw: - return http2errStreamClosed - case sc.serveMsgCh <- msg: - } - - select { - case <-sc.doneServing: - return http2errClientDisconnected - case <-st.cw: - return http2errStreamClosed - case err := <-msg.done: - sc.srv.state.putErrChan(msg.done) - return err - } -} - -type http2startPushRequest struct { - parent *http2stream - method string - url *url.URL - header Header - done chan error -} - -func (sc *http2serverConn) startPush(msg *http2startPushRequest) { - sc.serveG.check() - - // http://tools.ietf.org/html/rfc7540#section-6.6. - // PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that - // is in either the "open" or "half-closed (remote)" state. - if msg.parent.state != http2stateOpen && msg.parent.state != http2stateHalfClosedRemote { - // responseWriter.Push checks that the stream is peer-initiated. - msg.done <- http2errStreamClosed - return - } - - // http://tools.ietf.org/html/rfc7540#section-6.6. - if !sc.pushEnabled { - msg.done <- ErrNotSupported - return - } - - // PUSH_PROMISE frames must be sent in increasing order by stream ID, so - // we allocate an ID for the promised stream lazily, when the PUSH_PROMISE - // is written. Once the ID is allocated, we start the request handler. - allocatePromisedID := func() (uint32, error) { - sc.serveG.check() - - // Check this again, just in case. Technically, we might have received - // an updated SETTINGS by the time we got around to writing this frame. - if !sc.pushEnabled { - return 0, ErrNotSupported - } - // http://tools.ietf.org/html/rfc7540#section-6.5.2. - if sc.curPushedStreams+1 > sc.clientMaxStreams { - return 0, http2ErrPushLimitReached - } - - // http://tools.ietf.org/html/rfc7540#section-5.1.1. - // Streams initiated by the server MUST use even-numbered identifiers. - // A server that is unable to establish a new stream identifier can send a GOAWAY - // frame so that the client is forced to open a new connection for new streams. - if sc.maxPushPromiseID+2 >= 1<<31 { - sc.startGracefulShutdownInternal() - return 0, http2ErrPushLimitReached - } - sc.maxPushPromiseID += 2 - promisedID := sc.maxPushPromiseID - - // http://tools.ietf.org/html/rfc7540#section-8.2. - // Strictly speaking, the new stream should start in "reserved (local)", then - // transition to "half closed (remote)" after sending the initial HEADERS, but - // we start in "half closed (remote)" for simplicity. - // See further comments at the definition of stateHalfClosedRemote. - promised := sc.newStream(promisedID, msg.parent.id, http2stateHalfClosedRemote, http2defaultRFC9218Priority(sc.priorityAware && !sc.hasIntermediary)) - rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{ - Method: msg.method, - Scheme: msg.url.Scheme, - Authority: msg.url.Host, - Path: msg.url.RequestURI(), - Header: http2cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE - }) - if err != nil { - // Should not happen, since we've already validated msg.url. - panic(fmt.Sprintf("newWriterAndRequestNoBody(%+v): %v", msg.url, err)) - } - - sc.curHandlers++ - go sc.runHandler(rw, req, sc.handler.ServeHTTP) - return promisedID, nil - } - - sc.writeFrame(http2FrameWriteRequest{ - write: &http2writePushPromise{ - streamID: msg.parent.id, - method: msg.method, - url: msg.url, - h: msg.header, - allocatePromisedID: allocatePromisedID, - }, - stream: msg.parent, - done: msg.done, - }) -} - -// foreachHeaderElement splits v according to the "#rule" construction -// in RFC 7230 section 7 and calls fn for each non-empty element. -func http2foreachHeaderElement(v string, fn func(string)) { - v = textproto.TrimString(v) - if v == "" { - return - } - if !strings.Contains(v, ",") { - fn(v) - return - } - for _, f := range strings.Split(v, ",") { - if f = textproto.TrimString(f); f != "" { - fn(f) - } - } -} - -// From http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.2 -var http2connHeaders = []string{ - "Connection", - "Keep-Alive", - "Proxy-Connection", - "Transfer-Encoding", - "Upgrade", -} - -// checkValidHTTP2RequestHeaders checks whether h is a valid HTTP/2 request, -// per RFC 7540 Section 8.1.2.2. -// The returned error is reported to users. -func http2checkValidHTTP2RequestHeaders(h Header) error { - for _, k := range http2connHeaders { - if _, ok := h[k]; ok { - return fmt.Errorf("request header %q is not valid in HTTP/2", k) - } - } - te := h["Te"] - if len(te) > 0 && (len(te) > 1 || (te[0] != "trailers" && te[0] != "")) { - return errors.New(`request header "TE" may only be "trailers" in HTTP/2`) - } - return nil -} - -func http2new400Handler(err error) HandlerFunc { - return func(w ResponseWriter, r *Request) { - Error(w, err.Error(), StatusBadRequest) - } -} - -// h1ServerKeepAlivesDisabled reports whether hs has its keep-alives -// disabled. See comments on h1ServerShutdownChan above for why -// the code is written this way. -func http2h1ServerKeepAlivesDisabled(hs *Server) bool { - var x interface{} = hs - type I interface { - doKeepAlives() bool - } - if hs, ok := x.(I); ok { - return !hs.doKeepAlives() - } - return false -} - -func (sc *http2serverConn) countError(name string, err error) error { - if sc == nil || sc.srv == nil { - return err - } - f := sc.countErrorFunc - if f == nil { - return err - } - var typ string - var code http2ErrCode - switch e := err.(type) { - case http2ConnectionError: - typ = "conn" - code = http2ErrCode(e) - case http2StreamError: - typ = "stream" - code = http2ErrCode(e.Code) - default: - return err - } - codeStr := http2errCodeName[code] - if codeStr == "" { - codeStr = strconv.Itoa(int(code)) - } - f(fmt.Sprintf("%s_%s_%s", typ, codeStr, name)) - return err -} - -const ( - // transportDefaultConnFlow is how many connection-level flow control - // tokens we give the server at start-up, past the default 64k. - http2transportDefaultConnFlow = 1 << 30 - - // transportDefaultStreamFlow is how many stream-level flow - // control tokens we announce to the peer, and how many bytes - // we buffer per stream. - http2transportDefaultStreamFlow = 4 << 20 - - http2defaultUserAgent = "Go-http-client/2.0" - - // initialMaxConcurrentStreams is a connections maxConcurrentStreams until - // it's received servers initial SETTINGS frame, which corresponds with the - // spec's minimum recommended value. - http2initialMaxConcurrentStreams = 100 - - // defaultMaxConcurrentStreams is a connections default maxConcurrentStreams - // if the server doesn't include one in its initial SETTINGS frame. - http2defaultMaxConcurrentStreams = 1000 -) - -// Transport is an HTTP/2 Transport. -// -// A Transport internally caches connections to servers. It is safe -// for concurrent use by multiple goroutines. -type http2Transport struct { - // DialTLSContext specifies an optional dial function with context for - // creating TLS connections for requests. - // - // If DialTLSContext and DialTLS is nil, tls.Dial is used. - // - // If the returned net.Conn has a ConnectionState method like tls.Conn, - // it will be used to set http.Response.TLS. - DialTLSContext func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) - - // DialTLS specifies an optional dial function for creating - // TLS connections for requests. - // - // If DialTLSContext and DialTLS is nil, tls.Dial is used. - // - // Deprecated: Use DialTLSContext instead, which allows the transport - // to cancel dials as soon as they are no longer needed. - // If both are set, DialTLSContext takes priority. - DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error) - - // TLSClientConfig specifies the TLS configuration to use with - // tls.Client. If nil, the default configuration is used. - TLSClientConfig *tls.Config - - // ConnPool optionally specifies an alternate connection pool to use. - // If nil, the default is used. - ConnPool http2ClientConnPool - - // DisableCompression, if true, prevents the Transport from - // requesting compression with an "Accept-Encoding: gzip" - // request header when the Request contains no existing - // Accept-Encoding value. If the Transport requests gzip on - // its own and gets a gzipped response, it's transparently - // decoded in the Response.Body. However, if the user - // explicitly requested gzip it is not automatically - // uncompressed. - DisableCompression bool - - // AllowHTTP, if true, permits HTTP/2 requests using the insecure, - // plain-text "http" scheme. Note that this does not enable h2c support. - AllowHTTP bool - - // MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to - // send in the initial settings frame. It is how many bytes - // of response headers are allowed. Unlike the http2 spec, zero here - // means to use a default limit (currently 10MB). If you actually - // want to advertise an unlimited value to the peer, Transport - // interprets the highest possible value here (0xffffffff or 1<<32-1) - // to mean no limit. - MaxHeaderListSize uint32 - - // MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the - // initial settings frame. It is the size in bytes of the largest frame - // payload that the sender is willing to receive. If 0, no setting is - // sent, and the value is provided by the peer, which should be 16384 - // according to the spec: - // https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2. - // Values are bounded in the range 16k to 16M. - MaxReadFrameSize uint32 - - // MaxDecoderHeaderTableSize optionally specifies the http2 - // SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It - // informs the remote endpoint of the maximum size of the header compression - // table used to decode header blocks, in octets. If zero, the default value - // of 4096 is used. - MaxDecoderHeaderTableSize uint32 - - // MaxEncoderHeaderTableSize optionally specifies an upper limit for the - // header compression table used for encoding request headers. Received - // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero, - // the default value of 4096 is used. - MaxEncoderHeaderTableSize uint32 - - // StrictMaxConcurrentStreams controls whether the server's - // SETTINGS_MAX_CONCURRENT_STREAMS should be respected - // globally. If false, new TCP connections are created to the - // server as needed to keep each under the per-connection - // SETTINGS_MAX_CONCURRENT_STREAMS limit. If true, the - // server's SETTINGS_MAX_CONCURRENT_STREAMS is interpreted as - // a global limit and callers of RoundTrip block when needed, - // waiting for their turn. - StrictMaxConcurrentStreams bool - - // IdleConnTimeout is the maximum amount of time an idle - // (keep-alive) connection will remain idle before closing - // itself. - // Zero means no limit. - IdleConnTimeout time.Duration - - // ReadIdleTimeout is the timeout after which a health check using ping - // frame will be carried out if no frame is received on the connection. - // Note that a ping response will is considered a received frame, so if - // there is no other traffic on the connection, the health check will - // be performed every ReadIdleTimeout interval. - // If zero, no health check is performed. - ReadIdleTimeout time.Duration - - // PingTimeout is the timeout after which the connection will be closed - // if a response to Ping is not received. - // Defaults to 15s. - PingTimeout time.Duration - - // WriteByteTimeout is the timeout after which the connection will be - // closed no data can be written to it. The timeout begins when data is - // available to write, and is extended whenever any bytes are written. - WriteByteTimeout time.Duration - - // CountError, if non-nil, is called on HTTP/2 transport errors. - // It's intended to increment a metric for monitoring, such - // as an expvar or Prometheus metric. - // The errType consists of only ASCII word characters. - CountError func(errType string) - - // t1, if non-nil, is the standard library Transport using - // this transport. Its settings are used (but not its - // RoundTrip method, etc). - t1 *Transport - - connPoolOnce sync.Once - connPoolOrDef http2ClientConnPool // non-nil version of ConnPool - - *http2transportTestHooks -} - -// Hook points used for testing. -// Outside of tests, t.transportTestHooks is nil and these all have minimal implementations. -// Inside tests, see the testSyncHooks function docs. - -type http2transportTestHooks struct { - newclientconn func(*http2ClientConn) -} - -func (t *http2Transport) maxHeaderListSize() uint32 { - n := int64(t.MaxHeaderListSize) - if t.t1 != nil && t.t1.MaxResponseHeaderBytes != 0 { - n = t.t1.MaxResponseHeaderBytes - if n > 0 { - n = http2adjustHTTP1MaxHeaderSize(n) - } - } - if n <= 0 { - return 10 << 20 - } - if n >= 0xffffffff { - return 0 - } - return uint32(n) -} - -func (t *http2Transport) disableCompression() bool { - return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression) -} - -// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2. -// It returns an error if t1 has already been HTTP/2-enabled. -// -// Use ConfigureTransports instead to configure the HTTP/2 Transport. -func http2ConfigureTransport(t1 *Transport) error { - _, err := http2ConfigureTransports(t1) - return err -} - -// ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2. -// It returns a new HTTP/2 Transport for further configuration. -// It returns an error if t1 has already been HTTP/2-enabled. -func http2ConfigureTransports(t1 *Transport) (*http2Transport, error) { - return http2configureTransports(t1) -} - -func http2configureTransports(t1 *Transport) (*http2Transport, error) { - connPool := new(http2clientConnPool) - t2 := &http2Transport{ - ConnPool: http2noDialClientConnPool{connPool}, - t1: t1, - } - connPool.t = t2 - if err := http2registerHTTPSProtocol(t1, http2noDialH2RoundTripper{t2}); err != nil { - return nil, err - } - if t1.TLSClientConfig == nil { - t1.TLSClientConfig = new(tls.Config) - } - if !http2strSliceContains(t1.TLSClientConfig.NextProtos, "h2") { - t1.TLSClientConfig.NextProtos = append([]string{"h2"}, t1.TLSClientConfig.NextProtos...) - } - if !http2strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") { - t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1") - } - upgradeFn := func(scheme, authority string, c net.Conn) RoundTripper { - addr := http2authorityAddr(scheme, authority) - if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil { - go c.Close() - return http2erringRoundTripper{err} - } else if !used { - // Turns out we don't need this c. - // For example, two goroutines made requests to the same host - // at the same time, both kicking off TCP dials. (since protocol - // was unknown) - go c.Close() - } - if scheme == "http" { - return (*http2unencryptedTransport)(t2) - } - return t2 - } - if t1.TLSNextProto == nil { - t1.TLSNextProto = make(map[string]func(string, *tls.Conn) RoundTripper) - } - t1.TLSNextProto[http2NextProtoTLS] = func(authority string, c *tls.Conn) RoundTripper { - return upgradeFn("https", authority, c) - } - // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns. - t1.TLSNextProto[http2nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) RoundTripper { - nc, err := http2unencryptedNetConnFromTLSConn(c) - if err != nil { - go c.Close() - return http2erringRoundTripper{err} - } - return upgradeFn("http", authority, nc) - } - return t2, nil -} - -// unencryptedTransport is a Transport with a RoundTrip method that -// always permits http:// URLs. -type http2unencryptedTransport http2Transport - -func (t *http2unencryptedTransport) RoundTrip(req *Request) (*Response, error) { - return (*http2Transport)(t).RoundTripOpt(req, http2RoundTripOpt{allowHTTP: true}) -} - -func (t *http2Transport) connPool() http2ClientConnPool { - t.connPoolOnce.Do(t.initConnPool) - return t.connPoolOrDef -} - -func (t *http2Transport) initConnPool() { - if t.ConnPool != nil { - t.connPoolOrDef = t.ConnPool - } else { - t.connPoolOrDef = &http2clientConnPool{t: t} - } -} - -// ClientConn is the state of a single HTTP/2 client connection to an -// HTTP/2 server. -type http2ClientConn struct { - t *http2Transport - tconn net.Conn // usually *tls.Conn, except specialized impls - tlsState *tls.ConnectionState // nil only for specialized impls - atomicReused uint32 // whether conn is being reused; atomic - singleUse bool // whether being used for a single http.Request - getConnCalled bool // used by clientConnPool - - // readLoop goroutine fields: - readerDone chan struct{} // closed on error - readerErr error // set before readerDone is closed - - idleTimeout time.Duration // or 0 for never - idleTimer *time.Timer - - mu sync.Mutex // guards following - cond *sync.Cond // hold mu; broadcast on flow/closed changes - flow http2outflow // our conn-level flow control quota (cs.outflow is per stream) - inflow http2inflow // peer's conn-level flow control - doNotReuse bool // whether conn is marked to not be reused for any future requests - closing bool - closed bool - closedOnIdle bool // true if conn was closed for idleness - seenSettings bool // true if we've seen a settings frame, false otherwise - seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails - wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back - goAway *http2GoAwayFrame // if non-nil, the GoAwayFrame we received - goAwayDebug string // goAway frame's debug data, retained as a string - streams map[uint32]*http2clientStream // client-initiated - streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip - nextStreamID uint32 - pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams - pings map[[8]byte]chan struct{} // in flight ping data to notification channel - br *bufio.Reader - lastActive time.Time - lastIdle time.Time // time last idle - // Settings from peer: (also guarded by wmu) - maxFrameSize uint32 - maxConcurrentStreams uint32 - peerMaxHeaderListSize uint64 - peerMaxHeaderTableSize uint32 - initialWindowSize uint32 - initialStreamRecvWindowSize int32 - readIdleTimeout time.Duration - pingTimeout time.Duration - extendedConnectAllowed bool - strictMaxConcurrentStreams bool - - // rstStreamPingsBlocked works around an unfortunate gRPC behavior. - // gRPC strictly limits the number of PING frames that it will receive. - // The default is two pings per two hours, but the limit resets every time - // the gRPC endpoint sends a HEADERS or DATA frame. See golang/go#70575. - // - // rstStreamPingsBlocked is set after receiving a response to a PING frame - // bundled with an RST_STREAM (see pendingResets below), and cleared after - // receiving a HEADERS or DATA frame. - rstStreamPingsBlocked bool - - // pendingResets is the number of RST_STREAM frames we have sent to the peer, - // without confirming that the peer has received them. When we send a RST_STREAM, - // we bundle it with a PING frame, unless a PING is already in flight. We count - // the reset stream against the connection's concurrency limit until we get - // a PING response. This limits the number of requests we'll try to send to a - // completely unresponsive connection. - pendingResets int - - // readBeforeStreamID is the smallest stream ID that has not been followed by - // a frame read from the peer. We use this to determine when a request may - // have been sent to a completely unresponsive connection: - // If the request ID is less than readBeforeStreamID, then we have had some - // indication of life on the connection since sending the request. - readBeforeStreamID uint32 - - // reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests. - // Write to reqHeaderMu to lock it, read from it to unlock. - // Lock reqmu BEFORE mu or wmu. - reqHeaderMu chan struct{} - - // internalStateHook reports state changes back to the net/http.ClientConn. - // Note that this is different from the user state hook registered by - // net/http.ClientConn.SetStateHook: The internal hook calls ClientConn, - // which calls the user hook. - internalStateHook func() - - // wmu is held while writing. - // Acquire BEFORE mu when holding both, to avoid blocking mu on network writes. - // Only acquire both at the same time when changing peer settings. - wmu sync.Mutex - bw *bufio.Writer - fr *http2Framer - werr error // first write error that has occurred - hbuf bytes.Buffer // HPACK encoder writes into this - henc *hpack.Encoder -} - -// clientStream is the state for a single HTTP/2 stream. One of these -// is created for each Transport.RoundTrip call. -type http2clientStream struct { - cc *http2ClientConn - - // Fields of Request that we may access even after the response body is closed. - ctx context.Context - reqCancel <-chan struct{} - - trace *httptrace.ClientTrace // or nil - ID uint32 - bufPipe http2pipe // buffered pipe with the flow-controlled response payload - requestedGzip bool - isHead bool - - abortOnce sync.Once - abort chan struct{} // closed to signal stream should end immediately - abortErr error // set if abort is closed - - peerClosed chan struct{} // closed when the peer sends an END_STREAM flag - donec chan struct{} // closed after the stream is in the closed state - on100 chan struct{} // buffered; written to if a 100 is received - - respHeaderRecv chan struct{} // closed when headers are received - res *Response // set if respHeaderRecv is closed - - flow http2outflow // guarded by cc.mu - inflow http2inflow // guarded by cc.mu - bytesRemain int64 // -1 means unknown; owned by transportResponseBody.Read - readErr error // sticky read error; owned by transportResponseBody.Read - - reqBody io.ReadCloser - reqBodyContentLength int64 // -1 means unknown - reqBodyClosed chan struct{} // guarded by cc.mu; non-nil on Close, closed when done - - // owned by writeRequest: - sentEndStream bool // sent an END_STREAM flag to the peer - sentHeaders bool - - // owned by clientConnReadLoop: - firstByte bool // got the first response byte - pastHeaders bool // got first MetaHeadersFrame (actual headers) - pastTrailers bool // got optional second MetaHeadersFrame (trailers) - readClosed bool // peer sent an END_STREAM flag - readAborted bool // read loop reset the stream - totalHeaderSize int64 // total size of 1xx headers seen - - trailer Header // accumulated trailers - resTrailer *Header // client's Response.Trailer -} - -var http2got1xxFuncForTests func(int, textproto.MIMEHeader) error - -// get1xxTraceFunc returns the value of request's httptrace.ClientTrace.Got1xxResponse func, -// if any. It returns nil if not set or if the Go version is too old. -func (cs *http2clientStream) get1xxTraceFunc() func(int, textproto.MIMEHeader) error { - if fn := http2got1xxFuncForTests; fn != nil { - return fn - } - return http2traceGot1xxResponseFunc(cs.trace) -} - -func (cs *http2clientStream) abortStream(err error) { - cs.cc.mu.Lock() - defer cs.cc.mu.Unlock() - cs.abortStreamLocked(err) -} - -func (cs *http2clientStream) abortStreamLocked(err error) { - cs.abortOnce.Do(func() { - cs.abortErr = err - close(cs.abort) - }) - if cs.reqBody != nil { - cs.closeReqBodyLocked() - } - // TODO(dneil): Clean up tests where cs.cc.cond is nil. - if cs.cc.cond != nil { - // Wake up writeRequestBody if it is waiting on flow control. - cs.cc.cond.Broadcast() - } -} - -func (cs *http2clientStream) abortRequestBodyWrite() { - cc := cs.cc - cc.mu.Lock() - defer cc.mu.Unlock() - if cs.reqBody != nil && cs.reqBodyClosed == nil { - cs.closeReqBodyLocked() - cc.cond.Broadcast() - } -} - -func (cs *http2clientStream) closeReqBodyLocked() { - if cs.reqBodyClosed != nil { - return - } - cs.reqBodyClosed = make(chan struct{}) - reqBodyClosed := cs.reqBodyClosed - go func() { - cs.reqBody.Close() - close(reqBodyClosed) - }() -} - -type http2stickyErrWriter struct { - conn net.Conn - timeout time.Duration - err *error -} - -func (sew http2stickyErrWriter) Write(p []byte) (n int, err error) { - if *sew.err != nil { - return 0, *sew.err - } - n, err = http2writeWithByteTimeout(sew.conn, sew.timeout, p) - *sew.err = err - return n, err -} - -// noCachedConnError is the concrete type of ErrNoCachedConn, which -// needs to be detected by net/http regardless of whether it's its -// bundled version (in h2_bundle.go with a rewritten type name) or -// from a user's x/net/http2. As such, as it has a unique method name -// (IsHTTP2NoCachedConnError) that net/http sniffs for via func -// isNoCachedConnError. -type http2noCachedConnError struct{} - -func (http2noCachedConnError) IsHTTP2NoCachedConnError() {} - -func (http2noCachedConnError) Error() string { return "http2: no cached connection was available" } - -// isNoCachedConnError reports whether err is of type noCachedConnError -// or its equivalent renamed type in net/http2's h2_bundle.go. Both types -// may coexist in the same running program. -func http2isNoCachedConnError(err error) bool { - _, ok := err.(interface{ IsHTTP2NoCachedConnError() }) - return ok -} - -var http2ErrNoCachedConn error = http2noCachedConnError{} - -// RoundTripOpt are options for the Transport.RoundTripOpt method. -type http2RoundTripOpt struct { - // OnlyCachedConn controls whether RoundTripOpt may - // create a new TCP connection. If set true and - // no cached connection is available, RoundTripOpt - // will return ErrNoCachedConn. - OnlyCachedConn bool - - allowHTTP bool // allow http:// URLs -} - -func (t *http2Transport) RoundTrip(req *Request) (*Response, error) { - return t.RoundTripOpt(req, http2RoundTripOpt{}) -} - -// authorityAddr returns a given authority (a host/IP, or host:port / ip:port) -// and returns a host:port. The port 443 is added if needed. -func http2authorityAddr(scheme string, authority string) (addr string) { - host, port, err := net.SplitHostPort(authority) - if err != nil { // authority didn't have a port - host = authority - port = "" - } - if port == "" { // authority's port was empty - port = "443" - if scheme == "http" { - port = "80" - } - } - if a, err := idna.ToASCII(host); err == nil { - host = a - } - // IPv6 address literal, without a port: - if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { - return host + ":" + port - } - return net.JoinHostPort(host, port) -} - -// RoundTripOpt is like RoundTrip, but takes options. -func (t *http2Transport) RoundTripOpt(req *Request, opt http2RoundTripOpt) (*Response, error) { - switch req.URL.Scheme { - case "https": - // Always okay. - case "http": - if !t.AllowHTTP && !opt.allowHTTP { - return nil, errors.New("http2: unencrypted HTTP/2 not enabled") - } - default: - return nil, errors.New("http2: unsupported scheme") - } - - addr := http2authorityAddr(req.URL.Scheme, req.URL.Host) - for retry := 0; ; retry++ { - cc, err := t.connPool().GetClientConn(req, addr) - if err != nil { - t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err) - return nil, err - } - reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1) - http2traceGotConn(req, cc, reused) - res, err := cc.RoundTrip(req) - if err != nil && retry <= 6 { - roundTripErr := err - if req, err = http2shouldRetryRequest(req, err); err == nil { - // After the first retry, do exponential backoff with 10% jitter. - if retry == 0 { - t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) - continue - } - backoff := float64(uint(1) << (uint(retry) - 1)) - backoff += backoff * (0.1 * mathrand.Float64()) - d := time.Second * time.Duration(backoff) - tm := time.NewTimer(d) - select { - case <-tm.C: - t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) - continue - case <-req.Context().Done(): - tm.Stop() - err = req.Context().Err() - } - } - } - if err == http2errClientConnNotEstablished { - // This ClientConn was created recently, - // this is the first request to use it, - // and the connection is closed and not usable. - // - // In this state, cc.idleTimer will remove the conn from the pool - // when it fires. Stop the timer and remove it here so future requests - // won't try to use this connection. - // - // If the timer has already fired and we're racing it, the redundant - // call to MarkDead is harmless. - if cc.idleTimer != nil { - cc.idleTimer.Stop() - } - t.connPool().MarkDead(cc) - } - if err != nil { - t.vlogf("RoundTrip failure: %v", err) - return nil, err - } - return res, nil - } -} - -// CloseIdleConnections closes any connections which were previously -// connected from previous requests but are now sitting idle. -// It does not interrupt any connections currently in use. -func (t *http2Transport) CloseIdleConnections() { - if cp, ok := t.connPool().(http2clientConnPoolIdleCloser); ok { - cp.closeIdleConnections() - } -} - -var ( - http2errClientConnClosed = errors.New("http2: client conn is closed") - http2errClientConnUnusable = errors.New("http2: client conn not usable") - http2errClientConnNotEstablished = errors.New("http2: client conn could not be established") - http2errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY") - http2errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close") -) - -// shouldRetryRequest is called by RoundTrip when a request fails to get -// response headers. It is always called with a non-nil error. -// It returns either a request to retry (either the same request, or a -// modified clone), or an error if the request can't be replayed. -func http2shouldRetryRequest(req *Request, err error) (*Request, error) { - if !http2canRetryError(err) { - return nil, err - } - // If the Body is nil (or http.NoBody), it's safe to reuse - // this request and its Body. - if req.Body == nil || req.Body == NoBody { - return req, nil - } - - // If the request body can be reset back to its original - // state via the optional req.GetBody, do that. - if req.GetBody != nil { - body, err := req.GetBody() - if err != nil { - return nil, err - } - newReq := *req - newReq.Body = body - return &newReq, nil - } - - // The Request.Body can't reset back to the beginning, but we - // don't seem to have started to read from it yet, so reuse - // the request directly. - if err == http2errClientConnUnusable { - return req, nil - } - - return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", err) -} - -func http2canRetryError(err error) bool { - if err == http2errClientConnUnusable || err == http2errClientConnGotGoAway { - return true - } - if se, ok := err.(http2StreamError); ok { - if se.Code == http2ErrCodeProtocol && se.Cause == http2errFromPeer { - // See golang/go#47635, golang/go#42777 - return true - } - return se.Code == http2ErrCodeRefusedStream - } - return false -} - -func (t *http2Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*http2ClientConn, error) { - if t.http2transportTestHooks != nil { - return t.newClientConn(nil, singleUse, nil) - } - host, _, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - tconn, err := t.dialTLS(ctx, "tcp", addr, t.newTLSConfig(host)) - if err != nil { - return nil, err - } - return t.newClientConn(tconn, singleUse, nil) -} - -func (t *http2Transport) newTLSConfig(host string) *tls.Config { - cfg := new(tls.Config) - if t.TLSClientConfig != nil { - *cfg = *t.TLSClientConfig.Clone() - } - if !http2strSliceContains(cfg.NextProtos, http2NextProtoTLS) { - cfg.NextProtos = append([]string{http2NextProtoTLS}, cfg.NextProtos...) - } - if cfg.ServerName == "" { - cfg.ServerName = host - } - return cfg -} - -func (t *http2Transport) dialTLS(ctx context.Context, network, addr string, tlsCfg *tls.Config) (net.Conn, error) { - if t.DialTLSContext != nil { - return t.DialTLSContext(ctx, network, addr, tlsCfg) - } else if t.DialTLS != nil { - return t.DialTLS(network, addr, tlsCfg) - } - - tlsCn, err := t.dialTLSWithContext(ctx, network, addr, tlsCfg) - if err != nil { - return nil, err - } - state := tlsCn.ConnectionState() - if p := state.NegotiatedProtocol; p != http2NextProtoTLS { - return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, http2NextProtoTLS) - } - if !state.NegotiatedProtocolIsMutual { - return nil, errors.New("http2: could not negotiate protocol mutually") - } - return tlsCn, nil -} - -// disableKeepAlives reports whether connections should be closed as -// soon as possible after handling the first request. -func (t *http2Transport) disableKeepAlives() bool { - return t.t1 != nil && t.t1.DisableKeepAlives -} - -func (t *http2Transport) expectContinueTimeout() time.Duration { - if t.t1 == nil { - return 0 - } - return t.t1.ExpectContinueTimeout -} - -func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) { - return t.newClientConn(c, t.disableKeepAlives(), nil) -} - -func (t *http2Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*http2ClientConn, error) { - conf := http2configFromTransport(t) - cc := &http2ClientConn{ - t: t, - tconn: c, - readerDone: make(chan struct{}), - nextStreamID: 1, - maxFrameSize: 16 << 10, // spec default - initialWindowSize: 65535, // spec default - initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, - maxConcurrentStreams: http2initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. - strictMaxConcurrentStreams: conf.StrictMaxConcurrentRequests, - peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead. - streams: make(map[uint32]*http2clientStream), - singleUse: singleUse, - seenSettingsChan: make(chan struct{}), - wantSettingsAck: true, - readIdleTimeout: conf.SendPingTimeout, - pingTimeout: conf.PingTimeout, - pings: make(map[[8]byte]chan struct{}), - reqHeaderMu: make(chan struct{}, 1), - lastActive: time.Now(), - internalStateHook: internalStateHook, - } - if t.http2transportTestHooks != nil { - t.http2transportTestHooks.newclientconn(cc) - c = cc.tconn - } - if http2VerboseLogs { - t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr()) - } - - cc.cond = sync.NewCond(&cc.mu) - cc.flow.add(int32(http2initialWindowSize)) - - // TODO: adjust this writer size to account for frame size + - // MTU + crypto/tls record padding. - cc.bw = bufio.NewWriter(http2stickyErrWriter{ - conn: c, - timeout: conf.WriteByteTimeout, - err: &cc.werr, - }) - cc.br = bufio.NewReader(c) - cc.fr = http2NewFramer(cc.bw, cc.br) - cc.fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) - if t.CountError != nil { - cc.fr.countError = t.CountError - } - maxHeaderTableSize := conf.MaxDecoderHeaderTableSize - cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil) - cc.fr.MaxHeaderListSize = t.maxHeaderListSize() - - cc.henc = hpack.NewEncoder(&cc.hbuf) - cc.henc.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) - cc.peerMaxHeaderTableSize = http2initialHeaderTableSize - - if cs, ok := c.(http2connectionStater); ok { - state := cs.ConnectionState() - cc.tlsState = &state - } - - initialSettings := []http2Setting{ - {ID: http2SettingEnablePush, Val: 0}, - {ID: http2SettingInitialWindowSize, Val: uint32(cc.initialStreamRecvWindowSize)}, - } - initialSettings = append(initialSettings, http2Setting{ID: http2SettingMaxFrameSize, Val: conf.MaxReadFrameSize}) - if max := t.maxHeaderListSize(); max != 0 { - initialSettings = append(initialSettings, http2Setting{ID: http2SettingMaxHeaderListSize, Val: max}) - } - if maxHeaderTableSize != http2initialHeaderTableSize { - initialSettings = append(initialSettings, http2Setting{ID: http2SettingHeaderTableSize, Val: maxHeaderTableSize}) - } - - cc.bw.Write(http2clientPreface) - cc.fr.WriteSettings(initialSettings...) - cc.fr.WriteWindowUpdate(0, uint32(conf.MaxUploadBufferPerConnection)) - cc.inflow.init(conf.MaxUploadBufferPerConnection + http2initialWindowSize) - cc.bw.Flush() - if cc.werr != nil { - cc.Close() - return nil, cc.werr - } - - // Start the idle timer after the connection is fully initialized. - if d := t.idleConnTimeout(); d != 0 { - cc.idleTimeout = d - cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout) - } - - go cc.readLoop() - return cc, nil -} - -func (cc *http2ClientConn) healthCheck() { - pingTimeout := cc.pingTimeout - // We don't need to periodically ping in the health check, because the readLoop of ClientConn will - // trigger the healthCheck again if there is no frame received. - ctx, cancel := context.WithTimeout(context.Background(), pingTimeout) - defer cancel() - cc.vlogf("http2: Transport sending health check") - err := cc.Ping(ctx) - if err != nil { - cc.vlogf("http2: Transport health check failure: %v", err) - cc.closeForLostPing() - } else { - cc.vlogf("http2: Transport health check success") - } -} - -// SetDoNotReuse marks cc as not reusable for future HTTP requests. -func (cc *http2ClientConn) SetDoNotReuse() { - cc.mu.Lock() - defer cc.mu.Unlock() - cc.doNotReuse = true -} - -func (cc *http2ClientConn) setGoAway(f *http2GoAwayFrame) { - cc.mu.Lock() - defer cc.mu.Unlock() - - old := cc.goAway - cc.goAway = f - - // Merge the previous and current GoAway error frames. - if cc.goAwayDebug == "" { - cc.goAwayDebug = string(f.DebugData()) - } - if old != nil && old.ErrCode != http2ErrCodeNo { - cc.goAway.ErrCode = old.ErrCode - } - last := f.LastStreamID - for streamID, cs := range cc.streams { - if streamID <= last { - // The server's GOAWAY indicates that it received this stream. - // It will either finish processing it, or close the connection - // without doing so. Either way, leave the stream alone for now. - continue - } - if streamID == 1 && cc.goAway.ErrCode != http2ErrCodeNo { - // Don't retry the first stream on a connection if we get a non-NO error. - // If the server is sending an error on a new connection, - // retrying the request on a new one probably isn't going to work. - cs.abortStreamLocked(fmt.Errorf("http2: Transport received GOAWAY from server ErrCode:%v", cc.goAway.ErrCode)) - } else { - // Aborting the stream with errClentConnGotGoAway indicates that - // the request should be retried on a new connection. - cs.abortStreamLocked(http2errClientConnGotGoAway) - } - } -} - -// CanTakeNewRequest reports whether the connection can take a new request, -// meaning it has not been closed or received or sent a GOAWAY. -// -// If the caller is going to immediately make a new request on this -// connection, use ReserveNewRequest instead. -func (cc *http2ClientConn) CanTakeNewRequest() bool { - cc.mu.Lock() - defer cc.mu.Unlock() - return cc.canTakeNewRequestLocked() -} - -// ReserveNewRequest is like CanTakeNewRequest but also reserves a -// concurrent stream in cc. The reservation is decremented on the -// next call to RoundTrip. -func (cc *http2ClientConn) ReserveNewRequest() bool { - cc.mu.Lock() - defer cc.mu.Unlock() - if st := cc.idleStateLocked(); !st.canTakeNewRequest { - return false - } - cc.streamsReserved++ - return true -} - -// ClientConnState describes the state of a ClientConn. -type http2ClientConnState struct { - // Closed is whether the connection is closed. - Closed bool - - // Closing is whether the connection is in the process of - // closing. It may be closing due to shutdown, being a - // single-use connection, being marked as DoNotReuse, or - // having received a GOAWAY frame. - Closing bool - - // StreamsActive is how many streams are active. - StreamsActive int - - // StreamsReserved is how many streams have been reserved via - // ClientConn.ReserveNewRequest. - StreamsReserved int - - // StreamsPending is how many requests have been sent in excess - // of the peer's advertised MaxConcurrentStreams setting and - // are waiting for other streams to complete. - StreamsPending int - - // MaxConcurrentStreams is how many concurrent streams the - // peer advertised as acceptable. Zero means no SETTINGS - // frame has been received yet. - MaxConcurrentStreams uint32 - - // LastIdle, if non-zero, is when the connection last - // transitioned to idle state. - LastIdle time.Time -} - -// State returns a snapshot of cc's state. -func (cc *http2ClientConn) State() http2ClientConnState { - cc.wmu.Lock() - maxConcurrent := cc.maxConcurrentStreams - if !cc.seenSettings { - maxConcurrent = 0 - } - cc.wmu.Unlock() - - cc.mu.Lock() - defer cc.mu.Unlock() - return http2ClientConnState{ - Closed: cc.closed, - Closing: cc.closing || cc.singleUse || cc.doNotReuse || cc.goAway != nil, - StreamsActive: len(cc.streams) + cc.pendingResets, - StreamsReserved: cc.streamsReserved, - StreamsPending: cc.pendingRequests, - LastIdle: cc.lastIdle, - MaxConcurrentStreams: maxConcurrent, - } -} - -// clientConnIdleState describes the suitability of a client -// connection to initiate a new RoundTrip request. -type http2clientConnIdleState struct { - canTakeNewRequest bool -} - -func (cc *http2ClientConn) idleState() http2clientConnIdleState { - cc.mu.Lock() - defer cc.mu.Unlock() - return cc.idleStateLocked() -} - -func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) { - if cc.singleUse && cc.nextStreamID > 1 { - return - } - var maxConcurrentOkay bool - if cc.strictMaxConcurrentStreams { - // We'll tell the caller we can take a new request to - // prevent the caller from dialing a new TCP - // connection, but then we'll block later before - // writing it. - maxConcurrentOkay = true - } else { - // We can take a new request if the total of - // - active streams; - // - reservation slots for new streams; and - // - streams for which we have sent a RST_STREAM and a PING, - // but received no subsequent frame - // is less than the concurrency limit. - maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) - } - - st.canTakeNewRequest = maxConcurrentOkay && cc.isUsableLocked() - - // If this connection has never been used for a request and is closed, - // then let it take a request (which will fail). - // If the conn was closed for idleness, we're racing the idle timer; - // don't try to use the conn. (Issue #70515.) - // - // This avoids a situation where an error early in a connection's lifetime - // goes unreported. - if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed && !cc.closedOnIdle { - st.canTakeNewRequest = true - } - - return -} - -func (cc *http2ClientConn) isUsableLocked() bool { - return cc.goAway == nil && - !cc.closed && - !cc.closing && - !cc.doNotReuse && - int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 && - !cc.tooIdleLocked() -} - -// canReserveLocked reports whether a net/http.ClientConn can reserve a slot on this conn. -// -// This follows slightly different rules than clientConnIdleState.canTakeNewRequest. -// We only permit reservations up to the conn's concurrency limit. -// This differs from ClientConn.ReserveNewRequest, which permits reservations -// past the limit when StrictMaxConcurrentStreams is set. -func (cc *http2ClientConn) canReserveLocked() bool { - if cc.currentRequestCountLocked() >= int(cc.maxConcurrentStreams) { - return false - } - if !cc.isUsableLocked() { - return false - } - return true -} - -// currentRequestCountLocked reports the number of concurrency slots currently in use, -// including active streams, reserved slots, and reset streams waiting for acknowledgement. -func (cc *http2ClientConn) currentRequestCountLocked() int { - return len(cc.streams) + cc.streamsReserved + cc.pendingResets -} - -func (cc *http2ClientConn) canTakeNewRequestLocked() bool { - st := cc.idleStateLocked() - return st.canTakeNewRequest -} - -// availableLocked reports the number of concurrency slots available. -func (cc *http2ClientConn) availableLocked() int { - if !cc.canTakeNewRequestLocked() { - return 0 - } - return max(0, int(cc.maxConcurrentStreams)-cc.currentRequestCountLocked()) -} - -// tooIdleLocked reports whether this connection has been been sitting idle -// for too much wall time. -func (cc *http2ClientConn) tooIdleLocked() bool { - // The Round(0) strips the monontonic clock reading so the - // times are compared based on their wall time. We don't want - // to reuse a connection that's been sitting idle during - // VM/laptop suspend if monotonic time was also frozen. - return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && time.Since(cc.lastIdle.Round(0)) > cc.idleTimeout -} - -// onIdleTimeout is called from a time.AfterFunc goroutine. It will -// only be called when we're idle, but because we're coming from a new -// goroutine, there could be a new request coming in at the same time, -// so this simply calls the synchronized closeIfIdle to shut down this -// connection. The timer could just call closeIfIdle, but this is more -// clear. -func (cc *http2ClientConn) onIdleTimeout() { - cc.closeIfIdle() -} - -func (cc *http2ClientConn) closeConn() { - t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn) - defer t.Stop() - cc.tconn.Close() - cc.maybeCallStateHook() -} - -// A tls.Conn.Close can hang for a long time if the peer is unresponsive. -// Try to shut it down more aggressively. -func (cc *http2ClientConn) forceCloseConn() { - tc, ok := cc.tconn.(*tls.Conn) - if !ok { - return - } - if nc := tc.NetConn(); nc != nil { - nc.Close() - } -} - -func (cc *http2ClientConn) closeIfIdle() { - cc.mu.Lock() - if len(cc.streams) > 0 || cc.streamsReserved > 0 { - cc.mu.Unlock() - return - } - cc.closed = true - cc.closedOnIdle = true - nextID := cc.nextStreamID - // TODO: do clients send GOAWAY too? maybe? Just Close: - cc.mu.Unlock() - - if http2VerboseLogs { - cc.vlogf("http2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)", cc, cc.singleUse, nextID-2) - } - cc.closeConn() -} - -func (cc *http2ClientConn) isDoNotReuseAndIdle() bool { - cc.mu.Lock() - defer cc.mu.Unlock() - return cc.doNotReuse && len(cc.streams) == 0 -} - -var http2shutdownEnterWaitStateHook = func() {} - -// Shutdown gracefully closes the client connection, waiting for running streams to complete. -func (cc *http2ClientConn) Shutdown(ctx context.Context) error { - if err := cc.sendGoAway(); err != nil { - return err - } - // Wait for all in-flight streams to complete or connection to close - done := make(chan struct{}) - cancelled := false // guarded by cc.mu - go func() { - cc.mu.Lock() - defer cc.mu.Unlock() - for { - if len(cc.streams) == 0 || cc.closed { - cc.closed = true - close(done) - break - } - if cancelled { - break - } - cc.cond.Wait() - } - }() - http2shutdownEnterWaitStateHook() - select { - case <-done: - cc.closeConn() - return nil - case <-ctx.Done(): - cc.mu.Lock() - // Free the goroutine above - cancelled = true - cc.cond.Broadcast() - cc.mu.Unlock() - return ctx.Err() - } -} - -func (cc *http2ClientConn) sendGoAway() error { - cc.mu.Lock() - closing := cc.closing - cc.closing = true - maxStreamID := cc.nextStreamID - cc.mu.Unlock() - if closing { - // GOAWAY sent already - return nil - } - - cc.wmu.Lock() - defer cc.wmu.Unlock() - // Send a graceful shutdown frame to server - if err := cc.fr.WriteGoAway(maxStreamID, http2ErrCodeNo, nil); err != nil { - return err - } - if err := cc.bw.Flush(); err != nil { - return err - } - // Prevent new requests - return nil -} - -// closes the client connection immediately. In-flight requests are interrupted. -// err is sent to streams. -func (cc *http2ClientConn) closeForError(err error) { - cc.mu.Lock() - cc.closed = true - for _, cs := range cc.streams { - cs.abortStreamLocked(err) - } - cc.cond.Broadcast() - cc.mu.Unlock() - cc.closeConn() -} - -// Close closes the client connection immediately. -// -// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead. -func (cc *http2ClientConn) Close() error { - cc.closeForError(http2errClientConnForceClosed) - return nil -} - -// closes the client connection immediately. In-flight requests are interrupted. -func (cc *http2ClientConn) closeForLostPing() { - err := errors.New("http2: client connection lost") - if f := cc.t.CountError; f != nil { - f("conn_close_lost_ping") - } - cc.closeForError(err) -} - -// errRequestCanceled is a copy of net/http's errRequestCanceled because it's not -// exported. At least they'll be DeepEqual for h1-vs-h2 comparisons tests. -var http2errRequestCanceled = errors.New("net/http: request canceled") - -func (cc *http2ClientConn) responseHeaderTimeout() time.Duration { - if cc.t.t1 != nil { - return cc.t.t1.ResponseHeaderTimeout - } - // No way to do this (yet?) with just an http2.Transport. Probably - // no need. Request.Cancel this is the new way. We only need to support - // this for compatibility with the old http.Transport fields when - // we're doing transparent http2. - return 0 -} - -// actualContentLength returns a sanitized version of -// req.ContentLength, where 0 actually means zero (not unknown) and -1 -// means unknown. -func http2actualContentLength(req *Request) int64 { - if req.Body == nil || req.Body == NoBody { - return 0 - } - if req.ContentLength != 0 { - return req.ContentLength - } - return -1 -} - -func (cc *http2ClientConn) decrStreamReservations() { - cc.mu.Lock() - defer cc.mu.Unlock() - cc.decrStreamReservationsLocked() -} - -func (cc *http2ClientConn) decrStreamReservationsLocked() { - if cc.streamsReserved > 0 { - cc.streamsReserved-- - } -} - -func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { - return cc.roundTrip(req, nil) -} - -func (cc *http2ClientConn) roundTrip(req *Request, streamf func(*http2clientStream)) (*Response, error) { - ctx := req.Context() - cs := &http2clientStream{ - cc: cc, - ctx: ctx, - reqCancel: req.Cancel, - isHead: req.Method == "HEAD", - reqBody: req.Body, - reqBodyContentLength: http2actualContentLength(req), - trace: httptrace.ContextClientTrace(ctx), - peerClosed: make(chan struct{}), - abort: make(chan struct{}), - respHeaderRecv: make(chan struct{}), - donec: make(chan struct{}), - } - - cs.requestedGzip = httpcommon.IsRequestGzip(req.Method, req.Header, cc.t.disableCompression()) - - go cs.doRequest(req, streamf) - - waitDone := func() error { - select { - case <-cs.donec: - return nil - case <-ctx.Done(): - return ctx.Err() - case <-cs.reqCancel: - return http2errRequestCanceled - } - } - - handleResponseHeaders := func() (*Response, error) { - res := cs.res - if res.StatusCode > 299 { - // On error or status code 3xx, 4xx, 5xx, etc abort any - // ongoing write, assuming that the server doesn't care - // about our request body. If the server replied with 1xx or - // 2xx, however, then assume the server DOES potentially - // want our body (e.g. full-duplex streaming: - // golang.org/issue/13444). If it turns out the server - // doesn't, they'll RST_STREAM us soon enough. This is a - // heuristic to avoid adding knobs to Transport. Hopefully - // we can keep it. - cs.abortRequestBodyWrite() - } - res.Request = req - res.TLS = cc.tlsState - if res.Body == http2noBody && http2actualContentLength(req) == 0 { - // If there isn't a request or response body still being - // written, then wait for the stream to be closed before - // RoundTrip returns. - if err := waitDone(); err != nil { - return nil, err - } - } - return res, nil - } - - cancelRequest := func(cs *http2clientStream, err error) error { - cs.cc.mu.Lock() - bodyClosed := cs.reqBodyClosed - cs.cc.mu.Unlock() - // Wait for the request body to be closed. - // - // If nothing closed the body before now, abortStreamLocked - // will have started a goroutine to close it. - // - // Closing the body before returning avoids a race condition - // with net/http checking its readTrackingBody to see if the - // body was read from or closed. See golang/go#60041. - // - // The body is closed in a separate goroutine without the - // connection mutex held, but dropping the mutex before waiting - // will keep us from holding it indefinitely if the body - // close is slow for some reason. - if bodyClosed != nil { - <-bodyClosed - } - return err - } - - for { - select { - case <-cs.respHeaderRecv: - return handleResponseHeaders() - case <-cs.abort: - select { - case <-cs.respHeaderRecv: - // If both cs.respHeaderRecv and cs.abort are signaling, - // pick respHeaderRecv. The server probably wrote the - // response and immediately reset the stream. - // golang.org/issue/49645 - return handleResponseHeaders() - default: - waitDone() - return nil, cs.abortErr - } - case <-ctx.Done(): - err := ctx.Err() - cs.abortStream(err) - return nil, cancelRequest(cs, err) - case <-cs.reqCancel: - cs.abortStream(http2errRequestCanceled) - return nil, cancelRequest(cs, http2errRequestCanceled) - } - } -} - -// doRequest runs for the duration of the request lifetime. -// -// It sends the request and performs post-request cleanup (closing Request.Body, etc.). -func (cs *http2clientStream) doRequest(req *Request, streamf func(*http2clientStream)) { - err := cs.writeRequest(req, streamf) - cs.cleanupWriteRequest(err) -} - -var http2errExtendedConnectNotSupported = errors.New("net/http: extended connect not supported by peer") - -// writeRequest sends a request. -// -// It returns nil after the request is written, the response read, -// and the request stream is half-closed by the peer. -// -// It returns non-nil if the request ends otherwise. -// If the returned error is StreamError, the error Code may be used in resetting the stream. -func (cs *http2clientStream) writeRequest(req *Request, streamf func(*http2clientStream)) (err error) { - cc := cs.cc - ctx := cs.ctx - - // wait for setting frames to be received, a server can change this value later, - // but we just wait for the first settings frame - var isExtendedConnect bool - if req.Method == "CONNECT" && req.Header.Get(":protocol") != "" { - isExtendedConnect = true - } - - // Acquire the new-request lock by writing to reqHeaderMu. - // This lock guards the critical section covering allocating a new stream ID - // (requires mu) and creating the stream (requires wmu). - if cc.reqHeaderMu == nil { - panic("RoundTrip on uninitialized ClientConn") // for tests - } - if isExtendedConnect { - select { - case <-cs.reqCancel: - return http2errRequestCanceled - case <-ctx.Done(): - return ctx.Err() - case <-cc.seenSettingsChan: - if !cc.extendedConnectAllowed { - return http2errExtendedConnectNotSupported - } - } - } - select { - case cc.reqHeaderMu <- struct{}{}: - case <-cs.reqCancel: - return http2errRequestCanceled - case <-ctx.Done(): - return ctx.Err() - } - - cc.mu.Lock() - if cc.idleTimer != nil { - cc.idleTimer.Stop() - } - cc.decrStreamReservationsLocked() - if err := cc.awaitOpenSlotForStreamLocked(cs); err != nil { - cc.mu.Unlock() - <-cc.reqHeaderMu - return err - } - cc.addStreamLocked(cs) // assigns stream ID - if http2isConnectionCloseRequest(req) { - cc.doNotReuse = true - } - cc.mu.Unlock() - - if streamf != nil { - streamf(cs) - } - - continueTimeout := cc.t.expectContinueTimeout() - if continueTimeout != 0 { - if !httpguts.HeaderValuesContainsToken(req.Header["Expect"], "100-continue") { - continueTimeout = 0 - } else { - cs.on100 = make(chan struct{}, 1) - } - } - - // Past this point (where we send request headers), it is possible for - // RoundTrip to return successfully. Since the RoundTrip contract permits - // the caller to "mutate or reuse" the Request after closing the Response's Body, - // we must take care when referencing the Request from here on. - err = cs.encodeAndWriteHeaders(req) - <-cc.reqHeaderMu - if err != nil { - return err - } - - hasBody := cs.reqBodyContentLength != 0 - if !hasBody { - cs.sentEndStream = true - } else { - if continueTimeout != 0 { - http2traceWait100Continue(cs.trace) - timer := time.NewTimer(continueTimeout) - select { - case <-timer.C: - err = nil - case <-cs.on100: - err = nil - case <-cs.abort: - err = cs.abortErr - case <-ctx.Done(): - err = ctx.Err() - case <-cs.reqCancel: - err = http2errRequestCanceled - } - timer.Stop() - if err != nil { - http2traceWroteRequest(cs.trace, err) - return err - } - } - - if err = cs.writeRequestBody(req); err != nil { - if err != http2errStopReqBodyWrite { - http2traceWroteRequest(cs.trace, err) - return err - } - } else { - cs.sentEndStream = true - } - } - - http2traceWroteRequest(cs.trace, err) - - var respHeaderTimer <-chan time.Time - var respHeaderRecv chan struct{} - if d := cc.responseHeaderTimeout(); d != 0 { - timer := time.NewTimer(d) - defer timer.Stop() - respHeaderTimer = timer.C - respHeaderRecv = cs.respHeaderRecv - } - // Wait until the peer half-closes its end of the stream, - // or until the request is aborted (via context, error, or otherwise), - // whichever comes first. - for { - select { - case <-cs.peerClosed: - return nil - case <-respHeaderTimer: - return http2errTimeout - case <-respHeaderRecv: - respHeaderRecv = nil - respHeaderTimer = nil // keep waiting for END_STREAM - case <-cs.abort: - return cs.abortErr - case <-ctx.Done(): - return ctx.Err() - case <-cs.reqCancel: - return http2errRequestCanceled - } - } -} - -func (cs *http2clientStream) encodeAndWriteHeaders(req *Request) error { - cc := cs.cc - ctx := cs.ctx - - cc.wmu.Lock() - defer cc.wmu.Unlock() - - // If the request was canceled while waiting for cc.mu, just quit. - select { - case <-cs.abort: - return cs.abortErr - case <-ctx.Done(): - return ctx.Err() - case <-cs.reqCancel: - return http2errRequestCanceled - default: - } - - // Encode headers. - // - // we send: HEADERS{1}, CONTINUATION{0,} + DATA{0,} (DATA is - // sent by writeRequestBody below, along with any Trailers, - // again in form HEADERS{1}, CONTINUATION{0,}) - cc.hbuf.Reset() - res, err := http2encodeRequestHeaders(req, cs.requestedGzip, cc.peerMaxHeaderListSize, func(name, value string) { - cc.writeHeader(name, value) - }) - if err != nil { - return fmt.Errorf("http2: %w", err) - } - hdrs := cc.hbuf.Bytes() - - // Write the request. - endStream := !res.HasBody && !res.HasTrailers - cs.sentHeaders = true - err = cc.writeHeaders(cs.ID, endStream, int(cc.maxFrameSize), hdrs) - http2traceWroteHeaders(cs.trace) - return err -} - -func http2encodeRequestHeaders(req *Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) { - return httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{ - Request: httpcommon.Request{ - Header: req.Header, - Trailer: req.Trailer, - URL: req.URL, - Host: req.Host, - Method: req.Method, - ActualContentLength: http2actualContentLength(req), - }, - AddGzipHeader: addGzipHeader, - PeerMaxHeaderListSize: peerMaxHeaderListSize, - DefaultUserAgent: http2defaultUserAgent, - }, headerf) -} - -// cleanupWriteRequest performs post-request tasks. -// -// If err (the result of writeRequest) is non-nil and the stream is not closed, -// cleanupWriteRequest will send a reset to the peer. -func (cs *http2clientStream) cleanupWriteRequest(err error) { - cc := cs.cc - - if cs.ID == 0 { - // We were canceled before creating the stream, so return our reservation. - cc.decrStreamReservations() - } - - // TODO: write h12Compare test showing whether - // Request.Body is closed by the Transport, - // and in multiple cases: server replies <=299 and >299 - // while still writing request body - cc.mu.Lock() - mustCloseBody := false - if cs.reqBody != nil && cs.reqBodyClosed == nil { - mustCloseBody = true - cs.reqBodyClosed = make(chan struct{}) - } - bodyClosed := cs.reqBodyClosed - closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil - // Have we read any frames from the connection since sending this request? - readSinceStream := cc.readBeforeStreamID > cs.ID - cc.mu.Unlock() - if mustCloseBody { - cs.reqBody.Close() - close(bodyClosed) - } - if bodyClosed != nil { - <-bodyClosed - } - - if err != nil && cs.sentEndStream { - // If the connection is closed immediately after the response is read, - // we may be aborted before finishing up here. If the stream was closed - // cleanly on both sides, there is no error. - select { - case <-cs.peerClosed: - err = nil - default: - } - } - if err != nil { - cs.abortStream(err) // possibly redundant, but harmless - if cs.sentHeaders { - if se, ok := err.(http2StreamError); ok { - if se.Cause != http2errFromPeer { - cc.writeStreamReset(cs.ID, se.Code, false, err) - } - } else { - // We're cancelling an in-flight request. - // - // This could be due to the server becoming unresponsive. - // To avoid sending too many requests on a dead connection, - // if we haven't read any frames from the connection since - // sending this request, we let it continue to consume - // a concurrency slot until we can confirm the server is - // still responding. - // We do this by sending a PING frame along with the RST_STREAM - // (unless a ping is already in flight). - // - // For simplicity, we don't bother tracking the PING payload: - // We reset cc.pendingResets any time we receive a PING ACK. - // - // We skip this if the conn is going to be closed on idle, - // because it's short lived and will probably be closed before - // we get the ping response. - ping := false - if !closeOnIdle && !readSinceStream { - cc.mu.Lock() - // rstStreamPingsBlocked works around a gRPC behavior: - // see comment on the field for details. - if !cc.rstStreamPingsBlocked { - if cc.pendingResets == 0 { - ping = true - } - cc.pendingResets++ - } - cc.mu.Unlock() - } - cc.writeStreamReset(cs.ID, http2ErrCodeCancel, ping, err) - } - } - cs.bufPipe.CloseWithError(err) // no-op if already closed - } else { - if cs.sentHeaders && !cs.sentEndStream { - cc.writeStreamReset(cs.ID, http2ErrCodeNo, false, nil) - } - cs.bufPipe.CloseWithError(http2errRequestCanceled) - } - if cs.ID != 0 { - cc.forgetStreamID(cs.ID) - } - - cc.wmu.Lock() - werr := cc.werr - cc.wmu.Unlock() - if werr != nil { - cc.Close() - } - - close(cs.donec) - cc.maybeCallStateHook() -} - -// awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams. -// Must hold cc.mu. -func (cc *http2ClientConn) awaitOpenSlotForStreamLocked(cs *http2clientStream) error { - for { - if cc.closed && cc.nextStreamID == 1 && cc.streamsReserved == 0 { - // This is the very first request sent to this connection. - // Return a fatal error which aborts the retry loop. - return http2errClientConnNotEstablished - } - cc.lastActive = time.Now() - if cc.closed || !cc.canTakeNewRequestLocked() { - return http2errClientConnUnusable - } - cc.lastIdle = time.Time{} - if cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) { - return nil - } - cc.pendingRequests++ - cc.cond.Wait() - cc.pendingRequests-- - select { - case <-cs.abort: - return cs.abortErr - default: - } - } -} - -// requires cc.wmu be held -func (cc *http2ClientConn) writeHeaders(streamID uint32, endStream bool, maxFrameSize int, hdrs []byte) error { - first := true // first frame written (HEADERS is first, then CONTINUATION) - for len(hdrs) > 0 && cc.werr == nil { - chunk := hdrs - if len(chunk) > maxFrameSize { - chunk = chunk[:maxFrameSize] - } - hdrs = hdrs[len(chunk):] - endHeaders := len(hdrs) == 0 - if first { - cc.fr.WriteHeaders(http2HeadersFrameParam{ - StreamID: streamID, - BlockFragment: chunk, - EndStream: endStream, - EndHeaders: endHeaders, - }) - first = false - } else { - cc.fr.WriteContinuation(streamID, endHeaders, chunk) - } - } - cc.bw.Flush() - return cc.werr -} - -// internal error values; they don't escape to callers -var ( - // abort request body write; don't send cancel - http2errStopReqBodyWrite = errors.New("http2: aborting request body write") - - // abort request body write, but send stream reset of cancel. - http2errStopReqBodyWriteAndCancel = errors.New("http2: canceling request") - - http2errReqBodyTooLong = errors.New("http2: request body larger than specified content length") -) - -// frameScratchBufferLen returns the length of a buffer to use for -// outgoing request bodies to read/write to/from. -// -// It returns max(1, min(peer's advertised max frame size, -// Request.ContentLength+1, 512KB)). -func (cs *http2clientStream) frameScratchBufferLen(maxFrameSize int) int { - const max = 512 << 10 - n := int64(maxFrameSize) - if n > max { - n = max - } - if cl := cs.reqBodyContentLength; cl != -1 && cl+1 < n { - // Add an extra byte past the declared content-length to - // give the caller's Request.Body io.Reader a chance to - // give us more bytes than they declared, so we can catch it - // early. - n = cl + 1 - } - if n < 1 { - return 1 - } - return int(n) // doesn't truncate; max is 512K -} - -// Seven bufPools manage different frame sizes. This helps to avoid scenarios where long-running -// streaming requests using small frame sizes occupy large buffers initially allocated for prior -// requests needing big buffers. The size ranges are as follows: -// {0 KB, 16 KB], {16 KB, 32 KB], {32 KB, 64 KB], {64 KB, 128 KB], {128 KB, 256 KB], -// {256 KB, 512 KB], {512 KB, infinity} -// In practice, the maximum scratch buffer size should not exceed 512 KB due to -// frameScratchBufferLen(maxFrameSize), thus the "infinity pool" should never be used. -// It exists mainly as a safety measure, for potential future increases in max buffer size. -var http2bufPools [7]sync.Pool // of *[]byte - -func http2bufPoolIndex(size int) int { - if size <= 16384 { - return 0 - } - size -= 1 - bits := bits.Len(uint(size)) - index := bits - 14 - if index >= len(http2bufPools) { - return len(http2bufPools) - 1 - } - return index -} - -func (cs *http2clientStream) writeRequestBody(req *Request) (err error) { - cc := cs.cc - body := cs.reqBody - sentEnd := false // whether we sent the final DATA frame w/ END_STREAM - - hasTrailers := req.Trailer != nil - remainLen := cs.reqBodyContentLength - hasContentLen := remainLen != -1 - - cc.mu.Lock() - maxFrameSize := int(cc.maxFrameSize) - cc.mu.Unlock() - - // Scratch buffer for reading into & writing from. - scratchLen := cs.frameScratchBufferLen(maxFrameSize) - var buf []byte - index := http2bufPoolIndex(scratchLen) - if bp, ok := http2bufPools[index].Get().(*[]byte); ok && len(*bp) >= scratchLen { - defer http2bufPools[index].Put(bp) - buf = *bp - } else { - buf = make([]byte, scratchLen) - defer http2bufPools[index].Put(&buf) - } - - var sawEOF bool - for !sawEOF { - n, err := body.Read(buf) - if hasContentLen { - remainLen -= int64(n) - if remainLen == 0 && err == nil { - // The request body's Content-Length was predeclared and - // we just finished reading it all, but the underlying io.Reader - // returned the final chunk with a nil error (which is one of - // the two valid things a Reader can do at EOF). Because we'd prefer - // to send the END_STREAM bit early, double-check that we're actually - // at EOF. Subsequent reads should return (0, EOF) at this point. - // If either value is different, we return an error in one of two ways below. - var scratch [1]byte - var n1 int - n1, err = body.Read(scratch[:]) - remainLen -= int64(n1) - } - if remainLen < 0 { - err = http2errReqBodyTooLong - return err - } - } - if err != nil { - cc.mu.Lock() - bodyClosed := cs.reqBodyClosed != nil - cc.mu.Unlock() - switch { - case bodyClosed: - return http2errStopReqBodyWrite - case err == io.EOF: - sawEOF = true - err = nil - default: - return err - } - } - - remain := buf[:n] - for len(remain) > 0 && err == nil { - var allowed int32 - allowed, err = cs.awaitFlowControl(len(remain)) - if err != nil { - return err - } - cc.wmu.Lock() - data := remain[:allowed] - remain = remain[allowed:] - sentEnd = sawEOF && len(remain) == 0 && !hasTrailers - err = cc.fr.WriteData(cs.ID, sentEnd, data) - if err == nil { - // TODO(bradfitz): this flush is for latency, not bandwidth. - // Most requests won't need this. Make this opt-in or - // opt-out? Use some heuristic on the body type? Nagel-like - // timers? Based on 'n'? Only last chunk of this for loop, - // unless flow control tokens are low? For now, always. - // If we change this, see comment below. - err = cc.bw.Flush() - } - cc.wmu.Unlock() - } - if err != nil { - return err - } - } - - if sentEnd { - // Already sent END_STREAM (which implies we have no - // trailers) and flushed, because currently all - // WriteData frames above get a flush. So we're done. - return nil - } - - // Since the RoundTrip contract permits the caller to "mutate or reuse" - // a request after the Response's Body is closed, verify that this hasn't - // happened before accessing the trailers. - cc.mu.Lock() - trailer := req.Trailer - err = cs.abortErr - cc.mu.Unlock() - if err != nil { - return err - } - - cc.wmu.Lock() - defer cc.wmu.Unlock() - var trls []byte - if len(trailer) > 0 { - trls, err = cc.encodeTrailers(trailer) - if err != nil { - return err - } - } - - // Two ways to send END_STREAM: either with trailers, or - // with an empty DATA frame. - if len(trls) > 0 { - err = cc.writeHeaders(cs.ID, true, maxFrameSize, trls) - } else { - err = cc.fr.WriteData(cs.ID, true, nil) - } - if ferr := cc.bw.Flush(); ferr != nil && err == nil { - err = ferr - } - return err -} - -// awaitFlowControl waits for [1, min(maxBytes, cc.cs.maxFrameSize)] flow -// control tokens from the server. -// It returns either the non-zero number of tokens taken or an error -// if the stream is dead. -func (cs *http2clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) { - cc := cs.cc - ctx := cs.ctx - cc.mu.Lock() - defer cc.mu.Unlock() - for { - if cc.closed { - return 0, http2errClientConnClosed - } - if cs.reqBodyClosed != nil { - return 0, http2errStopReqBodyWrite - } - select { - case <-cs.abort: - return 0, cs.abortErr - case <-ctx.Done(): - return 0, ctx.Err() - case <-cs.reqCancel: - return 0, http2errRequestCanceled - default: - } - if a := cs.flow.available(); a > 0 { - take := a - if int(take) > maxBytes { - - take = int32(maxBytes) // can't truncate int; take is int32 - } - if take > int32(cc.maxFrameSize) { - take = int32(cc.maxFrameSize) - } - cs.flow.take(take) - return take, nil - } - cc.cond.Wait() - } -} - -// requires cc.wmu be held. -func (cc *http2ClientConn) encodeTrailers(trailer Header) ([]byte, error) { - cc.hbuf.Reset() - - hlSize := uint64(0) - for k, vv := range trailer { - for _, v := range vv { - hf := hpack.HeaderField{Name: k, Value: v} - hlSize += uint64(hf.Size()) - } - } - if hlSize > cc.peerMaxHeaderListSize { - return nil, http2errRequestHeaderListSize - } - - for k, vv := range trailer { - lowKey, ascii := httpcommon.LowerHeader(k) - if !ascii { - // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header - // field names have to be ASCII characters (just as in HTTP/1.x). - continue - } - // Transfer-Encoding, etc.. have already been filtered at the - // start of RoundTrip - for _, v := range vv { - cc.writeHeader(lowKey, v) - } - } - return cc.hbuf.Bytes(), nil -} - -func (cc *http2ClientConn) writeHeader(name, value string) { - if http2VerboseLogs { - log.Printf("http2: Transport encoding header %q = %q", name, value) - } - cc.henc.WriteField(hpack.HeaderField{Name: name, Value: value}) -} - -type http2resAndError struct { - _ http2incomparable - res *Response - err error -} - -// requires cc.mu be held. -func (cc *http2ClientConn) addStreamLocked(cs *http2clientStream) { - cs.flow.add(int32(cc.initialWindowSize)) - cs.flow.setConnFlow(&cc.flow) - cs.inflow.init(cc.initialStreamRecvWindowSize) - cs.ID = cc.nextStreamID - cc.nextStreamID += 2 - cc.streams[cs.ID] = cs - if cs.ID == 0 { - panic("assigned stream ID 0") - } -} - -func (cc *http2ClientConn) forgetStreamID(id uint32) { - cc.mu.Lock() - slen := len(cc.streams) - delete(cc.streams, id) - if len(cc.streams) != slen-1 { - panic("forgetting unknown stream id") - } - cc.lastActive = time.Now() - if len(cc.streams) == 0 && cc.idleTimer != nil { - cc.idleTimer.Reset(cc.idleTimeout) - cc.lastIdle = time.Now() - } - // Wake up writeRequestBody via clientStream.awaitFlowControl and - // wake up RoundTrip if there is a pending request. - cc.cond.Broadcast() - - closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil - if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 { - if http2VerboseLogs { - cc.vlogf("http2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)", cc, cc.singleUse, cc.nextStreamID-2) - } - cc.closed = true - defer cc.closeConn() - } - - cc.mu.Unlock() -} - -// clientConnReadLoop is the state owned by the clientConn's frame-reading readLoop. -type http2clientConnReadLoop struct { - _ http2incomparable - cc *http2ClientConn -} - -// readLoop runs in its own goroutine and reads and dispatches frames. -func (cc *http2ClientConn) readLoop() { - rl := &http2clientConnReadLoop{cc: cc} - defer rl.cleanup() - cc.readerErr = rl.run() - if ce, ok := cc.readerErr.(http2ConnectionError); ok { - cc.wmu.Lock() - cc.fr.WriteGoAway(0, http2ErrCode(ce), nil) - cc.wmu.Unlock() - } -} - -// GoAwayError is returned by the Transport when the server closes the -// TCP connection after sending a GOAWAY frame. -type http2GoAwayError struct { - LastStreamID uint32 - ErrCode http2ErrCode - DebugData string -} - -func (e http2GoAwayError) Error() string { - return fmt.Sprintf("http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%q", - e.LastStreamID, e.ErrCode, e.DebugData) -} - -func http2isEOFOrNetReadError(err error) bool { - if err == io.EOF { - return true - } - ne, ok := err.(*net.OpError) - return ok && ne.Op == "read" -} - -func (rl *http2clientConnReadLoop) cleanup() { - cc := rl.cc - defer cc.closeConn() - defer close(cc.readerDone) - - if cc.idleTimer != nil { - cc.idleTimer.Stop() - } - - // Close any response bodies if the server closes prematurely. - // TODO: also do this if we've written the headers but not - // gotten a response yet. - err := cc.readerErr - cc.mu.Lock() - if cc.goAway != nil && http2isEOFOrNetReadError(err) { - err = http2GoAwayError{ - LastStreamID: cc.goAway.LastStreamID, - ErrCode: cc.goAway.ErrCode, - DebugData: cc.goAwayDebug, - } - } else if err == io.EOF { - err = io.ErrUnexpectedEOF - } - cc.closed = true - - // If the connection has never been used, and has been open for only a short time, - // leave it in the connection pool for a little while. - // - // This avoids a situation where new connections are constantly created, - // added to the pool, fail, and are removed from the pool, without any error - // being surfaced to the user. - unusedWaitTime := 5 * time.Second - if cc.idleTimeout > 0 && unusedWaitTime > cc.idleTimeout { - unusedWaitTime = cc.idleTimeout - } - idleTime := time.Now().Sub(cc.lastActive) - if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime && !cc.closedOnIdle { - cc.idleTimer = time.AfterFunc(unusedWaitTime-idleTime, func() { - cc.t.connPool().MarkDead(cc) - }) - } else { - cc.mu.Unlock() // avoid any deadlocks in MarkDead - cc.t.connPool().MarkDead(cc) - cc.mu.Lock() - } - - for _, cs := range cc.streams { - select { - case <-cs.peerClosed: - // The server closed the stream before closing the conn, - // so no need to interrupt it. - default: - cs.abortStreamLocked(err) - } - } - cc.cond.Broadcast() - cc.mu.Unlock() - - if !cc.seenSettings { - // If we have a pending request that wants extended CONNECT, - // let it continue and fail with the connection error. - cc.extendedConnectAllowed = true - close(cc.seenSettingsChan) - } -} - -// countReadFrameError calls Transport.CountError with a string -// representing err. -func (cc *http2ClientConn) countReadFrameError(err error) { - f := cc.t.CountError - if f == nil || err == nil { - return - } - if ce, ok := err.(http2ConnectionError); ok { - errCode := http2ErrCode(ce) - f(fmt.Sprintf("read_frame_conn_error_%s", errCode.stringToken())) - return - } - if errors.Is(err, io.EOF) { - f("read_frame_eof") - return - } - if errors.Is(err, io.ErrUnexpectedEOF) { - f("read_frame_unexpected_eof") - return - } - if errors.Is(err, http2ErrFrameTooLarge) { - f("read_frame_too_large") - return - } - f("read_frame_other") -} - -func (rl *http2clientConnReadLoop) run() error { - cc := rl.cc - gotSettings := false - readIdleTimeout := cc.readIdleTimeout - var t *time.Timer - if readIdleTimeout != 0 { - t = time.AfterFunc(readIdleTimeout, cc.healthCheck) - } - for { - f, err := cc.fr.ReadFrame() - if t != nil { - t.Reset(readIdleTimeout) - } - if err != nil { - cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err) - } - if se, ok := err.(http2StreamError); ok { - if cs := rl.streamByID(se.StreamID, http2notHeaderOrDataFrame); cs != nil { - if se.Cause == nil { - se.Cause = cc.fr.errDetail - } - rl.endStreamError(cs, se) - } - continue - } else if err != nil { - cc.countReadFrameError(err) - return err - } - if http2VerboseLogs { - cc.vlogf("http2: Transport received %s", http2summarizeFrame(f)) - } - if !gotSettings { - if _, ok := f.(*http2SettingsFrame); !ok { - cc.logf("protocol error: received %T before a SETTINGS frame", f) - return http2ConnectionError(http2ErrCodeProtocol) - } - gotSettings = true - } - - switch f := f.(type) { - case *http2MetaHeadersFrame: - err = rl.processHeaders(f) - case *http2DataFrame: - err = rl.processData(f) - case *http2GoAwayFrame: - err = rl.processGoAway(f) - case *http2RSTStreamFrame: - err = rl.processResetStream(f) - case *http2SettingsFrame: - err = rl.processSettings(f) - case *http2PushPromiseFrame: - err = rl.processPushPromise(f) - case *http2WindowUpdateFrame: - err = rl.processWindowUpdate(f) - case *http2PingFrame: - err = rl.processPing(f) - default: - cc.logf("Transport: unhandled response frame type %T", f) - } - if err != nil { - if http2VerboseLogs { - cc.vlogf("http2: Transport conn %p received error from processing frame %v: %v", cc, http2summarizeFrame(f), err) - } - return err - } - } -} - -func (rl *http2clientConnReadLoop) processHeaders(f *http2MetaHeadersFrame) error { - cs := rl.streamByID(f.StreamID, http2headerOrDataFrame) - if cs == nil { - // We'd get here if we canceled a request while the - // server had its response still in flight. So if this - // was just something we canceled, ignore it. - return nil - } - if cs.readClosed { - rl.endStreamError(cs, http2StreamError{ - StreamID: f.StreamID, - Code: http2ErrCodeProtocol, - Cause: errors.New("protocol error: headers after END_STREAM"), - }) - return nil - } - if !cs.firstByte { - if cs.trace != nil { - // TODO(bradfitz): move first response byte earlier, - // when we first read the 9 byte header, not waiting - // until all the HEADERS+CONTINUATION frames have been - // merged. This works for now. - http2traceFirstResponseByte(cs.trace) - } - cs.firstByte = true - } - if !cs.pastHeaders { - cs.pastHeaders = true - } else { - return rl.processTrailers(cs, f) - } - - res, err := rl.handleResponse(cs, f) - if err != nil { - if _, ok := err.(http2ConnectionError); ok { - return err - } - // Any other error type is a stream error. - rl.endStreamError(cs, http2StreamError{ - StreamID: f.StreamID, - Code: http2ErrCodeProtocol, - Cause: err, - }) - return nil // return nil from process* funcs to keep conn alive - } - if res == nil { - // (nil, nil) special case. See handleResponse docs. - return nil - } - cs.resTrailer = &res.Trailer - cs.res = res - close(cs.respHeaderRecv) - if f.StreamEnded() { - rl.endStream(cs) - } - return nil -} - -// may return error types nil, or ConnectionError. Any other error value -// is a StreamError of type ErrCodeProtocol. The returned error in that case -// is the detail. -// -// As a special case, handleResponse may return (nil, nil) to skip the -// frame (currently only used for 1xx responses). -func (rl *http2clientConnReadLoop) handleResponse(cs *http2clientStream, f *http2MetaHeadersFrame) (*Response, error) { - if f.Truncated { - return nil, http2errResponseHeaderListSize - } - - status := f.PseudoValue("status") - if status == "" { - return nil, errors.New("malformed response from server: missing status pseudo header") - } - statusCode, err := strconv.Atoi(status) - if err != nil { - return nil, errors.New("malformed response from server: malformed non-numeric status pseudo header") - } - - regularFields := f.RegularFields() - strs := make([]string, len(regularFields)) - header := make(Header, len(regularFields)) - res := &Response{ - Proto: "HTTP/2.0", - ProtoMajor: 2, - Header: header, - StatusCode: statusCode, - Status: status + " " + StatusText(statusCode), - } - for _, hf := range regularFields { - key := httpcommon.CanonicalHeader(hf.Name) - if key == "Trailer" { - t := res.Trailer - if t == nil { - t = make(Header) - res.Trailer = t - } - http2foreachHeaderElement(hf.Value, func(v string) { - t[httpcommon.CanonicalHeader(v)] = nil - }) - } else { - vv := header[key] - if vv == nil && len(strs) > 0 { - // More than likely this will be a single-element key. - // Most headers aren't multi-valued. - // Set the capacity on strs[0] to 1, so any future append - // won't extend the slice into the other strings. - vv, strs = strs[:1:1], strs[1:] - vv[0] = hf.Value - header[key] = vv - } else { - header[key] = append(vv, hf.Value) - } - } - } - - if statusCode >= 100 && statusCode <= 199 { - if f.StreamEnded() { - return nil, errors.New("1xx informational response with END_STREAM flag") - } - if fn := cs.get1xxTraceFunc(); fn != nil { - // If the 1xx response is being delivered to the user, - // then they're responsible for limiting the number - // of responses. - if err := fn(statusCode, textproto.MIMEHeader(header)); err != nil { - return nil, err - } - } else { - // If the user didn't examine the 1xx response, then we - // limit the size of all 1xx headers. - // - // This differs a bit from the HTTP/1 implementation, which - // limits the size of all 1xx headers plus the final response. - // Use the larger limit of MaxHeaderListSize and - // net/http.Transport.MaxResponseHeaderBytes. - limit := int64(cs.cc.t.maxHeaderListSize()) - if t1 := cs.cc.t.t1; t1 != nil && t1.MaxResponseHeaderBytes > limit { - limit = t1.MaxResponseHeaderBytes - } - for _, h := range f.Fields { - cs.totalHeaderSize += int64(h.Size()) - } - if cs.totalHeaderSize > limit { - if http2VerboseLogs { - log.Printf("http2: 1xx informational responses too large") - } - return nil, errors.New("header list too large") - } - } - if statusCode == 100 { - http2traceGot100Continue(cs.trace) - select { - case cs.on100 <- struct{}{}: - default: - } - } - cs.pastHeaders = false // do it all again - return nil, nil - } - - res.ContentLength = -1 - if clens := res.Header["Content-Length"]; len(clens) == 1 { - if cl, err := strconv.ParseUint(clens[0], 10, 63); err == nil { - res.ContentLength = int64(cl) - } else { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. - } - } else if len(clens) > 1 { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. - } else if f.StreamEnded() && !cs.isHead { - res.ContentLength = 0 - } - - if cs.isHead { - res.Body = http2noBody - return res, nil - } - - if f.StreamEnded() { - if res.ContentLength > 0 { - res.Body = http2missingBody{} - } else { - res.Body = http2noBody - } - return res, nil - } - - cs.bufPipe.setBuffer(&http2dataBuffer{expected: res.ContentLength}) - cs.bytesRemain = res.ContentLength - res.Body = http2transportResponseBody{cs} - - if cs.requestedGzip && http2asciiEqualFold(res.Header.Get("Content-Encoding"), "gzip") { - res.Header.Del("Content-Encoding") - res.Header.Del("Content-Length") - res.ContentLength = -1 - res.Body = &http2gzipReader{body: res.Body} - res.Uncompressed = true - } - return res, nil -} - -func (rl *http2clientConnReadLoop) processTrailers(cs *http2clientStream, f *http2MetaHeadersFrame) error { - if cs.pastTrailers { - // Too many HEADERS frames for this stream. - return http2ConnectionError(http2ErrCodeProtocol) - } - cs.pastTrailers = true - if !f.StreamEnded() { - // We expect that any headers for trailers also - // has END_STREAM. - return http2ConnectionError(http2ErrCodeProtocol) - } - if len(f.PseudoFields()) > 0 { - // No pseudo header fields are defined for trailers. - // TODO: ConnectionError might be overly harsh? Check. - return http2ConnectionError(http2ErrCodeProtocol) - } - - trailer := make(Header) - for _, hf := range f.RegularFields() { - key := httpcommon.CanonicalHeader(hf.Name) - trailer[key] = append(trailer[key], hf.Value) - } - cs.trailer = trailer - - rl.endStream(cs) - return nil -} - -// transportResponseBody is the concrete type of Transport.RoundTrip's -// Response.Body. It is an io.ReadCloser. -type http2transportResponseBody struct { - cs *http2clientStream -} - -func (b http2transportResponseBody) Read(p []byte) (n int, err error) { - cs := b.cs - cc := cs.cc - - if cs.readErr != nil { - return 0, cs.readErr - } - n, err = b.cs.bufPipe.Read(p) - if cs.bytesRemain != -1 { - if int64(n) > cs.bytesRemain { - n = int(cs.bytesRemain) - if err == nil { - err = errors.New("net/http: server replied with more than declared Content-Length; truncated") - cs.abortStream(err) - } - cs.readErr = err - return int(cs.bytesRemain), err - } - cs.bytesRemain -= int64(n) - if err == io.EOF && cs.bytesRemain > 0 { - err = io.ErrUnexpectedEOF - cs.readErr = err - return n, err - } - } - if n == 0 { - // No flow control tokens to send back. - return - } - - cc.mu.Lock() - connAdd := cc.inflow.add(n) - var streamAdd int32 - if err == nil { // No need to refresh if the stream is over or failed. - streamAdd = cs.inflow.add(n) - } - cc.mu.Unlock() - - if connAdd != 0 || streamAdd != 0 { - cc.wmu.Lock() - defer cc.wmu.Unlock() - if connAdd != 0 { - cc.fr.WriteWindowUpdate(0, http2mustUint31(connAdd)) - } - if streamAdd != 0 { - cc.fr.WriteWindowUpdate(cs.ID, http2mustUint31(streamAdd)) - } - cc.bw.Flush() - } - return -} - -var http2errClosedResponseBody = errors.New("http2: response body closed") - -func (b http2transportResponseBody) Close() error { - cs := b.cs - cc := cs.cc - - cs.bufPipe.BreakWithError(http2errClosedResponseBody) - cs.abortStream(http2errClosedResponseBody) - - unread := cs.bufPipe.Len() - if unread > 0 { - cc.mu.Lock() - // Return connection-level flow control. - connAdd := cc.inflow.add(unread) - cc.mu.Unlock() - - // TODO(dneil): Acquiring this mutex can block indefinitely. - // Move flow control return to a goroutine? - cc.wmu.Lock() - // Return connection-level flow control. - if connAdd > 0 { - cc.fr.WriteWindowUpdate(0, uint32(connAdd)) - } - cc.bw.Flush() - cc.wmu.Unlock() - } - - select { - case <-cs.donec: - case <-cs.ctx.Done(): - // See golang/go#49366: The net/http package can cancel the - // request context after the response body is fully read. - // Don't treat this as an error. - return nil - case <-cs.reqCancel: - return http2errRequestCanceled - } - return nil -} - -func (rl *http2clientConnReadLoop) processData(f *http2DataFrame) error { - cc := rl.cc - cs := rl.streamByID(f.StreamID, http2headerOrDataFrame) - data := f.Data() - if cs == nil { - cc.mu.Lock() - neverSent := cc.nextStreamID - cc.mu.Unlock() - if f.StreamID >= neverSent { - // We never asked for this. - cc.logf("http2: Transport received unsolicited DATA frame; closing connection") - return http2ConnectionError(http2ErrCodeProtocol) - } - // We probably did ask for this, but canceled. Just ignore it. - // TODO: be stricter here? only silently ignore things which - // we canceled, but not things which were closed normally - // by the peer? Tough without accumulating too much state. - - // But at least return their flow control: - if f.Length > 0 { - cc.mu.Lock() - ok := cc.inflow.take(f.Length) - connAdd := cc.inflow.add(int(f.Length)) - cc.mu.Unlock() - if !ok { - return http2ConnectionError(http2ErrCodeFlowControl) - } - if connAdd > 0 { - cc.wmu.Lock() - cc.fr.WriteWindowUpdate(0, uint32(connAdd)) - cc.bw.Flush() - cc.wmu.Unlock() - } - } - return nil - } - if cs.readClosed { - cc.logf("protocol error: received DATA after END_STREAM") - rl.endStreamError(cs, http2StreamError{ - StreamID: f.StreamID, - Code: http2ErrCodeProtocol, - }) - return nil - } - if !cs.pastHeaders { - cc.logf("protocol error: received DATA before a HEADERS frame") - rl.endStreamError(cs, http2StreamError{ - StreamID: f.StreamID, - Code: http2ErrCodeProtocol, - }) - return nil - } - if f.Length > 0 { - if cs.isHead && len(data) > 0 { - cc.logf("protocol error: received DATA on a HEAD request") - rl.endStreamError(cs, http2StreamError{ - StreamID: f.StreamID, - Code: http2ErrCodeProtocol, - }) - return nil - } - // Check connection-level flow control. - cc.mu.Lock() - if !http2takeInflows(&cc.inflow, &cs.inflow, f.Length) { - cc.mu.Unlock() - return http2ConnectionError(http2ErrCodeFlowControl) - } - // Return any padded flow control now, since we won't - // refund it later on body reads. - var refund int - if pad := int(f.Length) - len(data); pad > 0 { - refund += pad - } - - didReset := false - var err error - if len(data) > 0 { - if _, err = cs.bufPipe.Write(data); err != nil { - // Return len(data) now if the stream is already closed, - // since data will never be read. - didReset = true - refund += len(data) - } - } - - sendConn := cc.inflow.add(refund) - var sendStream int32 - if !didReset { - sendStream = cs.inflow.add(refund) - } - cc.mu.Unlock() - - if sendConn > 0 || sendStream > 0 { - cc.wmu.Lock() - if sendConn > 0 { - cc.fr.WriteWindowUpdate(0, uint32(sendConn)) - } - if sendStream > 0 { - cc.fr.WriteWindowUpdate(cs.ID, uint32(sendStream)) - } - cc.bw.Flush() - cc.wmu.Unlock() - } - - if err != nil { - rl.endStreamError(cs, err) - return nil - } - } - - if f.StreamEnded() { - rl.endStream(cs) - } - return nil -} - -func (rl *http2clientConnReadLoop) endStream(cs *http2clientStream) { - // TODO: check that any declared content-length matches, like - // server.go's (*stream).endStream method. - if !cs.readClosed { - cs.readClosed = true - // Close cs.bufPipe and cs.peerClosed with cc.mu held to avoid a - // race condition: The caller can read io.EOF from Response.Body - // and close the body before we close cs.peerClosed, causing - // cleanupWriteRequest to send a RST_STREAM. - rl.cc.mu.Lock() - defer rl.cc.mu.Unlock() - cs.bufPipe.closeWithErrorAndCode(io.EOF, cs.copyTrailers) - close(cs.peerClosed) - } -} - -func (rl *http2clientConnReadLoop) endStreamError(cs *http2clientStream, err error) { - cs.readAborted = true - cs.abortStream(err) -} - -// Constants passed to streamByID for documentation purposes. -const ( - http2headerOrDataFrame = true - http2notHeaderOrDataFrame = false -) - -// streamByID returns the stream with the given id, or nil if no stream has that id. -// If headerOrData is true, it clears rst.StreamPingsBlocked. -func (rl *http2clientConnReadLoop) streamByID(id uint32, headerOrData bool) *http2clientStream { - rl.cc.mu.Lock() - defer rl.cc.mu.Unlock() - if headerOrData { - // Work around an unfortunate gRPC behavior. - // See comment on ClientConn.rstStreamPingsBlocked for details. - rl.cc.rstStreamPingsBlocked = false - } - rl.cc.readBeforeStreamID = rl.cc.nextStreamID - cs := rl.cc.streams[id] - if cs != nil && !cs.readAborted { - return cs - } - return nil -} - -func (cs *http2clientStream) copyTrailers() { - for k, vv := range cs.trailer { - t := cs.resTrailer - if *t == nil { - *t = make(Header) - } - (*t)[k] = vv - } -} - -func (rl *http2clientConnReadLoop) processGoAway(f *http2GoAwayFrame) error { - cc := rl.cc - cc.t.connPool().MarkDead(cc) - if f.ErrCode != 0 { - // TODO: deal with GOAWAY more. particularly the error code - cc.vlogf("transport got GOAWAY with error code = %v", f.ErrCode) - if fn := cc.t.CountError; fn != nil { - fn("recv_goaway_" + f.ErrCode.stringToken()) - } - } - cc.setGoAway(f) - return nil -} - -func (rl *http2clientConnReadLoop) processSettings(f *http2SettingsFrame) error { - cc := rl.cc - // Locking both mu and wmu here allows frame encoding to read settings with only wmu held. - // Acquiring wmu when f.IsAck() is unnecessary, but convenient and mostly harmless. - cc.wmu.Lock() - defer cc.wmu.Unlock() - - if err := rl.processSettingsNoWrite(f); err != nil { - return err - } - if !f.IsAck() { - cc.fr.WriteSettingsAck() - cc.bw.Flush() - } - return nil -} - -func (rl *http2clientConnReadLoop) processSettingsNoWrite(f *http2SettingsFrame) error { - cc := rl.cc - defer cc.maybeCallStateHook() - cc.mu.Lock() - defer cc.mu.Unlock() - - if f.IsAck() { - if cc.wantSettingsAck { - cc.wantSettingsAck = false - return nil - } - return http2ConnectionError(http2ErrCodeProtocol) - } - - var seenMaxConcurrentStreams bool - err := f.ForeachSetting(func(s http2Setting) error { - switch s.ID { - case http2SettingMaxFrameSize: - cc.maxFrameSize = s.Val - case http2SettingMaxConcurrentStreams: - cc.maxConcurrentStreams = s.Val - seenMaxConcurrentStreams = true - case http2SettingMaxHeaderListSize: - cc.peerMaxHeaderListSize = uint64(s.Val) - case http2SettingInitialWindowSize: - // Values above the maximum flow-control - // window size of 2^31-1 MUST be treated as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR. - if s.Val > math.MaxInt32 { - return http2ConnectionError(http2ErrCodeFlowControl) - } - - // Adjust flow control of currently-open - // frames by the difference of the old initial - // window size and this one. - delta := int32(s.Val) - int32(cc.initialWindowSize) - for _, cs := range cc.streams { - cs.flow.add(delta) - } - cc.cond.Broadcast() - - cc.initialWindowSize = s.Val - case http2SettingHeaderTableSize: - cc.henc.SetMaxDynamicTableSize(s.Val) - cc.peerMaxHeaderTableSize = s.Val - case http2SettingEnableConnectProtocol: - if err := s.Valid(); err != nil { - return err - } - // If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL, - // we require that it do so in the first SETTINGS frame. - // - // When we attempt to use extended CONNECT, we wait for the first - // SETTINGS frame to see if the server supports it. If we let the - // server enable the feature with a later SETTINGS frame, then - // users will see inconsistent results depending on whether we've - // seen that frame or not. - if !cc.seenSettings { - cc.extendedConnectAllowed = s.Val == 1 - } - default: - cc.vlogf("Unhandled Setting: %v", s) - } - return nil - }) - if err != nil { - return err - } - - if !cc.seenSettings { - if !seenMaxConcurrentStreams { - // This was the servers initial SETTINGS frame and it - // didn't contain a MAX_CONCURRENT_STREAMS field so - // increase the number of concurrent streams this - // connection can establish to our default. - cc.maxConcurrentStreams = http2defaultMaxConcurrentStreams - } - close(cc.seenSettingsChan) - cc.seenSettings = true - } - - return nil -} - -func (rl *http2clientConnReadLoop) processWindowUpdate(f *http2WindowUpdateFrame) error { - cc := rl.cc - cs := rl.streamByID(f.StreamID, http2notHeaderOrDataFrame) - if f.StreamID != 0 && cs == nil { - return nil - } - - cc.mu.Lock() - defer cc.mu.Unlock() - - fl := &cc.flow - if cs != nil { - fl = &cs.flow - } - if !fl.add(int32(f.Increment)) { - // For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR - if cs != nil { - rl.endStreamError(cs, http2StreamError{ - StreamID: f.StreamID, - Code: http2ErrCodeFlowControl, - }) - return nil - } - - return http2ConnectionError(http2ErrCodeFlowControl) - } - cc.cond.Broadcast() - return nil -} - -func (rl *http2clientConnReadLoop) processResetStream(f *http2RSTStreamFrame) error { - cs := rl.streamByID(f.StreamID, http2notHeaderOrDataFrame) - if cs == nil { - // TODO: return error if server tries to RST_STREAM an idle stream - return nil - } - serr := http2streamError(cs.ID, f.ErrCode) - serr.Cause = http2errFromPeer - if f.ErrCode == http2ErrCodeProtocol { - rl.cc.SetDoNotReuse() - } - if fn := cs.cc.t.CountError; fn != nil { - fn("recv_rststream_" + f.ErrCode.stringToken()) - } - cs.abortStream(serr) - - cs.bufPipe.CloseWithError(serr) - return nil -} - -// Ping sends a PING frame to the server and waits for the ack. -func (cc *http2ClientConn) Ping(ctx context.Context) error { - c := make(chan struct{}) - // Generate a random payload - var p [8]byte - for { - if _, err := rand.Read(p[:]); err != nil { - return err - } - cc.mu.Lock() - // check for dup before insert - if _, found := cc.pings[p]; !found { - cc.pings[p] = c - cc.mu.Unlock() - break - } - cc.mu.Unlock() - } - var pingError error - errc := make(chan struct{}) - go func() { - cc.wmu.Lock() - defer cc.wmu.Unlock() - if pingError = cc.fr.WritePing(false, p); pingError != nil { - close(errc) - return - } - if pingError = cc.bw.Flush(); pingError != nil { - close(errc) - return - } - }() - select { - case <-c: - return nil - case <-errc: - return pingError - case <-ctx.Done(): - return ctx.Err() - case <-cc.readerDone: - // connection closed - return cc.readerErr - } -} - -func (rl *http2clientConnReadLoop) processPing(f *http2PingFrame) error { - if f.IsAck() { - cc := rl.cc - defer cc.maybeCallStateHook() - cc.mu.Lock() - defer cc.mu.Unlock() - // If ack, notify listener if any - if c, ok := cc.pings[f.Data]; ok { - close(c) - delete(cc.pings, f.Data) - } - if cc.pendingResets > 0 { - // See clientStream.cleanupWriteRequest. - cc.pendingResets = 0 - cc.rstStreamPingsBlocked = true - cc.cond.Broadcast() - } - return nil - } - cc := rl.cc - cc.wmu.Lock() - defer cc.wmu.Unlock() - if err := cc.fr.WritePing(true, f.Data); err != nil { - return err - } - return cc.bw.Flush() -} - -func (rl *http2clientConnReadLoop) processPushPromise(f *http2PushPromiseFrame) error { - // We told the peer we don't want them. - // Spec says: - // "PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH - // setting of the peer endpoint is set to 0. An endpoint that - // has set this setting and has received acknowledgement MUST - // treat the receipt of a PUSH_PROMISE frame as a connection - // error (Section 5.4.1) of type PROTOCOL_ERROR." - return http2ConnectionError(http2ErrCodeProtocol) -} - -// writeStreamReset sends a RST_STREAM frame. -// When ping is true, it also sends a PING frame with a random payload. -func (cc *http2ClientConn) writeStreamReset(streamID uint32, code http2ErrCode, ping bool, err error) { - // TODO: map err to more interesting error codes, once the - // HTTP community comes up with some. But currently for - // RST_STREAM there's no equivalent to GOAWAY frame's debug - // data, and the error codes are all pretty vague ("cancel"). - cc.wmu.Lock() - cc.fr.WriteRSTStream(streamID, code) - if ping { - var payload [8]byte - rand.Read(payload[:]) - cc.fr.WritePing(false, payload) - } - cc.bw.Flush() - cc.wmu.Unlock() -} - -var ( - http2errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit") - http2errRequestHeaderListSize = httpcommon.ErrRequestHeaderListSize -) - -func (cc *http2ClientConn) logf(format string, args ...interface{}) { - cc.t.logf(format, args...) -} - -func (cc *http2ClientConn) vlogf(format string, args ...interface{}) { - cc.t.vlogf(format, args...) -} - -func (t *http2Transport) vlogf(format string, args ...interface{}) { - if http2VerboseLogs { - t.logf(format, args...) - } -} - -func (t *http2Transport) logf(format string, args ...interface{}) { - log.Printf(format, args...) -} - -var http2noBody io.ReadCloser = http2noBodyReader{} - -type http2noBodyReader struct{} - -func (http2noBodyReader) Close() error { return nil } - -func (http2noBodyReader) Read([]byte) (int, error) { return 0, io.EOF } - -type http2missingBody struct{} - -func (http2missingBody) Close() error { return nil } - -func (http2missingBody) Read([]byte) (int, error) { return 0, io.ErrUnexpectedEOF } - -func http2strSliceContains(ss []string, s string) bool { - for _, v := range ss { - if v == s { - return true - } - } - return false -} - -type http2erringRoundTripper struct{ err error } - -func (rt http2erringRoundTripper) RoundTripErr() error { return rt.err } - -func (rt http2erringRoundTripper) RoundTrip(*Request) (*Response, error) { return nil, rt.err } - -var http2errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body") - -// gzipReader wraps a response body so it can lazily -// get gzip.Reader from the pool on the first call to Read. -// After Close is called it puts gzip.Reader to the pool immediately -// if there is no Read in progress or later when Read completes. -type http2gzipReader struct { - _ http2incomparable - body io.ReadCloser // underlying Response.Body - mu sync.Mutex // guards zr and zerr - zr *gzip.Reader // stores gzip reader from the pool between reads - zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close -} - -type http2eofReader struct{} - -func (http2eofReader) Read([]byte) (int, error) { return 0, io.EOF } - -func (http2eofReader) ReadByte() (byte, error) { return 0, io.EOF } - -var http2gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }} - -// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r. -func http2gzipPoolGet(r io.Reader) (*gzip.Reader, error) { - zr := http2gzipPool.Get().(*gzip.Reader) - if err := zr.Reset(r); err != nil { - http2gzipPoolPut(zr) - return nil, err - } - return zr, nil -} - -// gzipPoolPut puts a gzip.Reader back into the pool. -func http2gzipPoolPut(zr *gzip.Reader) { - // Reset will allocate bufio.Reader if we pass it anything - // other than a flate.Reader, so ensure that it's getting one. - var r flate.Reader = http2eofReader{} - zr.Reset(r) - http2gzipPool.Put(zr) -} - -// acquire returns a gzip.Reader for reading response body. -// The reader must be released after use. -func (gz *http2gzipReader) acquire() (*gzip.Reader, error) { - gz.mu.Lock() - defer gz.mu.Unlock() - if gz.zerr != nil { - return nil, gz.zerr - } - if gz.zr == nil { - gz.zr, gz.zerr = http2gzipPoolGet(gz.body) - if gz.zerr != nil { - return nil, gz.zerr - } - } - ret := gz.zr - gz.zr, gz.zerr = nil, http2errConcurrentReadOnResBody - return ret, nil -} - -// release returns the gzip.Reader to the pool if Close was called during Read. -func (gz *http2gzipReader) release(zr *gzip.Reader) { - gz.mu.Lock() - defer gz.mu.Unlock() - if gz.zerr == http2errConcurrentReadOnResBody { - gz.zr, gz.zerr = zr, nil - } else { // fs.ErrClosed - http2gzipPoolPut(zr) - } -} - -// close returns the gzip.Reader to the pool immediately or -// signals release to do so after Read completes. -func (gz *http2gzipReader) close() { - gz.mu.Lock() - defer gz.mu.Unlock() - if gz.zerr == nil && gz.zr != nil { - http2gzipPoolPut(gz.zr) - gz.zr = nil - } - gz.zerr = fs.ErrClosed -} - -func (gz *http2gzipReader) Read(p []byte) (n int, err error) { - zr, err := gz.acquire() - if err != nil { - return 0, err - } - defer gz.release(zr) - - return zr.Read(p) -} - -func (gz *http2gzipReader) Close() error { - gz.close() - - return gz.body.Close() -} - -type http2errorReader struct{ err error } - -func (r http2errorReader) Read(p []byte) (int, error) { return 0, r.err } - -// isConnectionCloseRequest reports whether req should use its own -// connection for a single request and then close the connection. -func http2isConnectionCloseRequest(req *Request) bool { - return req.Close || httpguts.HeaderValuesContainsToken(req.Header["Connection"], "close") -} - -// registerHTTPSProtocol calls Transport.RegisterProtocol but -// converting panics into errors. -func http2registerHTTPSProtocol(t *Transport, rt http2noDialH2RoundTripper) (err error) { - defer func() { - if e := recover(); e != nil { - err = fmt.Errorf("%v", e) - } - }() - t.RegisterProtocol("https", rt) - return nil -} - -// noDialH2RoundTripper is a RoundTripper which only tries to complete the request -// if there's already a cached connection to the host. -// (The field is exported so it can be accessed via reflect from net/http; tested -// by TestNoDialH2RoundTripperType) -// -// A noDialH2RoundTripper is registered with http1.Transport.RegisterProtocol, -// and the http1.Transport can use type assertions to call non-RoundTrip methods on it. -// This lets us expose, for example, NewClientConn to net/http. -type http2noDialH2RoundTripper struct{ *http2Transport } - -func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) { - res, err := rt.http2Transport.RoundTrip(req) - if http2isNoCachedConnError(err) { - return nil, ErrSkipAltProtocol - } - return res, err -} - -func (rt http2noDialH2RoundTripper) NewClientConn(conn net.Conn, internalStateHook func()) (RoundTripper, error) { - tr := rt.http2Transport - cc, err := tr.newClientConn(conn, tr.disableKeepAlives(), internalStateHook) - if err != nil { - return nil, err - } - - // RoundTrip should block when the conn is at its concurrency limit, - // not return an error. Setting strictMaxConcurrentStreams enables this. - cc.strictMaxConcurrentStreams = true - - return http2netHTTPClientConn{cc}, nil -} - -// netHTTPClientConn wraps ClientConn and implements the interface net/http expects from -// the RoundTripper returned by NewClientConn. -type http2netHTTPClientConn struct { - cc *http2ClientConn -} - -func (cc http2netHTTPClientConn) RoundTrip(req *Request) (*Response, error) { - return cc.cc.RoundTrip(req) -} - -func (cc http2netHTTPClientConn) Close() error { - return cc.cc.Close() -} - -func (cc http2netHTTPClientConn) Err() error { - cc.cc.mu.Lock() - defer cc.cc.mu.Unlock() - if cc.cc.closed { - return errors.New("connection closed") - } - return nil -} - -func (cc http2netHTTPClientConn) Reserve() error { - defer cc.cc.maybeCallStateHook() - cc.cc.mu.Lock() - defer cc.cc.mu.Unlock() - if !cc.cc.canReserveLocked() { - return errors.New("connection is unavailable") - } - cc.cc.streamsReserved++ - return nil -} - -func (cc http2netHTTPClientConn) Release() { - defer cc.cc.maybeCallStateHook() - cc.cc.mu.Lock() - defer cc.cc.mu.Unlock() - // We don't complain if streamsReserved is 0. - // - // This is consistent with RoundTrip: both Release and RoundTrip will - // consume a reservation iff one exists. - if cc.cc.streamsReserved > 0 { - cc.cc.streamsReserved-- - } -} - -func (cc http2netHTTPClientConn) Available() int { - cc.cc.mu.Lock() - defer cc.cc.mu.Unlock() - return cc.cc.availableLocked() -} - -func (cc http2netHTTPClientConn) InFlight() int { - cc.cc.mu.Lock() - defer cc.cc.mu.Unlock() - return cc.cc.currentRequestCountLocked() -} - -func (cc *http2ClientConn) maybeCallStateHook() { - if cc.internalStateHook != nil { - cc.internalStateHook() - } -} - -func (t *http2Transport) idleConnTimeout() time.Duration { - // to keep things backwards compatible, we use non-zero values of - // IdleConnTimeout, followed by using the IdleConnTimeout on the underlying - // http1 transport, followed by 0 - if t.IdleConnTimeout != 0 { - return t.IdleConnTimeout - } - - if t.t1 != nil { - return t.t1.IdleConnTimeout - } - - return 0 -} - -func http2traceGetConn(req *Request, hostPort string) { - trace := httptrace.ContextClientTrace(req.Context()) - if trace == nil || trace.GetConn == nil { - return - } - trace.GetConn(hostPort) -} - -func http2traceGotConn(req *Request, cc *http2ClientConn, reused bool) { - trace := httptrace.ContextClientTrace(req.Context()) - if trace == nil || trace.GotConn == nil { - return - } - ci := httptrace.GotConnInfo{Conn: cc.tconn} - ci.Reused = reused - cc.mu.Lock() - ci.WasIdle = len(cc.streams) == 0 && reused - if ci.WasIdle && !cc.lastActive.IsZero() { - ci.IdleTime = time.Since(cc.lastActive) - } - cc.mu.Unlock() - - trace.GotConn(ci) -} - -func http2traceWroteHeaders(trace *httptrace.ClientTrace) { - if trace != nil && trace.WroteHeaders != nil { - trace.WroteHeaders() - } -} - -func http2traceGot100Continue(trace *httptrace.ClientTrace) { - if trace != nil && trace.Got100Continue != nil { - trace.Got100Continue() - } -} - -func http2traceWait100Continue(trace *httptrace.ClientTrace) { - if trace != nil && trace.Wait100Continue != nil { - trace.Wait100Continue() - } -} - -func http2traceWroteRequest(trace *httptrace.ClientTrace, err error) { - if trace != nil && trace.WroteRequest != nil { - trace.WroteRequest(httptrace.WroteRequestInfo{Err: err}) - } -} - -func http2traceFirstResponseByte(trace *httptrace.ClientTrace) { - if trace != nil && trace.GotFirstResponseByte != nil { - trace.GotFirstResponseByte() - } -} - -func http2traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error { - if trace != nil { - return trace.Got1xxResponse - } - return nil -} - -// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS -// connection. -func (t *http2Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) { - dialer := &tls.Dialer{ - Config: cfg, - } - cn, err := dialer.DialContext(ctx, network, addr) - if err != nil { - return nil, err - } - tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed - return tlsCn, nil -} - -const http2nextProtoUnencryptedHTTP2 = "unencrypted_http2" - -// unencryptedNetConnFromTLSConn retrieves a net.Conn wrapped in a *tls.Conn. -// -// TLSNextProto functions accept a *tls.Conn. -// -// When passing an unencrypted HTTP/2 connection to a TLSNextProto function, -// we pass a *tls.Conn with an underlying net.Conn containing the unencrypted connection. -// To be extra careful about mistakes (accidentally dropping TLS encryption in a place -// where we want it), the tls.Conn contains a net.Conn with an UnencryptedNetConn method -// that returns the actual connection we want to use. -func http2unencryptedNetConnFromTLSConn(tc *tls.Conn) (net.Conn, error) { - conner, ok := tc.NetConn().(interface { - UnencryptedNetConn() net.Conn - }) - if !ok { - return nil, errors.New("http2: TLS conn unexpectedly found in unencrypted handoff") - } - return conner.UnencryptedNetConn(), nil -} - -// writeFramer is implemented by any type that is used to write frames. -type http2writeFramer interface { - writeFrame(http2writeContext) error - - // staysWithinBuffer reports whether this writer promises that - // it will only write less than or equal to size bytes, and it - // won't Flush the write context. - staysWithinBuffer(size int) bool -} - -// writeContext is the interface needed by the various frame writer -// types below. All the writeFrame methods below are scheduled via the -// frame writing scheduler (see writeScheduler in writesched.go). -// -// This interface is implemented by *serverConn. -// -// TODO: decide whether to a) use this in the client code (which didn't -// end up using this yet, because it has a simpler design, not -// currently implementing priorities), or b) delete this and -// make the server code a bit more concrete. -type http2writeContext interface { - Framer() *http2Framer - Flush() error - CloseConn() error - // HeaderEncoder returns an HPACK encoder that writes to the - // returned buffer. - HeaderEncoder() (*hpack.Encoder, *bytes.Buffer) -} - -// writeEndsStream reports whether w writes a frame that will transition -// the stream to a half-closed local state. This returns false for RST_STREAM, -// which closes the entire stream (not just the local half). -func http2writeEndsStream(w http2writeFramer) bool { - switch v := w.(type) { - case *http2writeData: - return v.endStream - case *http2writeResHeaders: - return v.endStream - case nil: - // This can only happen if the caller reuses w after it's - // been intentionally nil'ed out to prevent use. Keep this - // here to catch future refactoring breaking it. - panic("writeEndsStream called on nil writeFramer") - } - return false -} - -type http2flushFrameWriter struct{} - -func (http2flushFrameWriter) writeFrame(ctx http2writeContext) error { - return ctx.Flush() -} - -func (http2flushFrameWriter) staysWithinBuffer(max int) bool { return false } - -type http2writeSettings []http2Setting - -func (s http2writeSettings) staysWithinBuffer(max int) bool { - const settingSize = 6 // uint16 + uint32 - return http2frameHeaderLen+settingSize*len(s) <= max - -} - -func (s http2writeSettings) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WriteSettings([]http2Setting(s)...) -} - -type http2writeGoAway struct { - maxStreamID uint32 - code http2ErrCode -} - -func (p *http2writeGoAway) writeFrame(ctx http2writeContext) error { - err := ctx.Framer().WriteGoAway(p.maxStreamID, p.code, nil) - ctx.Flush() // ignore error: we're hanging up on them anyway - return err -} - -func (*http2writeGoAway) staysWithinBuffer(max int) bool { return false } // flushes - -type http2writeData struct { - streamID uint32 - p []byte - endStream bool -} - -func (w *http2writeData) String() string { - return fmt.Sprintf("writeData(stream=%d, p=%d, endStream=%v)", w.streamID, len(w.p), w.endStream) -} - -func (w *http2writeData) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WriteData(w.streamID, w.endStream, w.p) -} - -func (w *http2writeData) staysWithinBuffer(max int) bool { - return http2frameHeaderLen+len(w.p) <= max -} - -// handlerPanicRST is the message sent from handler goroutines when -// the handler panics. -type http2handlerPanicRST struct { - StreamID uint32 -} - -func (hp http2handlerPanicRST) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WriteRSTStream(hp.StreamID, http2ErrCodeInternal) -} - -func (hp http2handlerPanicRST) staysWithinBuffer(max int) bool { return http2frameHeaderLen+4 <= max } - -func (se http2StreamError) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WriteRSTStream(se.StreamID, se.Code) -} - -func (se http2StreamError) staysWithinBuffer(max int) bool { return http2frameHeaderLen+4 <= max } - -type http2writePing struct { - data [8]byte -} - -func (w http2writePing) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WritePing(false, w.data) -} - -func (w http2writePing) staysWithinBuffer(max int) bool { - return http2frameHeaderLen+len(w.data) <= max -} - -type http2writePingAck struct{ pf *http2PingFrame } - -func (w http2writePingAck) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WritePing(true, w.pf.Data) -} - -func (w http2writePingAck) staysWithinBuffer(max int) bool { - return http2frameHeaderLen+len(w.pf.Data) <= max -} - -type http2writeSettingsAck struct{} - -func (http2writeSettingsAck) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WriteSettingsAck() -} - -func (http2writeSettingsAck) staysWithinBuffer(max int) bool { return http2frameHeaderLen <= max } - -// splitHeaderBlock splits headerBlock into fragments so that each fragment fits -// in a single frame, then calls fn for each fragment. firstFrag/lastFrag are true -// for the first/last fragment, respectively. -func http2splitHeaderBlock(ctx http2writeContext, headerBlock []byte, fn func(ctx http2writeContext, frag []byte, firstFrag, lastFrag bool) error) error { - // For now we're lazy and just pick the minimum MAX_FRAME_SIZE - // that all peers must support (16KB). Later we could care - // more and send larger frames if the peer advertised it, but - // there's little point. Most headers are small anyway (so we - // generally won't have CONTINUATION frames), and extra frames - // only waste 9 bytes anyway. - const maxFrameSize = 16384 - - first := true - for len(headerBlock) > 0 { - frag := headerBlock - if len(frag) > maxFrameSize { - frag = frag[:maxFrameSize] - } - headerBlock = headerBlock[len(frag):] - if err := fn(ctx, frag, first, len(headerBlock) == 0); err != nil { - return err - } - first = false - } - return nil -} - -// writeResHeaders is a request to write a HEADERS and 0+ CONTINUATION frames -// for HTTP response headers or trailers from a server handler. -type http2writeResHeaders struct { - streamID uint32 - httpResCode int // 0 means no ":status" line - h Header // may be nil - trailers []string // if non-nil, which keys of h to write. nil means all. - endStream bool - - date string - contentType string - contentLength string -} - -func http2encKV(enc *hpack.Encoder, k, v string) { - if http2VerboseLogs { - log.Printf("http2: server encoding header %q = %q", k, v) - } - enc.WriteField(hpack.HeaderField{Name: k, Value: v}) -} - -func (w *http2writeResHeaders) staysWithinBuffer(max int) bool { - // TODO: this is a common one. It'd be nice to return true - // here and get into the fast path if we could be clever and - // calculate the size fast enough, or at least a conservative - // upper bound that usually fires. (Maybe if w.h and - // w.trailers are nil, so we don't need to enumerate it.) - // Otherwise I'm afraid that just calculating the length to - // answer this question would be slower than the ~2µs benefit. - return false -} - -func (w *http2writeResHeaders) writeFrame(ctx http2writeContext) error { - enc, buf := ctx.HeaderEncoder() - buf.Reset() - - if w.httpResCode != 0 { - http2encKV(enc, ":status", http2httpCodeString(w.httpResCode)) - } - - http2encodeHeaders(enc, w.h, w.trailers) - - if w.contentType != "" { - http2encKV(enc, "content-type", w.contentType) - } - if w.contentLength != "" { - http2encKV(enc, "content-length", w.contentLength) - } - if w.date != "" { - http2encKV(enc, "date", w.date) - } - - headerBlock := buf.Bytes() - if len(headerBlock) == 0 && w.trailers == nil { - panic("unexpected empty hpack") - } - - return http2splitHeaderBlock(ctx, headerBlock, w.writeHeaderBlock) -} - -func (w *http2writeResHeaders) writeHeaderBlock(ctx http2writeContext, frag []byte, firstFrag, lastFrag bool) error { - if firstFrag { - return ctx.Framer().WriteHeaders(http2HeadersFrameParam{ - StreamID: w.streamID, - BlockFragment: frag, - EndStream: w.endStream, - EndHeaders: lastFrag, - }) - } else { - return ctx.Framer().WriteContinuation(w.streamID, lastFrag, frag) - } -} - -// writePushPromise is a request to write a PUSH_PROMISE and 0+ CONTINUATION frames. -type http2writePushPromise struct { - streamID uint32 // pusher stream - method string // for :method - url *url.URL // for :scheme, :authority, :path - h Header - - // Creates an ID for a pushed stream. This runs on serveG just before - // the frame is written. The returned ID is copied to promisedID. - allocatePromisedID func() (uint32, error) - promisedID uint32 -} - -func (w *http2writePushPromise) staysWithinBuffer(max int) bool { - // TODO: see writeResHeaders.staysWithinBuffer - return false -} - -func (w *http2writePushPromise) writeFrame(ctx http2writeContext) error { - enc, buf := ctx.HeaderEncoder() - buf.Reset() - - http2encKV(enc, ":method", w.method) - http2encKV(enc, ":scheme", w.url.Scheme) - http2encKV(enc, ":authority", w.url.Host) - http2encKV(enc, ":path", w.url.RequestURI()) - http2encodeHeaders(enc, w.h, nil) - - headerBlock := buf.Bytes() - if len(headerBlock) == 0 { - panic("unexpected empty hpack") - } - - return http2splitHeaderBlock(ctx, headerBlock, w.writeHeaderBlock) -} - -func (w *http2writePushPromise) writeHeaderBlock(ctx http2writeContext, frag []byte, firstFrag, lastFrag bool) error { - if firstFrag { - return ctx.Framer().WritePushPromise(http2PushPromiseParam{ - StreamID: w.streamID, - PromiseID: w.promisedID, - BlockFragment: frag, - EndHeaders: lastFrag, - }) - } else { - return ctx.Framer().WriteContinuation(w.streamID, lastFrag, frag) - } -} - -type http2write100ContinueHeadersFrame struct { - streamID uint32 -} - -func (w http2write100ContinueHeadersFrame) writeFrame(ctx http2writeContext) error { - enc, buf := ctx.HeaderEncoder() - buf.Reset() - http2encKV(enc, ":status", "100") - return ctx.Framer().WriteHeaders(http2HeadersFrameParam{ - StreamID: w.streamID, - BlockFragment: buf.Bytes(), - EndStream: false, - EndHeaders: true, - }) -} - -func (w http2write100ContinueHeadersFrame) staysWithinBuffer(max int) bool { - // Sloppy but conservative: - return 9+2*(len(":status")+len("100")) <= max -} - -type http2writeWindowUpdate struct { - streamID uint32 // or 0 for conn-level - n uint32 -} - -func (wu http2writeWindowUpdate) staysWithinBuffer(max int) bool { return http2frameHeaderLen+4 <= max } - -func (wu http2writeWindowUpdate) writeFrame(ctx http2writeContext) error { - return ctx.Framer().WriteWindowUpdate(wu.streamID, wu.n) -} - -// encodeHeaders encodes an http.Header. If keys is not nil, then (k, h[k]) -// is encoded only if k is in keys. -func http2encodeHeaders(enc *hpack.Encoder, h Header, keys []string) { - if keys == nil { - sorter := http2sorterPool.Get().(*http2sorter) - // Using defer here, since the returned keys from the - // sorter.Keys method is only valid until the sorter - // is returned: - defer http2sorterPool.Put(sorter) - keys = sorter.Keys(h) - } - for _, k := range keys { - vv := h[k] - k, ascii := httpcommon.LowerHeader(k) - if !ascii { - // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header - // field names have to be ASCII characters (just as in HTTP/1.x). - continue - } - if !http2validWireHeaderFieldName(k) { - // Skip it as backup paranoia. Per - // golang.org/issue/14048, these should - // already be rejected at a higher level. - continue - } - isTE := k == "transfer-encoding" - for _, v := range vv { - if !httpguts.ValidHeaderFieldValue(v) { - // TODO: return an error? golang.org/issue/14048 - // For now just omit it. - continue - } - // TODO: more of "8.1.2.2 Connection-Specific Header Fields" - if isTE && v != "trailers" { - continue - } - http2encKV(enc, k, v) - } - } -} - -// WriteScheduler is the interface implemented by HTTP/2 write schedulers. -// Methods are never called concurrently. -type http2WriteScheduler interface { - // OpenStream opens a new stream in the write scheduler. - // It is illegal to call this with streamID=0 or with a streamID that is - // already open -- the call may panic. - OpenStream(streamID uint32, options http2OpenStreamOptions) - - // CloseStream closes a stream in the write scheduler. Any frames queued on - // this stream should be discarded. It is illegal to call this on a stream - // that is not open -- the call may panic. - CloseStream(streamID uint32) - - // AdjustStream adjusts the priority of the given stream. This may be called - // on a stream that has not yet been opened or has been closed. Note that - // RFC 7540 allows PRIORITY frames to be sent on streams in any state. See: - // https://tools.ietf.org/html/rfc7540#section-5.1 - AdjustStream(streamID uint32, priority http2PriorityParam) - - // Push queues a frame in the scheduler. In most cases, this will not be - // called with wr.StreamID()!=0 unless that stream is currently open. The one - // exception is RST_STREAM frames, which may be sent on idle or closed streams. - Push(wr http2FrameWriteRequest) - - // Pop dequeues the next frame to write. Returns false if no frames can - // be written. Frames with a given wr.StreamID() are Pop'd in the same - // order they are Push'd, except RST_STREAM frames. No frames should be - // discarded except by CloseStream. - Pop() (wr http2FrameWriteRequest, ok bool) -} - -// OpenStreamOptions specifies extra options for WriteScheduler.OpenStream. -type http2OpenStreamOptions struct { - // PusherID is zero if the stream was initiated by the client. Otherwise, - // PusherID names the stream that pushed the newly opened stream. - PusherID uint32 - // priority is used to set the priority of the newly opened stream. - priority http2PriorityParam -} - -// FrameWriteRequest is a request to write a frame. -type http2FrameWriteRequest struct { - // write is the interface value that does the writing, once the - // WriteScheduler has selected this frame to write. The write - // functions are all defined in write.go. - write http2writeFramer - - // stream is the stream on which this frame will be written. - // nil for non-stream frames like PING and SETTINGS. - // nil for RST_STREAM streams, which use the StreamError.StreamID field instead. - stream *http2stream - - // done, if non-nil, must be a buffered channel with space for - // 1 message and is sent the return value from write (or an - // earlier error) when the frame has been written. - done chan error -} - -// StreamID returns the id of the stream this frame will be written to. -// 0 is used for non-stream frames such as PING and SETTINGS. -func (wr http2FrameWriteRequest) StreamID() uint32 { - if wr.stream == nil { - if se, ok := wr.write.(http2StreamError); ok { - // (*serverConn).resetStream doesn't set - // stream because it doesn't necessarily have - // one. So special case this type of write - // message. - return se.StreamID - } - return 0 - } - return wr.stream.id -} - -// isControl reports whether wr is a control frame for MaxQueuedControlFrames -// purposes. That includes non-stream frames and RST_STREAM frames. -func (wr http2FrameWriteRequest) isControl() bool { - return wr.stream == nil -} - -// DataSize returns the number of flow control bytes that must be consumed -// to write this entire frame. This is 0 for non-DATA frames. -func (wr http2FrameWriteRequest) DataSize() int { - if wd, ok := wr.write.(*http2writeData); ok { - return len(wd.p) - } - return 0 -} - -// Consume consumes min(n, available) bytes from this frame, where available -// is the number of flow control bytes available on the stream. Consume returns -// 0, 1, or 2 frames, where the integer return value gives the number of frames -// returned. -// -// If flow control prevents consuming any bytes, this returns (_, _, 0). If -// the entire frame was consumed, this returns (wr, _, 1). Otherwise, this -// returns (consumed, rest, 2), where 'consumed' contains the consumed bytes and -// 'rest' contains the remaining bytes. The consumed bytes are deducted from the -// underlying stream's flow control budget. -func (wr http2FrameWriteRequest) Consume(n int32) (http2FrameWriteRequest, http2FrameWriteRequest, int) { - var empty http2FrameWriteRequest - - // Non-DATA frames are always consumed whole. - wd, ok := wr.write.(*http2writeData) - if !ok || len(wd.p) == 0 { - return wr, empty, 1 - } - - // Might need to split after applying limits. - allowed := wr.stream.flow.available() - if n < allowed { - allowed = n - } - if wr.stream.sc.maxFrameSize < allowed { - allowed = wr.stream.sc.maxFrameSize - } - if allowed <= 0 { - return empty, empty, 0 - } - if len(wd.p) > int(allowed) { - wr.stream.flow.take(allowed) - consumed := http2FrameWriteRequest{ - stream: wr.stream, - write: &http2writeData{ - streamID: wd.streamID, - p: wd.p[:allowed], - // Even if the original had endStream set, there - // are bytes remaining because len(wd.p) > allowed, - // so we know endStream is false. - endStream: false, - }, - // Our caller is blocking on the final DATA frame, not - // this intermediate frame, so no need to wait. - done: nil, - } - rest := http2FrameWriteRequest{ - stream: wr.stream, - write: &http2writeData{ - streamID: wd.streamID, - p: wd.p[allowed:], - endStream: wd.endStream, - }, - done: wr.done, - } - return consumed, rest, 2 - } - - // The frame is consumed whole. - // NB: This cast cannot overflow because allowed is <= math.MaxInt32. - wr.stream.flow.take(int32(len(wd.p))) - return wr, empty, 1 -} - -// String is for debugging only. -func (wr http2FrameWriteRequest) String() string { - var des string - if s, ok := wr.write.(fmt.Stringer); ok { - des = s.String() - } else { - des = fmt.Sprintf("%T", wr.write) - } - return fmt.Sprintf("[FrameWriteRequest stream=%d, ch=%v, writer=%v]", wr.StreamID(), wr.done != nil, des) -} - -// replyToWriter sends err to wr.done and panics if the send must block -// This does nothing if wr.done is nil. -func (wr *http2FrameWriteRequest) replyToWriter(err error) { - if wr.done == nil { - return - } - select { - case wr.done <- err: - default: - panic(fmt.Sprintf("unbuffered done channel passed in for type %T", wr.write)) - } - wr.write = nil // prevent use (assume it's tainted after wr.done send) -} - -// writeQueue is used by implementations of WriteScheduler. -// -// Each writeQueue contains a queue of FrameWriteRequests, meant to store all -// FrameWriteRequests associated with a given stream. This is implemented as a -// two-stage queue: currQueue[currPos:] and nextQueue. Removing an item is done -// by incrementing currPos of currQueue. Adding an item is done by appending it -// to the nextQueue. If currQueue is empty when trying to remove an item, we -// can swap currQueue and nextQueue to remedy the situation. -// This two-stage queue is analogous to the use of two lists in Okasaki's -// purely functional queue but without the overhead of reversing the list when -// swapping stages. -// -// writeQueue also contains prev and next, this can be used by implementations -// of WriteScheduler to construct data structures that represent the order of -// writing between different streams (e.g. circular linked list). -type http2writeQueue struct { - currQueue []http2FrameWriteRequest - nextQueue []http2FrameWriteRequest - currPos int - - prev, next *http2writeQueue -} - -func (q *http2writeQueue) empty() bool { - return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0 -} - -func (q *http2writeQueue) push(wr http2FrameWriteRequest) { - q.nextQueue = append(q.nextQueue, wr) -} - -func (q *http2writeQueue) shift() http2FrameWriteRequest { - if q.empty() { - panic("invalid use of queue") - } - if q.currPos >= len(q.currQueue) { - q.currQueue, q.currPos, q.nextQueue = q.nextQueue, 0, q.currQueue[:0] - } - wr := q.currQueue[q.currPos] - q.currQueue[q.currPos] = http2FrameWriteRequest{} - q.currPos++ - return wr -} - -func (q *http2writeQueue) peek() *http2FrameWriteRequest { - if q.currPos < len(q.currQueue) { - return &q.currQueue[q.currPos] - } - if len(q.nextQueue) > 0 { - return &q.nextQueue[0] - } - return nil -} - -// consume consumes up to n bytes from q.s[0]. If the frame is -// entirely consumed, it is removed from the queue. If the frame -// is partially consumed, the frame is kept with the consumed -// bytes removed. Returns true iff any bytes were consumed. -func (q *http2writeQueue) consume(n int32) (http2FrameWriteRequest, bool) { - if q.empty() { - return http2FrameWriteRequest{}, false - } - consumed, rest, numresult := q.peek().Consume(n) - switch numresult { - case 0: - return http2FrameWriteRequest{}, false - case 1: - q.shift() - case 2: - *q.peek() = rest - } - return consumed, true -} - -type http2writeQueuePool []*http2writeQueue - -// put inserts an unused writeQueue into the pool. - -// put inserts an unused writeQueue into the pool. -func (p *http2writeQueuePool) put(q *http2writeQueue) { - for i := range q.currQueue { - q.currQueue[i] = http2FrameWriteRequest{} - } - for i := range q.nextQueue { - q.nextQueue[i] = http2FrameWriteRequest{} - } - q.currQueue = q.currQueue[:0] - q.nextQueue = q.nextQueue[:0] - q.currPos = 0 - *p = append(*p, q) -} - -// get returns an empty writeQueue. -func (p *http2writeQueuePool) get() *http2writeQueue { - ln := len(*p) - if ln == 0 { - return new(http2writeQueue) - } - x := ln - 1 - q := (*p)[x] - (*p)[x] = nil - *p = (*p)[:x] - return q -} - -// RFC 7540, Section 5.3.5: the default weight is 16. -const http2priorityDefaultWeightRFC7540 = 15 // 16 = 15 + 1 - -// PriorityWriteSchedulerConfig configures a priorityWriteScheduler. -type http2PriorityWriteSchedulerConfig struct { - // MaxClosedNodesInTree controls the maximum number of closed streams to - // retain in the priority tree. Setting this to zero saves a small amount - // of memory at the cost of performance. - // - // See RFC 7540, Section 5.3.4: - // "It is possible for a stream to become closed while prioritization - // information ... is in transit. ... This potentially creates suboptimal - // prioritization, since the stream could be given a priority that is - // different from what is intended. To avoid these problems, an endpoint - // SHOULD retain stream prioritization state for a period after streams - // become closed. The longer state is retained, the lower the chance that - // streams are assigned incorrect or default priority values." - MaxClosedNodesInTree int - - // MaxIdleNodesInTree controls the maximum number of idle streams to - // retain in the priority tree. Setting this to zero saves a small amount - // of memory at the cost of performance. - // - // See RFC 7540, Section 5.3.4: - // Similarly, streams that are in the "idle" state can be assigned - // priority or become a parent of other streams. This allows for the - // creation of a grouping node in the dependency tree, which enables - // more flexible expressions of priority. Idle streams begin with a - // default priority (Section 5.3.5). - MaxIdleNodesInTree int - - // ThrottleOutOfOrderWrites enables write throttling to help ensure that - // data is delivered in priority order. This works around a race where - // stream B depends on stream A and both streams are about to call Write - // to queue DATA frames. If B wins the race, a naive scheduler would eagerly - // write as much data from B as possible, but this is suboptimal because A - // is a higher-priority stream. With throttling enabled, we write a small - // amount of data from B to minimize the amount of bandwidth that B can - // steal from A. - ThrottleOutOfOrderWrites bool -} - -// NewPriorityWriteScheduler constructs a WriteScheduler that schedules -// frames by following HTTP/2 priorities as described in RFC 7540 Section 5.3. -// If cfg is nil, default options are used. -func http2NewPriorityWriteScheduler(cfg *http2PriorityWriteSchedulerConfig) http2WriteScheduler { - return http2newPriorityWriteSchedulerRFC7540(cfg) -} - -func http2newPriorityWriteSchedulerRFC7540(cfg *http2PriorityWriteSchedulerConfig) http2WriteScheduler { - if cfg == nil { - // For justification of these defaults, see: - // https://docs.google.com/document/d/1oLhNg1skaWD4_DtaoCxdSRN5erEXrH-KnLrMwEpOtFY - cfg = &http2PriorityWriteSchedulerConfig{ - MaxClosedNodesInTree: 10, - MaxIdleNodesInTree: 10, - ThrottleOutOfOrderWrites: false, - } - } - - ws := &http2priorityWriteSchedulerRFC7540{ - nodes: make(map[uint32]*http2priorityNodeRFC7540), - maxClosedNodesInTree: cfg.MaxClosedNodesInTree, - maxIdleNodesInTree: cfg.MaxIdleNodesInTree, - enableWriteThrottle: cfg.ThrottleOutOfOrderWrites, - } - ws.nodes[0] = &ws.root - if cfg.ThrottleOutOfOrderWrites { - ws.writeThrottleLimit = 1024 - } else { - ws.writeThrottleLimit = math.MaxInt32 - } - return ws -} - -type http2priorityNodeStateRFC7540 int - -const ( - http2priorityNodeOpenRFC7540 http2priorityNodeStateRFC7540 = iota - http2priorityNodeClosedRFC7540 - http2priorityNodeIdleRFC7540 -) - -// priorityNodeRFC7540 is a node in an HTTP/2 priority tree. -// Each node is associated with a single stream ID. -// See RFC 7540, Section 5.3. -type http2priorityNodeRFC7540 struct { - q http2writeQueue // queue of pending frames to write - id uint32 // id of the stream, or 0 for the root of the tree - weight uint8 // the actual weight is weight+1, so the value is in [1,256] - state http2priorityNodeStateRFC7540 // open | closed | idle - bytes int64 // number of bytes written by this node, or 0 if closed - subtreeBytes int64 // sum(node.bytes) of all nodes in this subtree - - // These links form the priority tree. - parent *http2priorityNodeRFC7540 - kids *http2priorityNodeRFC7540 // start of the kids list - prev, next *http2priorityNodeRFC7540 // doubly-linked list of siblings -} - -func (n *http2priorityNodeRFC7540) setParent(parent *http2priorityNodeRFC7540) { - if n == parent { - panic("setParent to self") - } - if n.parent == parent { - return - } - // Unlink from current parent. - if parent := n.parent; parent != nil { - if n.prev == nil { - parent.kids = n.next - } else { - n.prev.next = n.next - } - if n.next != nil { - n.next.prev = n.prev - } - } - // Link to new parent. - // If parent=nil, remove n from the tree. - // Always insert at the head of parent.kids (this is assumed by walkReadyInOrder). - n.parent = parent - if parent == nil { - n.next = nil - n.prev = nil - } else { - n.next = parent.kids - n.prev = nil - if n.next != nil { - n.next.prev = n - } - parent.kids = n - } -} - -func (n *http2priorityNodeRFC7540) addBytes(b int64) { - n.bytes += b - for ; n != nil; n = n.parent { - n.subtreeBytes += b - } -} - -// walkReadyInOrder iterates over the tree in priority order, calling f for each node -// with a non-empty write queue. When f returns true, this function returns true and the -// walk halts. tmp is used as scratch space for sorting. -// -// f(n, openParent) takes two arguments: the node to visit, n, and a bool that is true -// if any ancestor p of n is still open (ignoring the root node). -func (n *http2priorityNodeRFC7540) walkReadyInOrder(openParent bool, tmp *[]*http2priorityNodeRFC7540, f func(*http2priorityNodeRFC7540, bool) bool) bool { - if !n.q.empty() && f(n, openParent) { - return true - } - if n.kids == nil { - return false - } - - // Don't consider the root "open" when updating openParent since - // we can't send data frames on the root stream (only control frames). - if n.id != 0 { - openParent = openParent || (n.state == http2priorityNodeOpenRFC7540) - } - - // Common case: only one kid or all kids have the same weight. - // Some clients don't use weights; other clients (like web browsers) - // use mostly-linear priority trees. - w := n.kids.weight - needSort := false - for k := n.kids.next; k != nil; k = k.next { - if k.weight != w { - needSort = true - break - } - } - if !needSort { - for k := n.kids; k != nil; k = k.next { - if k.walkReadyInOrder(openParent, tmp, f) { - return true - } - } - return false - } - - // Uncommon case: sort the child nodes. We remove the kids from the parent, - // then re-insert after sorting so we can reuse tmp for future sort calls. - *tmp = (*tmp)[:0] - for n.kids != nil { - *tmp = append(*tmp, n.kids) - n.kids.setParent(nil) - } - sort.Sort(http2sortPriorityNodeSiblingsRFC7540(*tmp)) - for i := len(*tmp) - 1; i >= 0; i-- { - (*tmp)[i].setParent(n) // setParent inserts at the head of n.kids - } - for k := n.kids; k != nil; k = k.next { - if k.walkReadyInOrder(openParent, tmp, f) { - return true - } - } - return false -} - -type http2sortPriorityNodeSiblingsRFC7540 []*http2priorityNodeRFC7540 - -func (z http2sortPriorityNodeSiblingsRFC7540) Len() int { return len(z) } - -func (z http2sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k], z[i] } - -func (z http2sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool { - // Prefer the subtree that has sent fewer bytes relative to its weight. - // See sections 5.3.2 and 5.3.4. - wi, bi := float64(z[i].weight)+1, float64(z[i].subtreeBytes) - wk, bk := float64(z[k].weight)+1, float64(z[k].subtreeBytes) - if bi == 0 && bk == 0 { - return wi >= wk - } - if bk == 0 { - return false - } - return bi/bk <= wi/wk -} - -type http2priorityWriteSchedulerRFC7540 struct { - // root is the root of the priority tree, where root.id = 0. - // The root queues control frames that are not associated with any stream. - root http2priorityNodeRFC7540 - - // nodes maps stream ids to priority tree nodes. - nodes map[uint32]*http2priorityNodeRFC7540 - - // maxID is the maximum stream id in nodes. - maxID uint32 - - // lists of nodes that have been closed or are idle, but are kept in - // the tree for improved prioritization. When the lengths exceed either - // maxClosedNodesInTree or maxIdleNodesInTree, old nodes are discarded. - closedNodes, idleNodes []*http2priorityNodeRFC7540 - - // From the config. - maxClosedNodesInTree int - maxIdleNodesInTree int - writeThrottleLimit int32 - enableWriteThrottle bool - - // tmp is scratch space for priorityNode.walkReadyInOrder to reduce allocations. - tmp []*http2priorityNodeRFC7540 - - // pool of empty queues for reuse. - queuePool http2writeQueuePool -} - -func (ws *http2priorityWriteSchedulerRFC7540) OpenStream(streamID uint32, options http2OpenStreamOptions) { - // The stream may be currently idle but cannot be opened or closed. - if curr := ws.nodes[streamID]; curr != nil { - if curr.state != http2priorityNodeIdleRFC7540 { - panic(fmt.Sprintf("stream %d already opened", streamID)) - } - curr.state = http2priorityNodeOpenRFC7540 - return - } - - // RFC 7540, Section 5.3.5: - // "All streams are initially assigned a non-exclusive dependency on stream 0x0. - // Pushed streams initially depend on their associated stream. In both cases, - // streams are assigned a default weight of 16." - parent := ws.nodes[options.PusherID] - if parent == nil { - parent = &ws.root - } - n := &http2priorityNodeRFC7540{ - q: *ws.queuePool.get(), - id: streamID, - weight: http2priorityDefaultWeightRFC7540, - state: http2priorityNodeOpenRFC7540, - } - n.setParent(parent) - ws.nodes[streamID] = n - if streamID > ws.maxID { - ws.maxID = streamID - } -} - -func (ws *http2priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) { - if streamID == 0 { - panic("violation of WriteScheduler interface: cannot close stream 0") - } - if ws.nodes[streamID] == nil { - panic(fmt.Sprintf("violation of WriteScheduler interface: unknown stream %d", streamID)) - } - if ws.nodes[streamID].state != http2priorityNodeOpenRFC7540 { - panic(fmt.Sprintf("violation of WriteScheduler interface: stream %d already closed", streamID)) - } - - n := ws.nodes[streamID] - n.state = http2priorityNodeClosedRFC7540 - n.addBytes(-n.bytes) - - q := n.q - ws.queuePool.put(&q) - if ws.maxClosedNodesInTree > 0 { - ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n) - } else { - ws.removeNode(n) - } -} - -func (ws *http2priorityWriteSchedulerRFC7540) AdjustStream(streamID uint32, priority http2PriorityParam) { - if streamID == 0 { - panic("adjustPriority on root") - } - - // If streamID does not exist, there are two cases: - // - A closed stream that has been removed (this will have ID <= maxID) - // - An idle stream that is being used for "grouping" (this will have ID > maxID) - n := ws.nodes[streamID] - if n == nil { - if streamID <= ws.maxID || ws.maxIdleNodesInTree == 0 { - return - } - ws.maxID = streamID - n = &http2priorityNodeRFC7540{ - q: *ws.queuePool.get(), - id: streamID, - weight: http2priorityDefaultWeightRFC7540, - state: http2priorityNodeIdleRFC7540, - } - n.setParent(&ws.root) - ws.nodes[streamID] = n - ws.addClosedOrIdleNode(&ws.idleNodes, ws.maxIdleNodesInTree, n) - } - - // Section 5.3.1: A dependency on a stream that is not currently in the tree - // results in that stream being given a default priority (Section 5.3.5). - parent := ws.nodes[priority.StreamDep] - if parent == nil { - n.setParent(&ws.root) - n.weight = http2priorityDefaultWeightRFC7540 - return - } - - // Ignore if the client tries to make a node its own parent. - if n == parent { - return - } - - // Section 5.3.3: - // "If a stream is made dependent on one of its own dependencies, the - // formerly dependent stream is first moved to be dependent on the - // reprioritized stream's previous parent. The moved dependency retains - // its weight." - // - // That is: if parent depends on n, move parent to depend on n.parent. - for x := parent.parent; x != nil; x = x.parent { - if x == n { - parent.setParent(n.parent) - break - } - } - - // Section 5.3.3: The exclusive flag causes the stream to become the sole - // dependency of its parent stream, causing other dependencies to become - // dependent on the exclusive stream. - if priority.Exclusive { - k := parent.kids - for k != nil { - next := k.next - if k != n { - k.setParent(n) - } - k = next - } - } - - n.setParent(parent) - n.weight = priority.Weight -} - -func (ws *http2priorityWriteSchedulerRFC7540) Push(wr http2FrameWriteRequest) { - var n *http2priorityNodeRFC7540 - if wr.isControl() { - n = &ws.root - } else { - id := wr.StreamID() - n = ws.nodes[id] - if n == nil { - // id is an idle or closed stream. wr should not be a HEADERS or - // DATA frame. In other case, we push wr onto the root, rather - // than creating a new priorityNode. - if wr.DataSize() > 0 { - panic("add DATA on non-open stream") - } - n = &ws.root - } - } - n.q.push(wr) -} - -func (ws *http2priorityWriteSchedulerRFC7540) Pop() (wr http2FrameWriteRequest, ok bool) { - ws.root.walkReadyInOrder(false, &ws.tmp, func(n *http2priorityNodeRFC7540, openParent bool) bool { - limit := int32(math.MaxInt32) - if openParent { - limit = ws.writeThrottleLimit - } - wr, ok = n.q.consume(limit) - if !ok { - return false - } - n.addBytes(int64(wr.DataSize())) - // If B depends on A and B continuously has data available but A - // does not, gradually increase the throttling limit to allow B to - // steal more and more bandwidth from A. - if openParent { - ws.writeThrottleLimit += 1024 - if ws.writeThrottleLimit < 0 { - ws.writeThrottleLimit = math.MaxInt32 - } - } else if ws.enableWriteThrottle { - ws.writeThrottleLimit = 1024 - } - return true - }) - return wr, ok -} - -func (ws *http2priorityWriteSchedulerRFC7540) addClosedOrIdleNode(list *[]*http2priorityNodeRFC7540, maxSize int, n *http2priorityNodeRFC7540) { - if maxSize == 0 { - return - } - if len(*list) == maxSize { - // Remove the oldest node, then shift left. - ws.removeNode((*list)[0]) - x := (*list)[1:] - copy(*list, x) - *list = (*list)[:len(x)] - } - *list = append(*list, n) -} - -func (ws *http2priorityWriteSchedulerRFC7540) removeNode(n *http2priorityNodeRFC7540) { - for n.kids != nil { - n.kids.setParent(n.parent) - } - n.setParent(nil) - delete(ws.nodes, n.id) -} - -type http2streamMetadata struct { - location *http2writeQueue - priority http2PriorityParam -} - -type http2priorityWriteSchedulerRFC9218 struct { - // control contains control frames (SETTINGS, PING, etc.). - control http2writeQueue - - // heads contain the head of a circular list of streams. - // We put these heads within a nested array that represents urgency and - // incremental, as defined in - // https://www.rfc-editor.org/rfc/rfc9218.html#name-priority-parameters. - // 8 represents u=0 up to u=7, and 2 represents i=false and i=true. - heads [8][2]*http2writeQueue - - // streams contains a mapping between each stream ID and their metadata, so - // we can quickly locate them when needing to, for example, adjust their - // priority. - streams map[uint32]http2streamMetadata - - // queuePool are empty queues for reuse. - queuePool http2writeQueuePool - - // prioritizeIncremental is used to determine whether we should prioritize - // incremental streams or not, when urgency is the same in a given Pop() - // call. - prioritizeIncremental bool - - // priorityUpdateBuf is used to buffer the most recent PRIORITY_UPDATE we - // receive per https://www.rfc-editor.org/rfc/rfc9218.html#name-the-priority_update-frame. - priorityUpdateBuf struct { - // streamID being 0 means that the buffer is empty. This is a safe - // assumption as PRIORITY_UPDATE for stream 0 is a PROTOCOL_ERROR. - streamID uint32 - priority http2PriorityParam - } -} - -func http2newPriorityWriteSchedulerRFC9218() http2WriteScheduler { - ws := &http2priorityWriteSchedulerRFC9218{ - streams: make(map[uint32]http2streamMetadata), - } - return ws -} - -func (ws *http2priorityWriteSchedulerRFC9218) OpenStream(streamID uint32, opt http2OpenStreamOptions) { - if ws.streams[streamID].location != nil { - panic(fmt.Errorf("stream %d already opened", streamID)) - } - if streamID == ws.priorityUpdateBuf.streamID { - ws.priorityUpdateBuf.streamID = 0 - opt.priority = ws.priorityUpdateBuf.priority - } - q := ws.queuePool.get() - ws.streams[streamID] = http2streamMetadata{ - location: q, - priority: opt.priority, - } - - u, i := opt.priority.urgency, opt.priority.incremental - if ws.heads[u][i] == nil { - ws.heads[u][i] = q - q.next = q - q.prev = q - } else { - // Queues are stored in a ring. - // Insert the new stream before ws.head, putting it at the end of the list. - q.prev = ws.heads[u][i].prev - q.next = ws.heads[u][i] - q.prev.next = q - q.next.prev = q - } -} - -func (ws *http2priorityWriteSchedulerRFC9218) CloseStream(streamID uint32) { - metadata := ws.streams[streamID] - q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental - if q == nil { - return - } - if q.next == q { - // This was the only open stream. - ws.heads[u][i] = nil - } else { - q.prev.next = q.next - q.next.prev = q.prev - if ws.heads[u][i] == q { - ws.heads[u][i] = q.next - } - } - delete(ws.streams, streamID) - ws.queuePool.put(q) -} - -func (ws *http2priorityWriteSchedulerRFC9218) AdjustStream(streamID uint32, priority http2PriorityParam) { - metadata := ws.streams[streamID] - q, u, i := metadata.location, metadata.priority.urgency, metadata.priority.incremental - if q == nil { - ws.priorityUpdateBuf.streamID = streamID - ws.priorityUpdateBuf.priority = priority - return - } - - // Remove stream from current location. - if q.next == q { - // This was the only open stream. - ws.heads[u][i] = nil - } else { - q.prev.next = q.next - q.next.prev = q.prev - if ws.heads[u][i] == q { - ws.heads[u][i] = q.next - } - } - - // Insert stream to the new queue. - u, i = priority.urgency, priority.incremental - if ws.heads[u][i] == nil { - ws.heads[u][i] = q - q.next = q - q.prev = q - } else { - // Queues are stored in a ring. - // Insert the new stream before ws.head, putting it at the end of the list. - q.prev = ws.heads[u][i].prev - q.next = ws.heads[u][i] - q.prev.next = q - q.next.prev = q - } - - // Update the metadata. - ws.streams[streamID] = http2streamMetadata{ - location: q, - priority: priority, - } -} - -func (ws *http2priorityWriteSchedulerRFC9218) Push(wr http2FrameWriteRequest) { - if wr.isControl() { - ws.control.push(wr) - return - } - q := ws.streams[wr.StreamID()].location - if q == nil { - // This is a closed stream. - // wr should not be a HEADERS or DATA frame. - // We push the request onto the control queue. - if wr.DataSize() > 0 { - panic("add DATA on non-open stream") - } - ws.control.push(wr) - return - } - q.push(wr) -} - -func (ws *http2priorityWriteSchedulerRFC9218) Pop() (http2FrameWriteRequest, bool) { - // Control and RST_STREAM frames first. - if !ws.control.empty() { - return ws.control.shift(), true - } - - // On the next Pop(), we want to prioritize incremental if we prioritized - // non-incremental request of the same urgency this time. Vice-versa. - // i.e. when there are incremental and non-incremental requests at the same - // priority, we give 50% of our bandwidth to the incremental ones in - // aggregate and 50% to the first non-incremental one (since - // non-incremental streams do not use round-robin writes). - ws.prioritizeIncremental = !ws.prioritizeIncremental - - // Always prioritize lowest u (i.e. highest urgency level). - for u := range ws.heads { - for i := range ws.heads[u] { - // When we want to prioritize incremental, we try to pop i=true - // first before i=false when u is the same. - if ws.prioritizeIncremental { - i = (i + 1) % 2 - } - q := ws.heads[u][i] - if q == nil { - continue - } - for { - if wr, ok := q.consume(math.MaxInt32); ok { - if i == 1 { - // For incremental streams, we update head to q.next so - // we can round-robin between multiple streams that can - // immediately benefit from partial writes. - ws.heads[u][i] = q.next - } else { - // For non-incremental streams, we try to finish one to - // completion rather than doing round-robin. However, - // we update head here so that if q.consume() is !ok - // (e.g. the stream has no more frame to consume), head - // is updated to the next q that has frames to consume - // on future iterations. This way, we do not prioritize - // writing to unavailable stream on next Pop() calls, - // preventing head-of-line blocking. - ws.heads[u][i] = q - } - return wr, true - } - q = q.next - if q == ws.heads[u][i] { - break - } - } - - } - } - return http2FrameWriteRequest{}, false -} - -// NewRandomWriteScheduler constructs a WriteScheduler that ignores HTTP/2 -// priorities. Control frames like SETTINGS and PING are written before DATA -// frames, but if no control frames are queued and multiple streams have queued -// HEADERS or DATA frames, Pop selects a ready stream arbitrarily. -func http2NewRandomWriteScheduler() http2WriteScheduler { - return &http2randomWriteScheduler{sq: make(map[uint32]*http2writeQueue)} -} - -type http2randomWriteScheduler struct { - // zero are frames not associated with a specific stream. - zero http2writeQueue - - // sq contains the stream-specific queues, keyed by stream ID. - // When a stream is idle, closed, or emptied, it's deleted - // from the map. - sq map[uint32]*http2writeQueue - - // pool of empty queues for reuse. - queuePool http2writeQueuePool -} - -func (ws *http2randomWriteScheduler) OpenStream(streamID uint32, options http2OpenStreamOptions) { - // no-op: idle streams are not tracked -} - -func (ws *http2randomWriteScheduler) CloseStream(streamID uint32) { - q, ok := ws.sq[streamID] - if !ok { - return - } - delete(ws.sq, streamID) - ws.queuePool.put(q) -} - -func (ws *http2randomWriteScheduler) AdjustStream(streamID uint32, priority http2PriorityParam) { - // no-op: priorities are ignored -} - -func (ws *http2randomWriteScheduler) Push(wr http2FrameWriteRequest) { - if wr.isControl() { - ws.zero.push(wr) - return - } - id := wr.StreamID() - q, ok := ws.sq[id] - if !ok { - q = ws.queuePool.get() - ws.sq[id] = q - } - q.push(wr) -} - -func (ws *http2randomWriteScheduler) Pop() (http2FrameWriteRequest, bool) { - // Control and RST_STREAM frames first. - if !ws.zero.empty() { - return ws.zero.shift(), true - } - // Iterate over all non-idle streams until finding one that can be consumed. - for streamID, q := range ws.sq { - if wr, ok := q.consume(math.MaxInt32); ok { - if q.empty() { - delete(ws.sq, streamID) - ws.queuePool.put(q) - } - return wr, true - } - } - return http2FrameWriteRequest{}, false -} - -type http2roundRobinWriteScheduler struct { - // control contains control frames (SETTINGS, PING, etc.). - control http2writeQueue - - // streams maps stream ID to a queue. - streams map[uint32]*http2writeQueue - - // stream queues are stored in a circular linked list. - // head is the next stream to write, or nil if there are no streams open. - head *http2writeQueue - - // pool of empty queues for reuse. - queuePool http2writeQueuePool -} - -// newRoundRobinWriteScheduler constructs a new write scheduler. -// The round robin scheduler prioritizes control frames -// like SETTINGS and PING over DATA frames. -// When there are no control frames to send, it performs a round-robin -// selection from the ready streams. -func http2newRoundRobinWriteScheduler() http2WriteScheduler { - ws := &http2roundRobinWriteScheduler{ - streams: make(map[uint32]*http2writeQueue), - } - return ws -} - -func (ws *http2roundRobinWriteScheduler) OpenStream(streamID uint32, options http2OpenStreamOptions) { - if ws.streams[streamID] != nil { - panic(fmt.Errorf("stream %d already opened", streamID)) - } - q := ws.queuePool.get() - ws.streams[streamID] = q - if ws.head == nil { - ws.head = q - q.next = q - q.prev = q - } else { - // Queues are stored in a ring. - // Insert the new stream before ws.head, putting it at the end of the list. - q.prev = ws.head.prev - q.next = ws.head - q.prev.next = q - q.next.prev = q - } -} - -func (ws *http2roundRobinWriteScheduler) CloseStream(streamID uint32) { - q := ws.streams[streamID] - if q == nil { - return - } - if q.next == q { - // This was the only open stream. - ws.head = nil - } else { - q.prev.next = q.next - q.next.prev = q.prev - if ws.head == q { - ws.head = q.next - } - } - delete(ws.streams, streamID) - ws.queuePool.put(q) -} - -func (ws *http2roundRobinWriteScheduler) AdjustStream(streamID uint32, priority http2PriorityParam) {} - -func (ws *http2roundRobinWriteScheduler) Push(wr http2FrameWriteRequest) { - if wr.isControl() { - ws.control.push(wr) - return - } - q := ws.streams[wr.StreamID()] - if q == nil { - // This is a closed stream. - // wr should not be a HEADERS or DATA frame. - // We push the request onto the control queue. - if wr.DataSize() > 0 { - panic("add DATA on non-open stream") - } - ws.control.push(wr) - return - } - q.push(wr) -} - -func (ws *http2roundRobinWriteScheduler) Pop() (http2FrameWriteRequest, bool) { - // Control and RST_STREAM frames first. - if !ws.control.empty() { - return ws.control.shift(), true - } - if ws.head == nil { - return http2FrameWriteRequest{}, false - } - q := ws.head - for { - if wr, ok := q.consume(math.MaxInt32); ok { - ws.head = q.next - return wr, true - } - q = q.next - if q == ws.head { - break - } - } - return http2FrameWriteRequest{}, false -} diff --git a/src/net/http/h2_error.go b/src/net/http/h2_error.go deleted file mode 100644 index 2c0b21ec07..0000000000 --- a/src/net/http/h2_error.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2022 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 !nethttpomithttp2 - -package http - -import ( - "reflect" -) - -func (e http2StreamError) As(target any) bool { - dst := reflect.ValueOf(target).Elem() - dstType := dst.Type() - if dstType.Kind() != reflect.Struct { - return false - } - src := reflect.ValueOf(e) - srcType := src.Type() - numField := srcType.NumField() - if dstType.NumField() != numField { - return false - } - for i := 0; i < numField; i++ { - sf := srcType.Field(i) - df := dstType.Field(i) - if sf.Name != df.Name || !sf.Type.ConvertibleTo(df.Type) { - return false - } - } - for i := 0; i < numField; i++ { - df := dst.Field(i) - df.Set(src.Field(i).Convert(df.Type())) - } - return true -} diff --git a/src/net/http/h2_error_test.go b/src/net/http/h2_error_test.go index e71825451a..1389858d79 100644 --- a/src/net/http/h2_error_test.go +++ b/src/net/http/h2_error_test.go @@ -9,6 +9,7 @@ package http import ( "errors" "fmt" + "net/http/internal/http2" "testing" ) @@ -25,7 +26,7 @@ func (e externalStreamError) Error() string { } func TestStreamError(t *testing.T) { - streamErr := http2streamError(42, http2ErrCodeProtocol) + streamErr := http2.StreamError{StreamID: 42, Code: http2.ErrCodeProtocol} extStreamErr, ok := errors.AsType[externalStreamError](streamErr) if !ok { t.Fatalf("errors.AsType failed") diff --git a/src/net/http/http.go b/src/net/http/http.go index dc4e3ba14d..407e15a1c4 100644 --- a/src/net/http/http.go +++ b/src/net/http/http.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 -import=golang.org/x/net/internal/httpcommon=net/http/internal/httpcommon -import=golang.org/x/net/internal/httpsfv=net/http/internal/httpsfv golang.org/x/net/http2 - package http import ( diff --git a/src/net/http/http2.go b/src/net/http/http2.go new file mode 100644 index 0000000000..a6f4f20f81 --- /dev/null +++ b/src/net/http/http2.go @@ -0,0 +1,340 @@ +// Copyright 2026 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 !nethttpomithttp2 + +package http + +import ( + "context" + "crypto/tls" + "errors" + "io" + "log" + "net" + "net/http/internal/http2" + "time" + + _ "unsafe" // for go:linkname +) + +// net/http supports HTTP/2 by default, but this support is removed when +// the nethttpomithttp2 build tag is set. +// +// HTTP/2 support is provided by the net/http/internal/http2 package. +// +// This file (http2.go) connects net/http to the http2 package. +// Since http imports http2, to avoid an import cycle we need to +// translate http package types (e.g., Request) into the equivalent +// http2 package types (e.g., http2.ClientRequest). +// +// The golang.org/x/net/http2 package is the original source of truth for +// the HTTP/2 implementation. At this time, users may still import that +// package and register its implementation on a net/http Transport or Server. +// However, the x/net package is no longer synchronized with std. + +func init() { + // NoBody and LocalAddrContextKey need to have the same value + // in the http and http2 packages. + // + // We can't define these values in net/http/internal, + // because their concrete types are part of the net/http API and + // moving them causes API checker failures. + // Override the http2 package versions at init time instead. + http2.LocalAddrContextKey = LocalAddrContextKey + http2.NoBody = NoBody +} + +type http2Server = http2.Server + +func (s *Server) configureHTTP2() { + h2srv := &http2.Server{} + + // Historically, we've configured the HTTP/2 idle timeout in this fashion: + // Set once at configuration time. + if s.IdleTimeout != 0 { + h2srv.IdleTimeout = s.IdleTimeout + } else { + h2srv.IdleTimeout = s.ReadTimeout + } + + if s.TLSConfig == nil { + s.TLSConfig = &tls.Config{} + } + s.nextProtoErr = h2srv.Configure(http2ServerConfig{s}, s.TLSConfig) + if s.nextProtoErr != nil { + return + } + + s.RegisterOnShutdown(h2srv.GracefulShutdown) + + if s.TLSNextProto == nil { + s.TLSNextProto = make(map[string]func(*Server, *tls.Conn, Handler)) + } + type baseContexter interface { + BaseContext() context.Context + } + s.TLSNextProto["h2"] = func(hs *Server, c *tls.Conn, h Handler) { + h2srv.ServeConn(c, &http2.ServeConnOpts{ + Context: h.(baseContexter).BaseContext(), + Handler: http2Handler{h}, + BaseConfig: http2ServerConfig{hs}, + }) + } + s.TLSNextProto[nextProtoUnencryptedHTTP2] = func(hs *Server, c *tls.Conn, h Handler) { + nc := c.NetConn().(interface { + UnencryptedNetConn() net.Conn + }).UnencryptedNetConn() + h2srv.ServeConn(nc, &http2.ServeConnOpts{ + Context: h.(baseContexter).BaseContext(), + Handler: http2Handler{h}, + BaseConfig: http2ServerConfig{hs}, + SawClientPreface: true, + }) + } + + s.h2 = h2srv +} + +func serveHTTP2Conn(ctx context.Context, c *conn, h Handler) bool { + if c.server.h2 == nil { + return false + } + c.server.h2.ServeConn(c.rwc, &http2.ServeConnOpts{ + Context: ctx, + Handler: http2Handler{h}, + BaseConfig: http2ServerConfig{c.server}, + SawClientPreface: true, + }) + return true +} + +type http2Handler struct { + h Handler +} + +func (h http2Handler) ServeHTTP(w *http2.ResponseWriter, req *http2.ServerRequest) { + h.h.ServeHTTP(http2ResponseWriter{w}, &Request{ + ctx: req.Context, + Proto: "HTTP/2.0", + ProtoMajor: 2, + ProtoMinor: 0, + Method: req.Method, + URL: req.URL, + Header: Header(req.Header), + RequestURI: req.RequestURI, + Trailer: Header(req.Trailer), + Body: req.Body, + Host: req.Host, + ContentLength: req.ContentLength, + RemoteAddr: req.RemoteAddr, + TLS: req.TLS, + MultipartForm: req.MultipartForm, + }) +} + +type http2ResponseWriter struct { + *http2.ResponseWriter +} + +// Optional http.ResponseWriter interfaces implemented. +var ( + _ CloseNotifier = http2ResponseWriter{} + _ Flusher = http2ResponseWriter{} + _ io.StringWriter = http2ResponseWriter{} +) + +func (w http2ResponseWriter) Flush() { w.ResponseWriter.FlushError() } +func (w http2ResponseWriter) FlushError() error { return w.ResponseWriter.FlushError() } + +func (w http2ResponseWriter) Header() Header { return Header(w.ResponseWriter.Header()) } + +func (w http2ResponseWriter) Push(target string, opts *PushOptions) error { + var ( + method string + header http2.Header + ) + if opts != nil { + method = opts.Method + header = http2.Header(opts.Header) + } + err := w.ResponseWriter.Push(target, method, header) + if err == http2.ErrNotSupported { + err = ErrNotSupported + } + return err +} + +type http2ServerConfig struct { + s *Server +} + +func (s http2ServerConfig) MaxHeaderBytes() int { return s.s.MaxHeaderBytes } +func (s http2ServerConfig) ConnState(c net.Conn, st http2.ConnState) { + if s.s.ConnState != nil { + s.s.ConnState(c, ConnState(st)) + } +} +func (s http2ServerConfig) DoKeepAlives() bool { return s.s.doKeepAlives() } +func (s http2ServerConfig) WriteTimeout() time.Duration { return s.s.WriteTimeout } +func (s http2ServerConfig) SendPingTimeout() time.Duration { return s.s.ReadTimeout } +func (s http2ServerConfig) ErrorLog() *log.Logger { return s.s.ErrorLog } +func (s http2ServerConfig) IdleTimeout() time.Duration { return s.s.IdleTimeout } +func (s http2ServerConfig) ReadTimeout() time.Duration { return s.s.ReadTimeout } +func (s http2ServerConfig) DisableClientPriority() bool { return s.s.DisableClientPriority } +func (s http2ServerConfig) HTTP2Config() http2.Config { + if s.s.HTTP2 == nil { + return http2.Config{} + } + return (http2.Config)(*s.s.HTTP2) +} + +func (t *Transport) configureHTTP2(protocols Protocols) { + if t.TLSClientConfig == nil { + t.TLSClientConfig = &tls.Config{} + } + if t.HTTP2 == nil { + t.HTTP2 = &HTTP2Config{} + } + t2 := http2.NewTransport(transportConfig{t}) + t2.AllowHTTP = true + t.h2transport = t2 + + t.registerProtocol("https", http2RoundTripper{t2, true}) + if t.TLSNextProto == nil { + t.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) RoundTripper) + } + t.TLSNextProto["h2"] = func(authority string, c *tls.Conn) RoundTripper { + err := t2.AddConn("https", authority, c) + if err != nil { + return http2ErringRoundTripper{err} + } + return http2RoundTripper{t2, false} + } + t.TLSNextProto[nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) RoundTripper { + unencrypted, ok := c.NetConn().(unencryptedNetConnInTLSConn) + if !ok { + return http2ErringRoundTripper{errors.New("http: *tls.Conn expected to wrap an unencrypted conn, but does not (BUG)")} + } + err := t2.AddConn("http", authority, unencrypted.conn) + if err != nil { + return http2ErringRoundTripper{err} + } + return http2RoundTripper{t2, false} + } + + // Auto-configure the http2.Transport's MaxHeaderListSize from + // the http.Transport's MaxResponseHeaderBytes. They don't + // exactly mean the same thing, but they're close. + if limit1 := t.MaxResponseHeaderBytes; limit1 != 0 && t2.MaxHeaderListSize == 0 { + const h2max = 1<<32 - 1 + if limit1 >= h2max { + t2.MaxHeaderListSize = h2max + } else { + t2.MaxHeaderListSize = uint32(limit1) + } + } + + // Server.ServeTLS clones the tls.Config before modifying it. + // Transport doesn't. We may want to make the two consistent some day. + // + // http2configureTransport will have already set NextProtos, but adjust it again + // here to remove HTTP/1.1 if the user has disabled it. + t.TLSClientConfig.NextProtos = adjustNextProtos(t.TLSClientConfig.NextProtos, protocols) +} + +type http2ErringRoundTripper struct{ err error } + +func (rt http2ErringRoundTripper) RoundTripErr() error { return rt.err } +func (rt http2ErringRoundTripper) RoundTrip(*Request) (*Response, error) { return nil, rt.err } + +func http2RoundTrip(req *Request, rt func(*http2.ClientRequest) (*http2.ClientResponse, error)) (*Response, error) { + resp := &Response{} + cresp, err := rt(&http2.ClientRequest{ + Context: req.Context(), + Method: req.Method, + URL: req.URL, + Header: http2.Header(req.Header), + Trailer: http2.Header(req.Trailer), + Body: req.Body, + Host: req.Host, + GetBody: req.GetBody, + ContentLength: req.ContentLength, + Cancel: req.Cancel, + Close: req.Close, + ResTrailer: (*http2.Header)(&resp.Trailer), + }) + if err != nil { + return nil, err + } + resp.Status = cresp.Status + " " + StatusText(cresp.StatusCode) + resp.StatusCode = cresp.StatusCode + resp.Proto = "HTTP/2.0" + resp.ProtoMajor = 2 + resp.ProtoMinor = 0 + resp.ContentLength = cresp.ContentLength + resp.Uncompressed = cresp.Uncompressed + resp.Header = Header(cresp.Header) + resp.Trailer = Header(cresp.Trailer) + resp.Body = cresp.Body + resp.TLS = cresp.TLS + resp.Request = req + return resp, nil +} + +type http2RoundTripper struct { + t *http2.Transport + mapCachedConnErr bool +} + +func (rt http2RoundTripper) RoundTrip(req *Request) (*Response, error) { + resp, err := http2RoundTrip(req, rt.t.RoundTrip) + if err != nil { + if rt.mapCachedConnErr && http2isNoCachedConnError(err) { + err = ErrSkipAltProtocol + } + return nil, err + } + return resp, nil +} + +func (rt http2RoundTripper) NewClientConn(nc net.Conn, internalStateHook func()) (RoundTripper, error) { + cc, err := rt.t.NewClientConn(nc, internalStateHook) + if err != nil { + return nil, err + } + return http2ClientConn{cc}, nil +} + +type http2ClientConn struct { + http2.NetHTTPClientConn +} + +func (cc http2ClientConn) RoundTrip(req *Request) (*Response, error) { + return http2RoundTrip(req, cc.NetHTTPClientConn.RoundTrip) +} + +type transportConfig struct { + t *Transport +} + +func (t transportConfig) MaxResponseHeaderBytes() int64 { return t.t.MaxResponseHeaderBytes } +func (t transportConfig) DisableCompression() bool { return t.t.DisableCompression } +func (t transportConfig) DisableKeepAlives() bool { return t.t.DisableKeepAlives } +func (t transportConfig) ExpectContinueTimeout() time.Duration { return t.t.ExpectContinueTimeout } +func (t transportConfig) ResponseHeaderTimeout() time.Duration { return t.t.ResponseHeaderTimeout } +func (t transportConfig) IdleConnTimeout() time.Duration { return t.t.IdleConnTimeout } + +func (t transportConfig) HTTP2Config() http2.Config { + return *(*http2.Config)(t.t.HTTP2) +} + +// transportFromH1Transport provides a way for HTTP/2 tests to extract +// the http2.Transport from an http.Transport. +// +//go:linkname transportFromH1Transport net/http/internal/http2_test.transportFromH1Transport +func transportFromH1Transport(t *Transport) any { + t.nextProtoOnce.Do(t.onceSetNextProtoDefaults) + return t.h2transport +} diff --git a/src/net/http/http_test.go b/src/net/http/http_test.go index 95b0e9c326..88ba0bc27e 100644 --- a/src/net/http/http_test.go +++ b/src/net/http/http_test.go @@ -164,7 +164,6 @@ func TestNoUnicodeStrings(t *testing.T) { } if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") || - path == "h2_bundle.go" || path == "internal/http2/ascii.go" || path == "internal/httpcommon/httpcommon.go" || d.IsDir() { diff --git a/src/net/http/internal/common.go b/src/net/http/internal/common.go new file mode 100644 index 0000000000..b32f9553c4 --- /dev/null +++ b/src/net/http/internal/common.go @@ -0,0 +1,14 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +import "errors" + +var ( + ErrAbortHandler = errors.New("net/http: abort Handler") + ErrBodyNotAllowed = errors.New("http: request method or response status code does not allow body") + ErrRequestCanceled = errors.New("net/http: request canceled") + ErrSkipAltProtocol = errors.New("net/http: skip alternate protocol") +) diff --git a/src/net/http/internal/http2/api.go b/src/net/http/internal/http2/api.go new file mode 100644 index 0000000000..33a711a278 --- /dev/null +++ b/src/net/http/internal/http2/api.go @@ -0,0 +1,158 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "context" + "crypto/tls" + "errors" + "io" + "log" + "mime/multipart" + "net" + "net/http/internal" + "net/textproto" + "net/url" + "time" +) + +// Since net/http imports the http2 package, http2 cannot use any net/http types. +// This file contains definitions which exist to to avoid introducing a dependency cycle. + +// Variables defined in net/http and initialized by an init func in that package. +// +// NoBody and LocalAddrContextKey have concrete types in net/http, +// and therefore can't be moved into a common package without introducing +// a dependency cycle. +var ( + NoBody io.ReadCloser + LocalAddrContextKey any +) + +var ( + ErrAbortHandler = internal.ErrAbortHandler + ErrBodyNotAllowed = internal.ErrBodyNotAllowed + ErrNotSupported = errors.ErrUnsupported + ErrSkipAltProtocol = internal.ErrSkipAltProtocol +) + +// A ClientRequest is a Request used by the HTTP/2 client (Transport). +type ClientRequest struct { + Context context.Context + Method string + URL *url.URL + Header Header + Trailer Header + Body io.ReadCloser + Host string + GetBody func() (io.ReadCloser, error) + ContentLength int64 + Cancel <-chan struct{} + Close bool + ResTrailer *Header + + // Include the per-request stream in the ClientRequest to avoid an allocation. + stream clientStream +} + +// Clone makes a shallow copy of ClientRequest. +// +// Clone is only used in shouldRetryRequest. +// We can drop it if we ever get rid of or rework that function. +func (req *ClientRequest) Clone() *ClientRequest { + return &ClientRequest{ + Context: req.Context, + Method: req.Method, + URL: req.URL, + Header: req.Header, + Trailer: req.Trailer, + Body: req.Body, + Host: req.Host, + GetBody: req.GetBody, + ContentLength: req.ContentLength, + Cancel: req.Cancel, + Close: req.Close, + ResTrailer: req.ResTrailer, + } +} + +// A ClientResponse is a Request used by the HTTP/2 client (Transport). +type ClientResponse struct { + Status string // e.g. "200" + StatusCode int // e.g. 200 + ContentLength int64 + Uncompressed bool + Header Header + Trailer Header + Body io.ReadCloser + TLS *tls.ConnectionState +} + +type Header = textproto.MIMEHeader + +// TransportConfig is configuration from an http.Transport. +type TransportConfig interface { + MaxResponseHeaderBytes() int64 + DisableCompression() bool + DisableKeepAlives() bool + ExpectContinueTimeout() time.Duration + ResponseHeaderTimeout() time.Duration + IdleConnTimeout() time.Duration + HTTP2Config() Config +} + +// ServerConfig is configuration from an http.Server. +type ServerConfig interface { + MaxHeaderBytes() int + ConnState(net.Conn, ConnState) + DoKeepAlives() bool + WriteTimeout() time.Duration + SendPingTimeout() time.Duration + ErrorLog() *log.Logger + ReadTimeout() time.Duration + HTTP2Config() Config + DisableClientPriority() bool +} + +type Handler interface { + ServeHTTP(*ResponseWriter, *ServerRequest) +} + +type ResponseWriter = responseWriter + +type PushOptions struct { + Method string + Header Header +} + +// A ServerRequest is a Request used by the HTTP/2 server. +type ServerRequest struct { + Context context.Context + Proto string // e.g. "HTTP/1.0" + ProtoMajor int // e.g. 1 + ProtoMinor int // e.g. 0 + Method string + URL *url.URL + Header Header + Trailer Header + Body io.ReadCloser + Host string + ContentLength int64 + RemoteAddr string + RequestURI string + TLS *tls.ConnectionState + MultipartForm *multipart.Form +} + +// ConnState is identical to net/http.ConnState. +type ConnState int + +const ( + ConnStateNew ConnState = iota + ConnStateActive + ConnStateIdle + ConnStateHijacked + ConnStateClosed +) diff --git a/src/net/http/internal/http2/client_conn_pool.go b/src/net/http/internal/http2/client_conn_pool.go index e81b73e6a7..ded7c39e77 100644 --- a/src/net/http/internal/http2/client_conn_pool.go +++ b/src/net/http/internal/http2/client_conn_pool.go @@ -10,7 +10,6 @@ import ( "context" "errors" "net" - "net/http" "sync" ) @@ -21,8 +20,8 @@ type ClientConnPool interface { // returned ClientConn accounts for the upcoming RoundTrip // call, so the caller should not omit it. If the caller needs // to, ClientConn.RoundTrip can be called with a bogus - // new(http.Request) to release the stream reservation. - GetClientConn(req *http.Request, addr string) (*ClientConn, error) + // new(ClientRequest) to release the stream reservation. + GetClientConn(req *ClientRequest, addr string) (*ClientConn, error) MarkDead(*ClientConn) } @@ -51,7 +50,7 @@ type clientConnPool struct { addConnCalls map[string]*addConnCall // in-flight addConnIfNeeded calls } -func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) { +func (p *clientConnPool) GetClientConn(req *ClientRequest, addr string) (*ClientConn, error) { return p.getClientConn(req, addr, dialOnMiss) } @@ -60,13 +59,13 @@ const ( noDialOnMiss = false ) -func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) { +func (p *clientConnPool) getClientConn(req *ClientRequest, addr string, dialOnMiss bool) (*ClientConn, error) { // TODO(dneil): Dial a new connection when t.DisableKeepAlives is set? if isConnectionCloseRequest(req) && dialOnMiss { // It gets its own connection. traceGetConn(req, addr) const singleUse = true - cc, err := p.t.dialClientConn(req.Context(), addr, singleUse) + cc, err := p.t.dialClientConn(req.Context, addr, singleUse) if err != nil { return nil, err } @@ -92,7 +91,7 @@ func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMis return nil, ErrNoCachedConn } traceGetConn(req, addr) - call := p.getStartDialLocked(req.Context(), addr) + call := p.getStartDialLocked(req.Context, addr) p.mu.Unlock() <-call.done if shouldRetryDial(call, req) { @@ -195,7 +194,7 @@ type addConnCall struct { } func (c *addConnCall) run(t *Transport, key string, nc net.Conn) { - cc, err := t.NewClientConn(nc) + cc, err := t.newClientConn(nc, t.disableKeepAlives(), nil) p := c.p p.mu.Lock() @@ -281,7 +280,7 @@ func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn { // connection instead. type noDialClientConnPool struct{ *clientConnPool } -func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) { +func (p noDialClientConnPool) GetClientConn(req *ClientRequest, addr string) (*ClientConn, error) { return p.getClientConn(req, addr, noDialOnMiss) } @@ -289,12 +288,12 @@ func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*Cl // retry dialing after the call finished unsuccessfully, for example // if the dial was canceled because of a context cancellation or // deadline expiry. -func shouldRetryDial(call *dialCall, req *http.Request) bool { +func shouldRetryDial(call *dialCall, req *ClientRequest) bool { if call.err == nil { // No error, no need to retry return false } - if call.ctx == req.Context() { + if call.ctx == req.Context { // If the call has the same context as the request, the dial // should not be retried, since any cancellation will have come // from this request. diff --git a/src/net/http/internal/http2/client_priority_go127.go b/src/net/http/internal/http2/client_priority_go127.go deleted file mode 100644 index 9e94eb642d..0000000000 --- a/src/net/http/internal/http2/client_priority_go127.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2026 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package http2 - -import "net/http" - -func clientPriorityDisabled(s *http.Server) bool { - return s.DisableClientPriority -} diff --git a/src/net/http/internal/http2/clientconn_test.go b/src/net/http/internal/http2/clientconn_test.go index 91a1ed50e3..d34a35e02a 100644 --- a/src/net/http/internal/http2/clientconn_test.go +++ b/src/net/http/internal/http2/clientconn_test.go @@ -14,6 +14,7 @@ import ( "fmt" "internal/gate" "io" + "net" "net/http" . "net/http/internal/http2" "reflect" @@ -21,6 +22,7 @@ import ( "testing" "testing/synctest" "time" + _ "unsafe" // for go:linkname "golang.org/x/net/http2/hpack" ) @@ -116,26 +118,11 @@ func newTestClientConnFromClientConn(t testing.TB, tr *Transport, cc *ClientConn cc: cc, } - // srv is the side controlled by the test. - var srv *synctestNetConn - if tconn := cc.TestNetConn(); tconn == nil { - // If cc.tconn is nil, we're being called with a new conn created by the - // Transport's client pool. This path skips dialing the server, and we - // create a test connection pair here. - var cli *synctestNetConn - cli, srv = synctestNetPipe() - cc.TestSetNetConn(cli) - } else { - // If cc.tconn is non-nil, we're in a test which provides a conn to the - // Transport via a TLSNextProto hook. Extract the test connection pair. - if tc, ok := tconn.(*tls.Conn); ok { - // Unwrap any *tls.Conn to the underlying net.Conn, - // to avoid dealing with encryption in tests. - tconn = tc.NetConn() - cc.TestSetNetConn(tconn) - } - srv = tconn.(*synctestNetConn).peer - } + // cli is the conn used by the client under test, srv is the side controlled by the test. + // We replace the conn being used by the client (possibly a *tls.Conn) with a new one, + // to avoid dealing with encryption in tests. + cli, srv := synctestNetPipe() + cc.TestSetNetConn(cli) srv.SetReadDeadline(time.Now()) tc.netconn = srv @@ -171,7 +158,8 @@ func newTestClientConn(t testing.TB, opts ...any) *testClientConn { tt := newTestTransport(t, opts...) const singleUse = false - _, err := tt.tr.TestNewClientConn(nil, singleUse, nil) + tr := transportFromH1Transport(tt.tr1).(*Transport) + _, err := tr.TestNewClientConn(nil, singleUse, nil) if err != nil { t.Fatalf("newClientConn: %v", err) } @@ -307,10 +295,48 @@ func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip { } tc.roundtrips = append(tc.roundtrips, rt) go func() { + // TODO: This duplicates too much of the net/http RoundTrip flow. + // We need to do that here because many of the http2 Transport tests + // rely on having a ClientConn to operate on. + // + // We should switch to using net/http.Transport.NewClientConn to create + // single-target client connections, and move any http2 tests which + // exercise pooling behavior into net/http. defer close(rt.donec) - rt.resp, rt.respErr = tc.cc.TestRoundTrip(req, func(streamID uint32) { - rt.id.Store(streamID) + cresp := &http.Response{} + creq := &ClientRequest{ + Context: req.Context(), + Method: req.Method, + URL: req.URL, + Header: Header(req.Header), + Trailer: Header(req.Trailer), + Body: req.Body, + Host: req.Host, + GetBody: req.GetBody, + ContentLength: req.ContentLength, + Cancel: req.Cancel, + Close: req.Close, + ResTrailer: (*Header)(&cresp.Trailer), + } + resp, err := tc.cc.TestRoundTrip(creq, func(id uint32) { + rt.id.Store(id) }) + rt.respErr = err + if resp != nil { + cresp.Status = resp.Status + " " + http.StatusText(resp.StatusCode) + cresp.StatusCode = resp.StatusCode + cresp.Proto = "HTTP/2.0" + cresp.ProtoMajor = 2 + cresp.ProtoMinor = 0 + cresp.ContentLength = resp.ContentLength + cresp.Uncompressed = resp.Uncompressed + cresp.Header = http.Header(resp.Header) + cresp.Trailer = http.Header(resp.Trailer) + cresp.Body = resp.Body + cresp.TLS = resp.TLS + cresp.Request = req + rt.resp = cresp + } }() synctest.Wait() @@ -493,8 +519,8 @@ func diffHeaders(got, want http.Header) string { // Tests that aren't specifically exercising RoundTrip's retry loop or connection pooling // should use testClientConn instead. type testTransport struct { - t testing.TB - tr *Transport + t testing.TB + tr1 *http.Transport ccs []*testClientConn } @@ -505,16 +531,33 @@ func newTestTransport(t testing.TB, opts ...any) *testTransport { t: t, } - tr := &Transport{} + tr1 := &http.Transport{ + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + // This connection will be replaced by newTestClientConnFromClientConn. + // net/http will perform a TLS handshake on it, though. + // + // TODO: We can simplify connection handling if we support + // returning a non-*tls.Conn from Transport.DialTLSContext, + // in which case we could have a DialTLSContext function that + // returns an unencrypted conn. + cli, srv := synctestNetPipe() + go func() { + tlsSrv := tls.Server(srv, testServerTLSConfig) + if err := tlsSrv.Handshake(); err != nil { + t.Errorf("unexpected TLS server handshake error: %v", err) + } + }() + return cli, nil + }, + Protocols: protocols("h2"), + TLSClientConfig: testClientTLSConfig, + } for _, o := range opts { switch o := o.(type) { case nil: case func(*http.Transport): - o(tr.TestTransport()) - case *Transport: - tr = o + o(tr1) case func(*http.HTTP2Config): - tr1 := tr.TestTransport() if tr1.HTTP2 == nil { tr1.HTTP2 = &http.HTTP2Config{} } @@ -523,10 +566,11 @@ func newTestTransport(t testing.TB, opts ...any) *testTransport { t.Fatalf("unknown newTestTransport option type %T", o) } } - tt.tr = tr + tt.tr1 = tr1 - tr.TestSetNewClientConnHook(func(cc *ClientConn) { - tc := newTestClientConnFromClientConn(t, tr, cc) + tr2 := transportFromH1Transport(tr1).(*Transport) + tr2.TestSetNewClientConnHook(func(cc *ClientConn) { + tc := newTestClientConnFromClientConn(t, tr2, cc) tt.ccs = append(tt.ccs, tc) }) @@ -552,20 +596,22 @@ func (tt *testTransport) getConn() *testClientConn { } tc := tt.ccs[0] tt.ccs = tt.ccs[1:] - synctest.Wait() tc.readClientPreface() synctest.Wait() return tc } func (tt *testTransport) roundTrip(req *http.Request) *testRoundTrip { + ctx, cancel := context.WithCancel(req.Context()) + req = req.WithContext(ctx) rt := &testRoundTrip{ - t: tt.t, - donec: make(chan struct{}), + t: tt.t, + donec: make(chan struct{}), + cancel: cancel, } go func() { defer close(rt.donec) - rt.resp, rt.respErr = tt.tr.RoundTrip(req) + rt.resp, rt.respErr = tt.tr1.RoundTrip(req) }() synctest.Wait() diff --git a/src/net/http/internal/http2/config.go b/src/net/http/internal/http2/config.go index 8a7a89d016..53dfec367f 100644 --- a/src/net/http/internal/http2/config.go +++ b/src/net/http/internal/http2/config.go @@ -6,72 +6,52 @@ package http2 import ( "math" - "net/http" "time" ) -// http2Config is a package-internal version of net/http.HTTP2Config. -// -// http.HTTP2Config was added in Go 1.24. -// When running with a version of net/http that includes HTTP2Config, -// we merge the configuration with the fields in Transport or Server -// to produce an http2Config. -// -// Zero valued fields in http2Config are interpreted as in the -// net/http.HTTPConfig documentation. -// -// Precedence order for reconciling configurations is: -// -// - Use the net/http.{Server,Transport}.HTTP2Config value, when non-zero. -// - Otherwise use the http2.{Server.Transport} value. -// - If the resulting value is zero or out of range, use a default. -type http2Config struct { - MaxConcurrentStreams uint32 - StrictMaxConcurrentRequests bool - MaxDecoderHeaderTableSize uint32 - MaxEncoderHeaderTableSize uint32 - MaxReadFrameSize uint32 - MaxUploadBufferPerConnection int32 - MaxUploadBufferPerStream int32 - SendPingTimeout time.Duration - PingTimeout time.Duration - WriteByteTimeout time.Duration - PermitProhibitedCipherSuites bool - CountError func(errType string) +// Config must be kept in sync with net/http.HTTP2Config. +type Config struct { + MaxConcurrentStreams int + StrictMaxConcurrentRequests bool + MaxDecoderHeaderTableSize int + MaxEncoderHeaderTableSize int + MaxReadFrameSize int + MaxReceiveBufferPerConnection int + MaxReceiveBufferPerStream int + SendPingTimeout time.Duration + PingTimeout time.Duration + WriteByteTimeout time.Duration + PermitProhibitedCipherSuites bool + CountError func(errType string) } -// configFromServer merges configuration settings from -// net/http.Server.HTTP2Config and http2.Server. -func configFromServer(h1 *http.Server, h2 *Server) http2Config { - conf := http2Config{ - MaxConcurrentStreams: h2.MaxConcurrentStreams, - MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, - MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, - MaxReadFrameSize: h2.MaxReadFrameSize, - MaxUploadBufferPerConnection: h2.MaxUploadBufferPerConnection, - MaxUploadBufferPerStream: h2.MaxUploadBufferPerStream, - SendPingTimeout: h2.ReadIdleTimeout, - PingTimeout: h2.PingTimeout, - WriteByteTimeout: h2.WriteByteTimeout, - PermitProhibitedCipherSuites: h2.PermitProhibitedCipherSuites, - CountError: h2.CountError, +func configFromServer(h1 ServerConfig, h2 *Server) Config { + conf := Config{ + MaxConcurrentStreams: int(h2.MaxConcurrentStreams), + MaxEncoderHeaderTableSize: int(h2.MaxEncoderHeaderTableSize), + MaxDecoderHeaderTableSize: int(h2.MaxDecoderHeaderTableSize), + MaxReadFrameSize: int(h2.MaxReadFrameSize), + MaxReceiveBufferPerConnection: int(h2.MaxUploadBufferPerConnection), + MaxReceiveBufferPerStream: int(h2.MaxUploadBufferPerStream), + SendPingTimeout: h2.ReadIdleTimeout, + PingTimeout: h2.PingTimeout, + WriteByteTimeout: h2.WriteByteTimeout, + PermitProhibitedCipherSuites: h2.PermitProhibitedCipherSuites, + CountError: h2.CountError, } - fillNetHTTPConfig(&conf, h1.HTTP2) + fillNetHTTPConfig(&conf, h1.HTTP2Config()) setConfigDefaults(&conf, true) return conf } -// configFromTransport merges configuration settings from h2 and h2.t1.HTTP2 -// (the net/http Transport). -func configFromTransport(h2 *Transport) http2Config { - conf := http2Config{ - StrictMaxConcurrentRequests: h2.StrictMaxConcurrentStreams, - MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, - MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, - MaxReadFrameSize: h2.MaxReadFrameSize, - SendPingTimeout: h2.ReadIdleTimeout, - PingTimeout: h2.PingTimeout, - WriteByteTimeout: h2.WriteByteTimeout, +func configFromTransport(h2 *Transport) Config { + conf := Config{ + MaxEncoderHeaderTableSize: int(h2.MaxEncoderHeaderTableSize), + MaxDecoderHeaderTableSize: int(h2.MaxDecoderHeaderTableSize), + MaxReadFrameSize: int(h2.MaxReadFrameSize), + SendPingTimeout: h2.ReadIdleTimeout, + PingTimeout: h2.PingTimeout, + WriteByteTimeout: h2.WriteByteTimeout, } // Unlike most config fields, where out-of-range values revert to the default, @@ -83,8 +63,9 @@ func configFromTransport(h2 *Transport) http2Config { } if h2.t1 != nil { - fillNetHTTPConfig(&conf, h2.t1.HTTP2) + fillNetHTTPConfig(&conf, h2.t1.HTTP2Config()) } + setConfigDefaults(&conf, false) return conf } @@ -95,19 +76,19 @@ func setDefault[T ~int | ~int32 | ~uint32 | ~int64](v *T, minval, maxval, defval } } -func setConfigDefaults(conf *http2Config, server bool) { - setDefault(&conf.MaxConcurrentStreams, 1, math.MaxUint32, defaultMaxStreams) - setDefault(&conf.MaxEncoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize) - setDefault(&conf.MaxDecoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize) +func setConfigDefaults(conf *Config, server bool) { + setDefault(&conf.MaxConcurrentStreams, 1, math.MaxInt32, defaultMaxStreams) + setDefault(&conf.MaxEncoderHeaderTableSize, 1, math.MaxInt32, initialHeaderTableSize) + setDefault(&conf.MaxDecoderHeaderTableSize, 1, math.MaxInt32, initialHeaderTableSize) if server { - setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, 1<<20) + setDefault(&conf.MaxReceiveBufferPerConnection, initialWindowSize, math.MaxInt32, 1<<20) } else { - setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, transportDefaultConnFlow) + setDefault(&conf.MaxReceiveBufferPerConnection, initialWindowSize, math.MaxInt32, transportDefaultConnFlow) } if server { - setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, 1<<20) + setDefault(&conf.MaxReceiveBufferPerStream, 1, math.MaxInt32, 1<<20) } else { - setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, transportDefaultStreamFlow) + setDefault(&conf.MaxReceiveBufferPerStream, 1, math.MaxInt32, transportDefaultStreamFlow) } setDefault(&conf.MaxReadFrameSize, minMaxFrameSize, maxFrameSize, defaultMaxReadFrameSize) setDefault(&conf.PingTimeout, 1, math.MaxInt64, 15*time.Second) @@ -123,33 +104,30 @@ func adjustHTTP1MaxHeaderSize(n int64) int64 { return n + typicalHeaders*perFieldOverhead } -func fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) { - if h2 == nil { - return - } +func fillNetHTTPConfig(conf *Config, h2 Config) { if h2.MaxConcurrentStreams != 0 { - conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) + conf.MaxConcurrentStreams = h2.MaxConcurrentStreams } - if http2ConfigStrictMaxConcurrentRequests(h2) { + if h2.StrictMaxConcurrentRequests { conf.StrictMaxConcurrentRequests = true } if h2.MaxEncoderHeaderTableSize != 0 { - conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize) + conf.MaxEncoderHeaderTableSize = h2.MaxEncoderHeaderTableSize } if h2.MaxDecoderHeaderTableSize != 0 { - conf.MaxDecoderHeaderTableSize = uint32(h2.MaxDecoderHeaderTableSize) + conf.MaxDecoderHeaderTableSize = h2.MaxDecoderHeaderTableSize } if h2.MaxConcurrentStreams != 0 { - conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) + conf.MaxConcurrentStreams = h2.MaxConcurrentStreams } if h2.MaxReadFrameSize != 0 { - conf.MaxReadFrameSize = uint32(h2.MaxReadFrameSize) + conf.MaxReadFrameSize = h2.MaxReadFrameSize } if h2.MaxReceiveBufferPerConnection != 0 { - conf.MaxUploadBufferPerConnection = int32(h2.MaxReceiveBufferPerConnection) + conf.MaxReceiveBufferPerConnection = h2.MaxReceiveBufferPerConnection } if h2.MaxReceiveBufferPerStream != 0 { - conf.MaxUploadBufferPerStream = int32(h2.MaxReceiveBufferPerStream) + conf.MaxReceiveBufferPerStream = h2.MaxReceiveBufferPerStream } if h2.SendPingTimeout != 0 { conf.SendPingTimeout = h2.SendPingTimeout diff --git a/src/net/http/internal/http2/config_go126.go b/src/net/http/internal/http2/config_go126.go deleted file mode 100644 index e1a9c63153..0000000000 --- a/src/net/http/internal/http2/config_go126.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2025 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package http2 - -import ( - "net/http" -) - -func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool { - return h2.StrictMaxConcurrentRequests -} diff --git a/src/net/http/internal/http2/errors.go b/src/net/http/internal/http2/errors.go index f2067dabc5..35c34b7ba5 100644 --- a/src/net/http/internal/http2/errors.go +++ b/src/net/http/internal/http2/errors.go @@ -7,6 +7,7 @@ package http2 import ( "errors" "fmt" + "reflect" ) // An ErrCode is an unsigned 32-bit error code as defined in the HTTP/2 spec. @@ -90,6 +91,33 @@ func (e StreamError) Error() string { return fmt.Sprintf("stream error: stream ID %d; %v", e.StreamID, e.Code) } +// This As function permits converting a StreamError into a x/net/http2.StreamError. +func (e StreamError) As(target any) bool { + dst := reflect.ValueOf(target).Elem() + dstType := dst.Type() + if dstType.Kind() != reflect.Struct { + return false + } + src := reflect.ValueOf(e) + srcType := src.Type() + numField := srcType.NumField() + if dstType.NumField() != numField { + return false + } + for i := 0; i < numField; i++ { + sf := srcType.Field(i) + df := dstType.Field(i) + if sf.Name != df.Name || !sf.Type.ConvertibleTo(df.Type) { + return false + } + } + for i := 0; i < numField; i++ { + df := dst.Field(i) + df.Set(src.Field(i).Convert(df.Type())) + } + return true +} + // 6.9.1 The Flow Control Window // "If a sender receives a WINDOW_UPDATE that causes a flow control // window to exceed this maximum it MUST terminate either the stream diff --git a/src/net/http/internal/http2/export_test.go b/src/net/http/internal/http2/export_test.go index f818ee8008..a9e230f5d0 100644 --- a/src/net/http/internal/http2/export_test.go +++ b/src/net/http/internal/http2/export_test.go @@ -8,7 +8,6 @@ import ( "context" "fmt" "net" - "net/http" "net/textproto" "sync" "testing" @@ -19,6 +18,10 @@ import ( "golang.org/x/net/http2/hpack" ) +func init() { + inTests = true +} + const ( DefaultMaxReadFrameSize = defaultMaxReadFrameSize DefaultMaxStreams = defaultMaxStreams @@ -68,7 +71,7 @@ func (sc *serverConn) TestFlowControlConsumed() (consumed int32) { donec := make(chan struct{}) sc.sendServeMsg(func(sc *serverConn) { defer close(donec) - initial := conf.MaxUploadBufferPerConnection + initial := int32(conf.MaxReceiveBufferPerConnection) avail := sc.inflow.avail + sc.inflow.unsent consumed = initial - avail }) @@ -117,16 +120,10 @@ func (t *Transport) TestSetNewClientConnHook(f func(*ClientConn)) { } } -func (t *Transport) TestTransport() *http.Transport { - if t.t1 == nil { - t.t1 = &http.Transport{} - } - return t.t1 -} - func (cc *ClientConn) TestNetConn() net.Conn { return cc.tconn } func (cc *ClientConn) TestSetNetConn(c net.Conn) { cc.tconn = c } -func (cc *ClientConn) TestRoundTrip(req *http.Request, f func(stremaID uint32)) (*http.Response, error) { + +func (cc *ClientConn) TestRoundTrip(req *ClientRequest, f func(stremaID uint32)) (*ClientResponse, error) { return cc.roundTrip(req, func(cs *clientStream) { f(cs.ID) }) @@ -237,6 +234,6 @@ func NewNoDialClientConnPool() ClientConnPool { return noDialClientConnPool{new(clientConnPool)} } -func EncodeRequestHeaders(req *http.Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) { +func EncodeRequestHeaders(req *ClientRequest, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) { return encodeRequestHeaders(req, addGzipHeader, peerMaxHeaderListSize, headerf) } diff --git a/src/net/http/internal/http2/http2.go b/src/net/http/internal/http2/http2.go index edf34e1f77..425e90c4f2 100644 --- a/src/net/http/internal/http2/http2.go +++ b/src/net/http/internal/http2/http2.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" "net" - "net/http" "os" "sort" "strconv" @@ -43,6 +42,8 @@ var ( // // Issue #71128. disableExtendedConnectProtocol = true + + inTests = false ) func init() { @@ -224,11 +225,6 @@ func httpCodeString(code int) string { return strconv.Itoa(code) } -// from pkg io -type stringWriter interface { - WriteString(s string) (n int, err error) -} - // A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed). type closeWaiter chan struct{} @@ -394,7 +390,7 @@ func (s *sorter) Less(i, j int) bool { return s.v[i] < s.v[j] } // // The returned slice is only valid until s used again or returned to // its pool. -func (s *sorter) Keys(h http.Header) []string { +func (s *sorter) Keys(h Header) []string { keys := s.v[:0] for k := range h { keys = append(keys, k) diff --git a/src/net/http/internal/http2/http2_test.go b/src/net/http/internal/http2/http2_test.go index 89003fd6b5..af82416046 100644 --- a/src/net/http/internal/http2/http2_test.go +++ b/src/net/http/internal/http2/http2_test.go @@ -7,7 +7,6 @@ package http2 import ( "flag" "fmt" - "net/http" "os" "path/filepath" "regexp" @@ -47,7 +46,7 @@ func TestSettingString(t *testing.T) { func TestSorterPoolAllocs(t *testing.T) { ss := []string{"a", "b", "c"} - h := http.Header{ + h := Header{ "a": nil, "b": nil, "c": nil, @@ -106,59 +105,6 @@ func equalError(a, b error) bool { return a.Error() == b.Error() } -// Tests that http2.Server.IdleTimeout is initialized from -// http.Server.{Idle,Read}Timeout. http.Server.IdleTimeout was -// added in Go 1.8. -func TestConfigureServerIdleTimeout_Go18(t *testing.T) { - const timeout = 5 * time.Second - const notThisOne = 1 * time.Second - - // With a zero http2.Server, verify that it copies IdleTimeout: - { - s1 := &http.Server{ - IdleTimeout: timeout, - ReadTimeout: notThisOne, - } - s2 := &Server{} - if err := ConfigureServer(s1, s2); err != nil { - t.Fatal(err) - } - if s2.IdleTimeout != timeout { - t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout) - } - } - - // And that it falls back to ReadTimeout: - { - s1 := &http.Server{ - ReadTimeout: timeout, - } - s2 := &Server{} - if err := ConfigureServer(s1, s2); err != nil { - t.Fatal(err) - } - if s2.IdleTimeout != timeout { - t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout) - } - } - - // Verify that s1's IdleTimeout doesn't overwrite an existing setting: - { - s1 := &http.Server{ - IdleTimeout: notThisOne, - } - s2 := &Server{ - IdleTimeout: timeout, - } - if err := ConfigureServer(s1, s2); err != nil { - t.Fatal(err) - } - if s2.IdleTimeout != timeout { - t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout) - } - } -} - var forbiddenStringsFunctions = map[string]bool{ // Functions that use Unicode-aware case folding. "EqualFold": true, diff --git a/src/net/http/internal/http2/netconn_test.go b/src/net/http/internal/http2/netconn_test.go index ef318a1abe..6cae2dd306 100644 --- a/src/net/http/internal/http2/netconn_test.go +++ b/src/net/http/internal/http2/netconn_test.go @@ -334,3 +334,36 @@ func (t *deadlineContext) setDeadline(deadline time.Time) { t.cancel = nil }) } + +type oneConnListener struct { + ch chan net.Conn + err error + once sync.Once + addr net.Addr +} + +func newOneConnListener(conn net.Conn) net.Listener { + ch := make(chan net.Conn, 1) + ch <- conn + return &oneConnListener{ch: ch} +} + +func (li *oneConnListener) Accept() (net.Conn, error) { + c := <-li.ch + if c == nil { + return nil, li.err + } + return c, nil +} + +func (li *oneConnListener) Close() error { + li.once.Do(func() { + li.err = errors.New("closed") + close(li.ch) + }) + return nil +} + +func (li *oneConnListener) Addr() net.Addr { + return li.addr +} diff --git a/src/net/http/internal/http2/server.go b/src/net/http/internal/http2/server.go index 98bec52343..a37a7280dd 100644 --- a/src/net/http/internal/http2/server.go +++ b/src/net/http/internal/http2/server.go @@ -37,7 +37,7 @@ import ( "log" "math" "net" - "net/http" + "net/http/internal" "net/http/internal/httpcommon" "net/textproto" "net/url" @@ -237,39 +237,18 @@ func (s *serverInternalState) putErrChan(ch chan error) { s.errChanPool.Put(ch) } -// ConfigureServer adds HTTP/2 support to a net/http Server. -// -// The configuration conf may be nil. -// -// ConfigureServer must be called before s begins serving. -func ConfigureServer(s *http.Server, conf *Server) error { - if s == nil { - panic("nil *http.Server") - } - if conf == nil { - conf = new(Server) - } - conf.state = &serverInternalState{ +func (s *Server) Configure(conf ServerConfig, tcfg *tls.Config) error { + s.state = &serverInternalState{ activeConns: make(map[*serverConn]struct{}), errChanPool: sync.Pool{New: func() any { return make(chan error, 1) }}, } - if h1, h2 := s, conf; h2.IdleTimeout == 0 { - if h1.IdleTimeout != 0 { - h2.IdleTimeout = h1.IdleTimeout - } else { - h2.IdleTimeout = h1.ReadTimeout - } - } - s.RegisterOnShutdown(conf.state.startGracefulShutdown) - if s.TLSConfig == nil { - s.TLSConfig = new(tls.Config) - } else if s.TLSConfig.CipherSuites != nil && s.TLSConfig.MinVersion < tls.VersionTLS13 { + if tcfg.CipherSuites != nil && tcfg.MinVersion < tls.VersionTLS13 { // If they already provided a TLS 1.0–1.2 CipherSuite list, return an // error if it is missing ECDHE_RSA_WITH_AES_128_GCM_SHA256 or // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256. haveRequired := false - for _, cs := range s.TLSConfig.CipherSuites { + for _, cs := range tcfg.CipherSuites { switch cs { case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // Alternative MTI cipher to not discourage ECDSA-only servers. @@ -290,63 +269,14 @@ func ConfigureServer(s *http.Server, conf *Server) error { // during next-proto selection, but using TLS <1.2 with // HTTP/2 is still the client's bug. - s.TLSConfig.PreferServerCipherSuites = true - - if !strSliceContains(s.TLSConfig.NextProtos, NextProtoTLS) { - s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, NextProtoTLS) - } - if !strSliceContains(s.TLSConfig.NextProtos, "http/1.1") { - s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "http/1.1") - } - - if s.TLSNextProto == nil { - s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){} - } - protoHandler := func(hs *http.Server, c net.Conn, h http.Handler, sawClientPreface bool) { - if testHookOnConn != nil { - testHookOnConn() - } - // The TLSNextProto interface predates contexts, so - // the net/http package passes down its per-connection - // base context via an exported but unadvertised - // method on the Handler. This is for internal - // net/http<=>http2 use only. - var ctx context.Context - type baseContexter interface { - BaseContext() context.Context - } - if bc, ok := h.(baseContexter); ok { - ctx = bc.BaseContext() - } - conf.ServeConn(c, &ServeConnOpts{ - Context: ctx, - Handler: h, - BaseConfig: hs, - SawClientPreface: sawClientPreface, - }) - } - s.TLSNextProto[NextProtoTLS] = func(hs *http.Server, c *tls.Conn, h http.Handler) { - protoHandler(hs, c, h, false) - } - // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns. - // - // A connection passed in this method has already had the HTTP/2 preface read from it. - s.TLSNextProto[nextProtoUnencryptedHTTP2] = func(hs *http.Server, c *tls.Conn, h http.Handler) { - nc, err := unencryptedNetConnFromTLSConn(c) - if err != nil { - if lg := hs.ErrorLog; lg != nil { - lg.Print(err) - } else { - log.Print(err) - } - go c.Close() - return - } - protoHandler(hs, nc, h, true) - } + tcfg.PreferServerCipherSuites = true return nil } +func (s *Server) GracefulShutdown() { + s.state.startGracefulShutdown() +} + // ServeConnOpts are options for the Server.ServeConn method. type ServeConnOpts struct { // Context is the base context to use. @@ -355,12 +285,12 @@ type ServeConnOpts struct { // BaseConfig optionally sets the base configuration // for values. If nil, defaults are used. - BaseConfig *http.Server + BaseConfig ServerConfig // Handler specifies which handler to use for processing // requests. If nil, BaseConfig.Handler is used. If BaseConfig // or BaseConfig.Handler is nil, http.DefaultServeMux is used. - Handler http.Handler + Handler Handler // Settings is the decoded contents of the HTTP2-Settings header // in an h2c upgrade request. @@ -378,25 +308,6 @@ func (o *ServeConnOpts) context() context.Context { return context.Background() } -func (o *ServeConnOpts) baseConfig() *http.Server { - if o != nil && o.BaseConfig != nil { - return o.BaseConfig - } - return new(http.Server) -} - -func (o *ServeConnOpts) handler() http.Handler { - if o != nil { - if o.Handler != nil { - return o.Handler - } - if o.BaseConfig != nil && o.BaseConfig.Handler != nil { - return o.BaseConfig.Handler - } - } - return http.DefaultServeMux -} - // ServeConn serves HTTP/2 requests on the provided connection and // blocks until the connection is no longer readable. // @@ -415,23 +326,36 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { if opts == nil { opts = &ServeConnOpts{} } - s.serveConn(c, opts, nil) + + var newf func(*serverConn) + if inTests { + // Fetch NewConnContextKey if set, leave newf as nil otherwise. + newf, _ = opts.Context.Value(NewConnContextKey).(func(*serverConn)) + } + + s.serveConn(c, opts, newf) } +type contextKey string + +var ( + NewConnContextKey = new("NewConnContextKey") + ConnectionStateContextKey = new("ConnectionStateContextKey") +) + func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) { baseCtx, cancel := serverConnBaseContext(c, opts) defer cancel() - http1srv := opts.baseConfig() - conf := configFromServer(http1srv, s) + conf := configFromServer(opts.BaseConfig, s) sc := &serverConn{ srv: s, - hs: http1srv, + hs: opts.BaseConfig, conn: c, baseCtx: baseCtx, remoteAddrStr: c.RemoteAddr().String(), bw: newBufferedWriter(c, conf.WriteByteTimeout), - handler: opts.handler(), + handler: opts.Handler, streams: make(map[uint32]*stream), readFrameCh: make(chan readFrameResult), wantWriteFrameCh: make(chan FrameWriteRequest, 8), @@ -440,9 +364,9 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon bodyReadCh: make(chan bodyReadMsg), // buffering doesn't matter either way doneServing: make(chan struct{}), clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value" - advMaxStreams: conf.MaxConcurrentStreams, + advMaxStreams: uint32(conf.MaxConcurrentStreams), initialStreamSendWindowSize: initialWindowSize, - initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, + initialStreamRecvWindowSize: int32(conf.MaxReceiveBufferPerStream), maxFrameSize: initialMaxFrameSize, pingTimeout: conf.PingTimeout, countErrorFunc: conf.CountError, @@ -462,14 +386,14 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon // passes the connection off to us with the deadline already set. // Write deadlines are set per stream in serverConn.newStream. // Disarm the net.Conn write deadline here. - if sc.hs.WriteTimeout > 0 { + if sc.hs.WriteTimeout() > 0 { sc.conn.SetWriteDeadline(time.Time{}) } switch { case s.NewWriteScheduler != nil: sc.writeSched = s.NewWriteScheduler() - case clientPriorityDisabled(http1srv): + case sc.hs.DisableClientPriority(): sc.writeSched = newRoundRobinWriteScheduler() default: sc.writeSched = newPriorityWriteSchedulerRFC9218() @@ -481,20 +405,29 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon sc.flow.add(initialWindowSize) sc.inflow.init(initialWindowSize) sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf) - sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) + sc.hpackEncoder.SetMaxDynamicTableSizeLimit(uint32(conf.MaxEncoderHeaderTableSize)) fr := NewFramer(sc.bw, c) if conf.CountError != nil { fr.countError = conf.CountError } - fr.ReadMetaHeaders = hpack.NewDecoder(conf.MaxDecoderHeaderTableSize, nil) + fr.ReadMetaHeaders = hpack.NewDecoder(uint32(conf.MaxDecoderHeaderTableSize), nil) fr.MaxHeaderListSize = sc.maxHeaderListSize() - fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) + fr.SetMaxReadFrameSize(uint32(conf.MaxReadFrameSize)) sc.framer = fr if tc, ok := c.(connectionStater); ok { sc.tlsState = new(tls.ConnectionState) *sc.tlsState = tc.ConnectionState() + + // Optionally override the ConnectionState in tests. + if inTests { + f, ok := opts.Context.Value(ConnectionStateContextKey).(func() tls.ConnectionState) + if ok { + *sc.tlsState = f() + } + } + // 9.2 Use of TLS Features // An implementation of HTTP/2 over TLS MUST use TLS // 1.2 or higher with the restrictions on feature set @@ -558,12 +491,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon } func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) { - ctx, cancel = context.WithCancel(opts.context()) - ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr()) - if hs := opts.baseConfig(); hs != nil { - ctx = context.WithValue(ctx, http.ServerContextKey, hs) - } - return + return context.WithCancel(opts.context()) } func (sc *serverConn) rejectConn(err ErrCode, debug string) { @@ -577,10 +505,10 @@ func (sc *serverConn) rejectConn(err ErrCode, debug string) { type serverConn struct { // Immutable: srv *Server - hs *http.Server + hs ServerConfig conn net.Conn bw *bufferedWriter // writing to conn - handler http.Handler + handler Handler baseCtx context.Context framer *Framer doneServing chan struct{} // closed when serverConn.serve ends @@ -659,10 +587,12 @@ func (sc *serverConn) writeSchedIgnoresRFC7540() bool { } } +const DefaultMaxHeaderBytes = 1 << 20 // keep this in sync with net/http + func (sc *serverConn) maxHeaderListSize() uint32 { - n := sc.hs.MaxHeaderBytes + n := sc.hs.MaxHeaderBytes() if n <= 0 { - n = http.DefaultMaxHeaderBytes + n = DefaultMaxHeaderBytes } return uint32(adjustHTTP1MaxHeaderSize(int64(n))) } @@ -701,8 +631,8 @@ type stream struct { writeDeadline *time.Timer // nil if unused closeErr error // set before cw is closed - trailer http.Header // accumulated trailers - reqTrailer http.Header // handler's Request.Trailer + trailer Header // accumulated trailers + reqTrailer Header // handler's Request.Trailer } func (sc *serverConn) Framer() *Framer { return sc.framer } @@ -739,10 +669,8 @@ func (sc *serverConn) state(streamID uint32) (streamState, *stream) { // setConnState calls the net/http ConnState hook for this connection, if configured. // Note that the net/http package does StateNew and StateClosed for us. // There is currently no plan for StateHijacked or hijacking HTTP/2 connections. -func (sc *serverConn) setConnState(state http.ConnState) { - if sc.hs.ConnState != nil { - sc.hs.ConnState(sc.conn, state) - } +func (sc *serverConn) setConnState(state ConnState) { + sc.hs.ConnState(sc.conn, state) } func (sc *serverConn) vlogf(format string, args ...interface{}) { @@ -752,7 +680,7 @@ func (sc *serverConn) vlogf(format string, args ...interface{}) { } func (sc *serverConn) logf(format string, args ...interface{}) { - if lg := sc.hs.ErrorLog; lg != nil { + if lg := sc.hs.ErrorLog(); lg != nil { lg.Printf(format, args...) } else { log.Printf(format, args...) @@ -831,7 +759,7 @@ func (sc *serverConn) canonicalHeader(v string) string { if sc.canonHeader == nil { sc.canonHeader = make(map[string]string) } - cv = http.CanonicalHeaderKey(v) + cv = textproto.CanonicalMIMEHeaderKey(v) size := 100 + len(v)*2 // 100 bytes of map overhead + key + value if sc.canonHeaderKeysSize+size <= maxCachedCanonicalHeadersKeysSize { sc.canonHeader[v] = cv @@ -925,7 +853,7 @@ func (sc *serverConn) notePanic() { } } -func (sc *serverConn) serve(conf http2Config) { +func (sc *serverConn) serve(conf Config) { sc.serveG.check() defer sc.notePanic() defer sc.conn.Close() @@ -938,10 +866,10 @@ func (sc *serverConn) serve(conf http2Config) { } settings := writeSettings{ - {SettingMaxFrameSize, conf.MaxReadFrameSize}, + {SettingMaxFrameSize, uint32(conf.MaxReadFrameSize)}, {SettingMaxConcurrentStreams, sc.advMaxStreams}, {SettingMaxHeaderListSize, sc.maxHeaderListSize()}, - {SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize}, + {SettingHeaderTableSize, uint32(conf.MaxDecoderHeaderTableSize)}, {SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)}, } if !disableExtendedConnectProtocol { @@ -957,7 +885,7 @@ func (sc *serverConn) serve(conf http2Config) { // Each connection starts with initialWindowSize inflow tokens. // If a higher value is configured, we add more tokens. - if diff := conf.MaxUploadBufferPerConnection - initialWindowSize; diff > 0 { + if diff := conf.MaxReceiveBufferPerConnection - initialWindowSize; diff > 0 { sc.sendWindowUpdate(nil, int(diff)) } @@ -969,8 +897,8 @@ func (sc *serverConn) serve(conf http2Config) { // "StateNew" state. We can't go directly to idle, though. // Active means we read some data and anticipate a request. We'll // do another Active when we get a HEADERS frame. - sc.setConnState(http.StateActive) - sc.setConnState(http.StateIdle) + sc.setConnState(ConnStateActive) + sc.setConnState(ConnStateIdle) if sc.srv.IdleTimeout > 0 { sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer) @@ -1737,7 +1665,7 @@ func (sc *serverConn) closeStream(st *stream, err error) { } delete(sc.streams, st.id) if len(sc.streams) == 0 { - sc.setConnState(http.StateIdle) + sc.setConnState(ConnStateIdle) if sc.srv.IdleTimeout > 0 && sc.idleTimer != nil { sc.idleTimer.Reset(sc.srv.IdleTimeout) } @@ -2126,7 +2054,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { } st.reqTrailer = req.Trailer if st.reqTrailer != nil { - st.trailer = make(http.Header) + st.trailer = make(Header) } st.body = req.Body.(*requestBody).pipe // may be nil st.declBodyBytes = req.ContentLength @@ -2136,7 +2064,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { // Their header list was too long. Send a 431 error. handler = handleHeaderListTooLong } else if err := checkValidHTTP2RequestHeaders(req.Header); err != nil { - handler = new400Handler(err) + handler = serve400Handler{err}.ServeHTTP } // The net/http package sets the read deadline from the @@ -2146,9 +2074,9 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { // similar to how the http1 server works. Here it's // technically more like the http1 Server's ReadHeaderTimeout // (in Go 1.8), though. That's a more sane option anyway. - if sc.hs.ReadTimeout > 0 { + if sc.hs.ReadTimeout() > 0 { sc.conn.SetReadDeadline(time.Time{}) - st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) + st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout(), st.onReadTimeout) } return sc.scheduleHandler(id, rw, req, handler) @@ -2242,8 +2170,8 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState, priority st.flow.conn = &sc.flow // link to conn-level counter st.flow.add(sc.initialStreamSendWindowSize) st.inflow.init(sc.initialStreamRecvWindowSize) - if sc.hs.WriteTimeout > 0 { - st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) + if writeTimeout := sc.hs.WriteTimeout(); writeTimeout > 0 { + st.writeDeadline = time.AfterFunc(writeTimeout, st.onWriteTimeout) } sc.streams[id] = st @@ -2254,13 +2182,13 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState, priority sc.curClientStreams++ } if sc.curOpenStreams() == 1 { - sc.setConnState(http.StateActive) + sc.setConnState(ConnStateActive) } return st } -func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*responseWriter, *http.Request, error) { +func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*responseWriter, *ServerRequest, error) { sc.serveG.check() rp := httpcommon.ServerRequestParam{ @@ -2295,7 +2223,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol)) } - header := make(http.Header) + header := make(Header) rp.Header = header for _, hf := range f.RegularFields() { header.Add(sc.canonicalHeader(hf.Name), hf.Value) @@ -2329,7 +2257,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res return rw, req, nil } -func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.ServerRequestParam) (*responseWriter, *http.Request, error) { +func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.ServerRequestParam) (*responseWriter, *ServerRequest, error) { sc.serveG.check() var tlsState *tls.ConnectionState // nil if not scheme https @@ -2347,7 +2275,9 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server stream: st, needsContinue: res.NeedsContinue, } - req := (&http.Request{ + rw := sc.newResponseWriter(st) + rw.rws.req = ServerRequest{ + Context: st.ctx, Method: rp.Method, URL: res.URL, RemoteAddr: sc.remoteAddrStr, @@ -2360,12 +2290,11 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server Host: rp.Authority, Body: body, Trailer: res.Trailer, - }).WithContext(st.ctx) - rw := sc.newResponseWriter(st, req) - return rw, req, nil + } + return rw, &rw.rws.req, nil } -func (sc *serverConn) newResponseWriter(st *stream, req *http.Request) *responseWriter { +func (sc *serverConn) newResponseWriter(st *stream) *responseWriter { rws := responseWriterStatePool.Get().(*responseWriterState) bwSave := rws.bw *rws = responseWriterState{} // zero all the fields @@ -2373,20 +2302,19 @@ func (sc *serverConn) newResponseWriter(st *stream, req *http.Request) *response rws.bw = bwSave rws.bw.Reset(chunkWriter{rws}) rws.stream = st - rws.req = req return &responseWriter{rws: rws} } type unstartedHandler struct { streamID uint32 rw *responseWriter - req *http.Request - handler func(http.ResponseWriter, *http.Request) + req *ServerRequest + handler func(*ResponseWriter, *ServerRequest) } // scheduleHandler starts a handler goroutine, // or schedules one to start as soon as an existing handler finishes. -func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { +func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *ServerRequest, handler func(*ResponseWriter, *ServerRequest)) error { sc.serveG.check() maxHandlers := sc.advMaxStreams if sc.curHandlers < maxHandlers { @@ -2431,7 +2359,7 @@ func (sc *serverConn) handlerDone() { } // Run on its own goroutine. -func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) { +func (sc *serverConn) runHandler(rw *responseWriter, req *ServerRequest, handler func(*ResponseWriter, *ServerRequest)) { defer sc.sendServeMsg(handlerDoneMsg) didPanic := true defer func() { @@ -2446,7 +2374,7 @@ func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler stream: rw.rws.stream, }) // Same as net/http: - if e != nil && e != http.ErrAbortHandler { + if e != nil && e != ErrAbortHandler { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] @@ -2460,7 +2388,7 @@ func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler didPanic = false } -func handleHeaderListTooLong(w http.ResponseWriter, r *http.Request) { +func handleHeaderListTooLong(w *ResponseWriter, r *ServerRequest) { // 10.5.1 Limits on Header Block Size: // .. "A server that receives a larger header block than it is // willing to handle can send an HTTP 431 (Request Header Fields Too @@ -2616,30 +2544,23 @@ type responseWriter struct { rws *responseWriterState } -// Optional http.ResponseWriter interfaces implemented. -var ( - _ http.CloseNotifier = (*responseWriter)(nil) - _ http.Flusher = (*responseWriter)(nil) - _ stringWriter = (*responseWriter)(nil) -) - type responseWriterState struct { // immutable within a request: stream *stream - req *http.Request + req ServerRequest conn *serverConn // TODO: adjust buffer writing sizes based on server config, frame size updates from peer, etc bw *bufio.Writer // writing to a chunkWriter{this *responseWriterState} // mutated by http.Handler goroutine: - handlerHeader http.Header // nil until called - snapHeader http.Header // snapshot of handlerHeader at WriteHeader time - trailers []string // set in writeChunk - status int // status code passed to WriteHeader - wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet. - sentHeader bool // have we sent the header frame? - handlerDone bool // handler has finished + handlerHeader Header // nil until called + snapHeader Header // snapshot of handlerHeader at WriteHeader time + trailers []string // set in writeChunk + status int // status code passed to WriteHeader + wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet. + sentHeader bool // have we sent the header frame? + handlerDone bool // handler has finished sentContentLen int64 // non-zero if handler set a Content-Length header wroteBytes int64 @@ -2675,7 +2596,7 @@ func (rws *responseWriterState) hasNonemptyTrailers() bool { // response header is written. It notes that a header will need to be // written in the trailers at the end of the response. func (rws *responseWriterState) declareTrailer(k string) { - k = http.CanonicalHeaderKey(k) + k = textproto.CanonicalMIMEHeaderKey(k) if !httpguts.ValidTrailerHeader(k) { // Forbidden by RFC 7230, section 4.1.2. rws.conn.logf("ignoring invalid trailer %q", k) @@ -2686,6 +2607,8 @@ func (rws *responseWriterState) declareTrailer(k string) { } } +const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" // keep in sync with net/http + // writeChunk writes chunks from the bufio.Writer. But because // bufio.Writer may bypass its chunking, sometimes p may be // arbitrarily large. @@ -2723,12 +2646,12 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) { ce := rws.snapHeader.Get("Content-Encoding") hasCE := len(ce) > 0 if !hasCE && !hasContentType && bodyAllowedForStatus(rws.status) && len(p) > 0 { - ctype = http.DetectContentType(p) + ctype = internal.DetectContentType(p) } var date string if _, ok := rws.snapHeader["Date"]; !ok { // TODO(bradfitz): be faster here, like net/http? measure. - date = time.Now().UTC().Format(http.TimeFormat) + date = time.Now().UTC().Format(TimeFormat) } for _, v := range rws.snapHeader["Trailer"] { @@ -2838,7 +2761,7 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() { } trailerKey := strings.TrimPrefix(k, TrailerPrefix) rws.declareTrailer(trailerKey) - rws.handlerHeader[http.CanonicalHeaderKey(trailerKey)] = vv + rws.handlerHeader[textproto.CanonicalMIMEHeaderKey(trailerKey)] = vv } if len(rws.trailers) > 1 { @@ -2954,13 +2877,13 @@ func (w *responseWriter) CloseNotify() <-chan bool { return ch } -func (w *responseWriter) Header() http.Header { +func (w *responseWriter) Header() Header { rws := w.rws if rws == nil { panic("Header called after Handler finished") } if rws.handlerHeader == nil { - rws.handlerHeader = make(http.Header) + rws.handlerHeader = make(Header) } return rws.handlerHeader } @@ -3005,7 +2928,7 @@ func (rws *responseWriterState) writeHeader(code int) { _, cl := h["Content-Length"] _, te := h["Transfer-Encoding"] if cl || te { - h = h.Clone() + h = cloneHeader(h) h.Del("Content-Length") h.Del("Transfer-Encoding") } @@ -3027,8 +2950,8 @@ func (rws *responseWriterState) writeHeader(code int) { } } -func cloneHeader(h http.Header) http.Header { - h2 := make(http.Header, len(h)) +func cloneHeader(h Header) Header { + h2 := make(Header, len(h)) for k, vv := range h { vv2 := make([]string, len(vv)) copy(vv2, vv) @@ -3063,7 +2986,7 @@ func (w *responseWriter) write(lenData int, dataB []byte, dataS string) (n int, w.WriteHeader(200) } if !bodyAllowedForStatus(rws.status) { - return 0, http.ErrBodyNotAllowed + return 0, ErrBodyNotAllowed } rws.wroteBytes += int64(len(dataB)) + int64(len(dataS)) // only one can be set if rws.sentContentLen != 0 && rws.wroteBytes > rws.sentContentLen { @@ -3092,9 +3015,7 @@ var ( ErrPushLimitReached = errors.New("http2: push would exceed peer's SETTINGS_MAX_CONCURRENT_STREAMS") ) -var _ http.Pusher = (*responseWriter)(nil) - -func (w *responseWriter) Push(target string, opts *http.PushOptions) error { +func (w *responseWriter) Push(target, method string, header Header) error { st := w.rws.stream sc := st.sc sc.serveG.checkNotOn() @@ -3105,16 +3026,12 @@ func (w *responseWriter) Push(target string, opts *http.PushOptions) error { return ErrRecursivePush } - if opts == nil { - opts = new(http.PushOptions) - } - // Default options. - if opts.Method == "" { - opts.Method = "GET" + if method == "" { + method = "GET" } - if opts.Header == nil { - opts.Header = http.Header{} + if header == nil { + header = Header{} } wantScheme := "http" if w.rws.req.TLS != nil { @@ -3140,7 +3057,7 @@ func (w *responseWriter) Push(target string, opts *http.PushOptions) error { return errors.New("URL must have a host") } } - for k := range opts.Header { + for k := range header { if strings.HasPrefix(k, ":") { return fmt.Errorf("promised request headers cannot include pseudo header %q", k) } @@ -3157,22 +3074,22 @@ func (w *responseWriter) Push(target string, opts *http.PushOptions) error { return fmt.Errorf("promised request headers cannot include %q", k) } } - if err := checkValidHTTP2RequestHeaders(opts.Header); err != nil { + if err := checkValidHTTP2RequestHeaders(header); err != nil { return err } // The RFC effectively limits promised requests to GET and HEAD: // "Promised requests MUST be cacheable [GET, HEAD, or POST], and MUST be safe [GET or HEAD]" // http://tools.ietf.org/html/rfc7540#section-8.2 - if opts.Method != "GET" && opts.Method != "HEAD" { - return fmt.Errorf("method %q must be GET or HEAD", opts.Method) + if method != "GET" && method != "HEAD" { + return fmt.Errorf("method %q must be GET or HEAD", method) } msg := &startPushRequest{ parent: st, - method: opts.Method, + method: method, url: u, - header: cloneHeader(opts.Header), + header: cloneHeader(header), done: sc.srv.state.getErrChan(), } @@ -3199,7 +3116,7 @@ type startPushRequest struct { parent *stream method string url *url.URL - header http.Header + header Header done chan error } @@ -3217,7 +3134,7 @@ func (sc *serverConn) startPush(msg *startPushRequest) { // http://tools.ietf.org/html/rfc7540#section-6.6. if !sc.pushEnabled { - msg.done <- http.ErrNotSupported + msg.done <- ErrNotSupported return } @@ -3230,7 +3147,7 @@ func (sc *serverConn) startPush(msg *startPushRequest) { // Check this again, just in case. Technically, we might have received // an updated SETTINGS by the time we got around to writing this frame. if !sc.pushEnabled { - return 0, http.ErrNotSupported + return 0, ErrNotSupported } // http://tools.ietf.org/html/rfc7540#section-6.5.2. if sc.curPushedStreams+1 > sc.clientMaxStreams { @@ -3314,7 +3231,7 @@ var connHeaders = []string{ // checkValidHTTP2RequestHeaders checks whether h is a valid HTTP/2 request, // per RFC 7540 Section 8.1.2.2. // The returned error is reported to users. -func checkValidHTTP2RequestHeaders(h http.Header) error { +func checkValidHTTP2RequestHeaders(h Header) error { for _, k := range connHeaders { if _, ok := h[k]; ok { return fmt.Errorf("request header %q is not valid in HTTP/2", k) @@ -3327,24 +3244,27 @@ func checkValidHTTP2RequestHeaders(h http.Header) error { return nil } -func new400Handler(err error) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - http.Error(w, err.Error(), http.StatusBadRequest) - } +type serve400Handler struct { + err error +} + +func (handler serve400Handler) ServeHTTP(w *ResponseWriter, r *ServerRequest) { + const statusBadRequest = 400 + + // TODO: Dedup with http.Error? + h := w.Header() + h.Del("Content-Length") + h.Set("Content-Type", "text/plain; charset=utf-8") + h.Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(statusBadRequest) + fmt.Fprintln(w, handler.err.Error()) } // h1ServerKeepAlivesDisabled reports whether hs has its keep-alives // disabled. See comments on h1ServerShutdownChan above for why // the code is written this way. -func h1ServerKeepAlivesDisabled(hs *http.Server) bool { - var x interface{} = hs - type I interface { - doKeepAlives() bool - } - if hs, ok := x.(I); ok { - return !hs.doKeepAlives() - } - return false +func h1ServerKeepAlivesDisabled(hs ServerConfig) bool { + return !hs.DoKeepAlives() } func (sc *serverConn) countError(name string, err error) error { diff --git a/src/net/http/internal/http2/server_internal_test.go b/src/net/http/internal/http2/server_internal_test.go index 763976e159..379b5de46d 100644 --- a/src/net/http/internal/http2/server_internal_test.go +++ b/src/net/http/internal/http2/server_internal_test.go @@ -7,42 +7,41 @@ package http2 import ( "errors" "fmt" - "net/http" "strings" "testing" ) func TestCheckValidHTTP2Request(t *testing.T) { tests := []struct { - h http.Header + h Header want error }{ { - h: http.Header{"Te": {"trailers"}}, + h: Header{"Te": {"trailers"}}, want: nil, }, { - h: http.Header{"Te": {"trailers", "bogus"}}, + h: Header{"Te": {"trailers", "bogus"}}, want: errors.New(`request header "TE" may only be "trailers" in HTTP/2`), }, { - h: http.Header{"Foo": {""}}, + h: Header{"Foo": {""}}, want: nil, }, { - h: http.Header{"Connection": {""}}, + h: Header{"Connection": {""}}, want: errors.New(`request header "Connection" is not valid in HTTP/2`), }, { - h: http.Header{"Proxy-Connection": {""}}, + h: Header{"Proxy-Connection": {""}}, want: errors.New(`request header "Proxy-Connection" is not valid in HTTP/2`), }, { - h: http.Header{"Keep-Alive": {""}}, + h: Header{"Keep-Alive": {""}}, want: errors.New(`request header "Keep-Alive" is not valid in HTTP/2`), }, { - h: http.Header{"Upgrade": {""}}, + h: Header{"Upgrade": {""}}, want: errors.New(`request header "Upgrade" is not valid in HTTP/2`), }, } diff --git a/src/net/http/internal/http2/server_test.go b/src/net/http/internal/http2/server_test.go index c793cf8444..11edfcb3be 100644 --- a/src/net/http/internal/http2/server_test.go +++ b/src/net/http/internal/http2/server_test.go @@ -30,7 +30,9 @@ import ( "testing" "testing/synctest" "time" + _ "unsafe" // for go:linkname + "net/http/internal/http2" . "net/http/internal/http2" "net/http/internal/testcert" @@ -79,6 +81,7 @@ type serverTester struct { logFilter []string // substrings to filter out scMu sync.Mutex // guards sc sc *ServerConn + wrotePreface bool testConnFramer callsMu sync.Mutex @@ -125,7 +128,6 @@ func newTestServer(t testing.TB, handler http.HandlerFunc, opts ...interface{}) ts.EnableHTTP2 = true ts.Config.ErrorLog = log.New(twriter{t: t}, "", log.LstdFlags) ts.Config.Protocols = protocols("h2") - h2server := new(Server) for _, opt := range opts { switch v := opt.(type) { case func(*httptest.Server): @@ -141,7 +143,6 @@ func newTestServer(t testing.TB, handler http.HandlerFunc, opts ...interface{}) t.Fatalf("unknown newTestServer option type %T", v) } } - ConfigureServer(ts.Config, h2server) if ts.Config.Protocols.HTTP2() { ts.TLS = testServerTLSConfig @@ -176,12 +177,7 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} t.Helper() h1server := &http.Server{} - h2server := &Server{} - tlsState := tls.ConnectionState{ - Version: tls.VersionTLS13, - ServerName: "go.dev", - CipherSuite: tls.TLS_AES_128_GCM_SHA256, - } + var tlsState *tls.ConnectionState for _, opt := range opts { switch v := opt.(type) { case func(*http.Server): @@ -192,21 +188,51 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} } v(h1server.HTTP2) case func(*tls.ConnectionState): - v(&tlsState) + if tlsState == nil { + tlsState = &tls.ConnectionState{ + Version: tls.VersionTLS13, + ServerName: "go.dev", + CipherSuite: tls.TLS_AES_128_GCM_SHA256, + } + } + v(tlsState) default: t.Fatalf("unknown newServerTester option type %T", v) } } - ConfigureServer(h1server, h2server) - cli, srv := synctestNetPipe() - cli.SetReadDeadline(time.Now()) + tlsConfig := h1server.TLSConfig + if tlsConfig == nil { + cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey) + if err != nil { + t.Fatal(err) + } + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: true, + NextProtos: []string{"h2"}, + } + h1server.TLSConfig = tlsConfig + } + + var cli, srv net.Conn + + cliPipe, srvPipe := synctestNetPipe() + + if h1server.Protocols != nil && h1server.Protocols.UnencryptedHTTP2() { + cli, srv = cliPipe, srvPipe + } else { + cli = tls.Client(cliPipe, &tls.Config{ + InsecureSkipVerify: true, + NextProtos: []string{"h2"}, + }) + srv = tls.Server(srvPipe, tlsConfig) + } st := &serverTester{ t: t, cc: cli, h1server: h1server, - h2server: h2server, } st.hpackEnc = hpack.NewEncoder(&st.headerBuf) if h1server.ErrorLog == nil { @@ -224,23 +250,42 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{} }) connc := make(chan *ServerConn) - go func() { - h2server.TestServeConn(&netConnWithConnectionState{ - Conn: srv, - state: tlsState, - }, &ServeConnOpts{ - Handler: handler, - BaseConfig: h1server, - }, func(sc *ServerConn) { + h1server.ConnContext = func(ctx context.Context, conn net.Conn) context.Context { + ctx = context.WithValue(ctx, NewConnContextKey, func(sc *ServerConn) { connc <- sc }) + if tlsState != nil { + ctx = context.WithValue(ctx, ConnectionStateContextKey, func() tls.ConnectionState { + return *tlsState + }) + } + return ctx + } + go func() { + li := newOneConnListener(srv) + t.Cleanup(func() { + li.Close() + }) + h1server.Serve(li) }() + if cliTLS, ok := cli.(*tls.Conn); ok { + if err := cliTLS.Handshake(); err != nil { + t.Fatalf("client TLS handshake: %v", err) + } + cliTLS.SetReadDeadline(time.Now()) + } else { + // Confusing but difficult to fix: Preface must be written + // before the conn appears on connc. + st.writePreface() + st.wrotePreface = true + cliPipe.SetReadDeadline(time.Now()) + } st.sc = <-connc st.fr = NewFramer(st.cc, st.cc) st.testConnFramer = testConnFramer{ t: t, - fr: NewFramer(st.cc, st.cc), + fr: NewFramer(cli, cli), dec: hpack.NewDecoder(InitialHeaderTableSize, nil), } synctest.Wait() @@ -256,6 +301,10 @@ func (c *netConnWithConnectionState) ConnectionState() tls.ConnectionState { return c.state } +func (c *netConnWithConnectionState) HandshakeContext() tls.ConnectionState { + return c.state +} + type serverTesterHandler struct { st *serverTester } @@ -337,8 +386,6 @@ func newServerTesterWithRealConn(t testing.TB, handler http.HandlerFunc, opts .. } } - 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 @@ -514,6 +561,9 @@ func (st *serverTester) greetAndCheckSettings(checkSetting func(s Setting) error } func (st *serverTester) writePreface() { + if st.wrotePreface { + return + } n, err := st.cc.Write([]byte(ClientPreface)) if err != nil { st.t.Fatalf("Error writing client preface: %v", err) @@ -1256,7 +1306,7 @@ func testServer_MaxQueuedControlFrames(t testing.TB) { st := newServerTester(t, nil) st.greet() - st.cc.(*synctestNetConn).SetReadBufferSize(0) // all writes block + st.cc.(*tls.Conn).NetConn().(*synctestNetConn).SetReadBufferSize(0) // all writes block // Send maxQueuedControlFrames pings, plus a few extra // to account for ones that enter the server's write buffer. @@ -1269,7 +1319,7 @@ func testServer_MaxQueuedControlFrames(t testing.TB) { // Unblock the server. // It should have closed the connection after exceeding the control frame limit. - st.cc.(*synctestNetConn).SetReadBufferSize(math.MaxInt) + st.cc.(*tls.Conn).NetConn().(*synctestNetConn).SetReadBufferSize(math.MaxInt) st.advance(GoAwayTimeout) // Some frames may have persisted in the server's buffers. @@ -3270,8 +3320,6 @@ func benchmarkServerToClientStream(b *testing.B, newServerOpts ...interface{}) { }) } -// 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" + @@ -3279,6 +3327,7 @@ func testIssue53(t testing.TB) { st := newServerTester(t, func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("hello")) }) + st.cc.Write([]byte(data)) st.wantFrameType(FrameSettings) st.wantFrameType(FrameWindowUpdate) @@ -3287,18 +3336,13 @@ func testIssue53(t testing.TB) { st.wantClosed() } -// golang.org/issue/12895 -func TestConfigureServer(t *testing.T) { synctestTest(t, testConfigureServer) } -func testConfigureServer(t testing.TB) { +func TestServerServeNoBannedCiphers(t *testing.T) { tests := []struct { name string tlsConfig *tls.Config wantErr string }{ { - name: "empty server", - }, - { name: "empty CipherSuites", tlsConfig: &tls.Config{}, }, @@ -3342,9 +3386,15 @@ func testConfigureServer(t testing.TB) { }, } for _, tt := range tests { - srv := &http.Server{TLSConfig: tt.tlsConfig} - err := ConfigureServer(srv, nil) - if (err != nil) != (tt.wantErr != "") { + tt.tlsConfig.Certificates = testServerTLSConfig.Certificates + + srv := &http.Server{ + TLSConfig: tt.tlsConfig, + Protocols: protocols("h2"), + } + + err := srv.ServeTLS(errListener{}, "", "") + if (err != net.ErrClosed) != (tt.wantErr != "") { if tt.wantErr != "" { t.Errorf("%s: success, but want error", tt.name) } else { @@ -3360,6 +3410,12 @@ func testConfigureServer(t testing.TB) { } } +type errListener struct{} + +func (li errListener) Accept() (net.Conn, error) { return nil, net.ErrClosed } +func (li errListener) Close() error { return nil } +func (li errListener) Addr() net.Addr { return nil } + func TestServerNoAutoContentLengthOnHead(t *testing.T) { synctestTest(t, testServerNoAutoContentLengthOnHead) } @@ -4024,7 +4080,12 @@ func testServerGracefulShutdown(t testing.TB) { st.bodylessReq1() st.sync() - st.h1server.Shutdown(context.Background()) + + shutdownc := make(chan struct{}) + go func() { + defer close(shutdownc) + st.h1server.Shutdown(context.Background()) + }() st.wantGoAway(1, ErrCodeNo) @@ -4045,6 +4106,9 @@ func testServerGracefulShutdown(t testing.TB) { if n != 0 || err == nil { t.Errorf("Read = %v, %v; want 0, non-nil", n, err) } + + // Shutdown happens after GoAwayTimeout and net/http.Server polling delay. + <-shutdownc } // Issue 31753: don't sniff when Content-Encoding is set @@ -5171,6 +5235,15 @@ func testServerRFC9218PriorityAware(t testing.TB) { } } +func TestConsistentConstants(t *testing.T) { + if h1, h2 := http.DefaultMaxHeaderBytes, http2.DefaultMaxHeaderBytes; h1 != h2 { + t.Errorf("DefaultMaxHeaderBytes: http (%v) != http2 (%v)", h1, h2) + } + if h1, h2 := http.TimeFormat, http2.TimeFormat; h1 != h2 { + t.Errorf("TimeFormat: http (%v) != http2 (%v)", h1, h2) + } +} + var ( testServerTLSConfig *tls.Config testClientTLSConfig *tls.Config @@ -5215,3 +5288,6 @@ func protocols(protos ...string) *http.Protocols { } return p } + +//go:linkname transportFromH1Transport +func transportFromH1Transport(tr *http.Transport) any diff --git a/src/net/http/internal/http2/transport.go b/src/net/http/internal/http2/transport.go index 4fd5070701..6caba5046a 100644 --- a/src/net/http/internal/http2/transport.go +++ b/src/net/http/internal/http2/transport.go @@ -23,10 +23,11 @@ import ( "math/bits" mathrand "math/rand" "net" - "net/http" "net/http/httptrace" + "net/http/internal" "net/http/internal/httpcommon" "net/textproto" + "slices" "strconv" "strings" "sync" @@ -177,10 +178,7 @@ type Transport struct { // The errType consists of only ASCII word characters. CountError func(errType string) - // t1, if non-nil, is the standard library Transport using - // this transport. Its settings are used (but not its - // RoundTrip method, etc). - t1 *http.Transport + t1 TransportConfig connPoolOnce sync.Once connPoolOrDef ClientConnPool // non-nil version of ConnPool @@ -197,12 +195,9 @@ type transportTestHooks struct { } func (t *Transport) maxHeaderListSize() uint32 { - n := int64(t.MaxHeaderListSize) - if t.t1 != nil && t.t1.MaxResponseHeaderBytes != 0 { - n = t.t1.MaxResponseHeaderBytes - if n > 0 { - n = adjustHTTP1MaxHeaderSize(n) - } + n := t.t1.MaxResponseHeaderBytes() + if n > 0 { + n = adjustHTTP1MaxHeaderSize(n) } if n <= 0 { return 10 << 20 @@ -214,84 +209,38 @@ func (t *Transport) maxHeaderListSize() uint32 { } func (t *Transport) disableCompression() bool { - return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression) -} - -// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2. -// It returns an error if t1 has already been HTTP/2-enabled. -// -// Use ConfigureTransports instead to configure the HTTP/2 Transport. -func ConfigureTransport(t1 *http.Transport) error { - _, err := ConfigureTransports(t1) - return err -} - -// ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2. -// It returns a new HTTP/2 Transport for further configuration. -// It returns an error if t1 has already been HTTP/2-enabled. -func ConfigureTransports(t1 *http.Transport) (*Transport, error) { - return configureTransports(t1) + return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression()) } -func configureTransports(t1 *http.Transport) (*Transport, error) { +func NewTransport(t1 TransportConfig) *Transport { connPool := new(clientConnPool) t2 := &Transport{ ConnPool: noDialClientConnPool{connPool}, t1: t1, } connPool.t = t2 - if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil { - return nil, err - } - if t1.TLSClientConfig == nil { - t1.TLSClientConfig = new(tls.Config) - } - if !strSliceContains(t1.TLSClientConfig.NextProtos, "h2") { - t1.TLSClientConfig.NextProtos = append([]string{"h2"}, t1.TLSClientConfig.NextProtos...) - } - if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") { - t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1") - } - upgradeFn := func(scheme, authority string, c net.Conn) http.RoundTripper { - addr := authorityAddr(scheme, authority) - if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil { - go c.Close() - return erringRoundTripper{err} - } else if !used { - // Turns out we don't need this c. - // For example, two goroutines made requests to the same host - // at the same time, both kicking off TCP dials. (since protocol - // was unknown) - go c.Close() - } - if scheme == "http" { - return (*unencryptedTransport)(t2) - } - return t2 - } - if t1.TLSNextProto == nil { - t1.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper) - } - t1.TLSNextProto[NextProtoTLS] = func(authority string, c *tls.Conn) http.RoundTripper { - return upgradeFn("https", authority, c) + return t2 +} + +func (t *Transport) AddConn(scheme, authority string, c net.Conn) error { + connPool, ok := t.ConnPool.(noDialClientConnPool) + if !ok { + go c.Close() + return nil } - // The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns. - t1.TLSNextProto[nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) http.RoundTripper { - nc, err := unencryptedNetConnFromTLSConn(c) - if err != nil { - go c.Close() - return erringRoundTripper{err} - } - return upgradeFn("http", authority, nc) + addr := authorityAddr(scheme, authority) + used, err := connPool.addConnIfNeeded(addr, t, c) + if !used { + go c.Close() } - return t2, nil + return err } // unencryptedTransport is a Transport with a RoundTrip method that // always permits http:// URLs. type unencryptedTransport Transport -func (t *unencryptedTransport) RoundTrip(req *http.Request) (*http.Response, error) { +func (t *unencryptedTransport) RoundTrip(req *ClientRequest) (*ClientResponse, error) { return (*Transport)(t).RoundTripOpt(req, RoundTripOpt{allowHTTP: true}) } @@ -428,8 +377,8 @@ type clientStream struct { donec chan struct{} // closed after the stream is in the closed state on100 chan struct{} // buffered; written to if a 100 is received - respHeaderRecv chan struct{} // closed when headers are received - res *http.Response // set if respHeaderRecv is closed + respHeaderRecv chan struct{} // closed when headers are received + res *ClientResponse // set if respHeaderRecv is closed flow outflow // guarded by cc.mu inflow inflow // guarded by cc.mu @@ -452,8 +401,10 @@ type clientStream struct { readAborted bool // read loop reset the stream totalHeaderSize int64 // total size of 1xx headers seen - trailer http.Header // accumulated trailers - resTrailer *http.Header // client's Response.Trailer + trailer Header // accumulated trailers + resTrailer *Header // client's Response.Trailer + + staticResp ClientResponse } var got1xxFuncForTests func(int, textproto.MIMEHeader) error @@ -557,7 +508,7 @@ type RoundTripOpt struct { allowHTTP bool // allow http:// URLs } -func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { +func (t *Transport) RoundTrip(req *ClientRequest) (*ClientResponse, error) { return t.RoundTripOpt(req, RoundTripOpt{}) } @@ -586,7 +537,7 @@ func authorityAddr(scheme string, authority string) (addr string) { } // RoundTripOpt is like RoundTrip, but takes options. -func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { +func (t *Transport) RoundTripOpt(req *ClientRequest, opt RoundTripOpt) (*ClientResponse, error) { switch req.URL.Scheme { case "https": // Always okay. @@ -624,9 +575,9 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res case <-tm.C: t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) continue - case <-req.Context().Done(): + case <-req.Context.Done(): tm.Stop() - err = req.Context().Err() + err = req.Context.Err() } } } @@ -654,6 +605,26 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res } } +func (t *Transport) IdleConnStrsForTesting() []string { + pool, ok := t.connPool().(noDialClientConnPool) + if !ok { + return nil + } + + var ret []string + pool.mu.Lock() + defer pool.mu.Unlock() + for k, ccs := range pool.conns { + for _, cc := range ccs { + if cc.idleState().canTakeNewRequest { + ret = append(ret, k) + } + } + } + slices.Sort(ret) + return ret +} + // CloseIdleConnections closes any connections which were previously // connected from previous requests but are now sitting idle. // It does not interrupt any connections currently in use. @@ -675,13 +646,13 @@ var ( // response headers. It is always called with a non-nil error. // It returns either a request to retry (either the same request, or a // modified clone), or an error if the request can't be replayed. -func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) { +func shouldRetryRequest(req *ClientRequest, err error) (*ClientRequest, error) { if !canRetryError(err) { return nil, err } // If the Body is nil (or http.NoBody), it's safe to reuse // this request and its Body. - if req.Body == nil || req.Body == http.NoBody { + if req.Body == nil || req.Body == NoBody { return req, nil } @@ -692,9 +663,9 @@ func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) { if err != nil { return nil, err } - newReq := *req + newReq := req.Clone() newReq.Body = body - return &newReq, nil + return newReq, nil } // The Request.Body can't reset back to the beginning, but we @@ -770,18 +741,27 @@ func (t *Transport) dialTLS(ctx context.Context, network, addr string, tlsCfg *t // disableKeepAlives reports whether connections should be closed as // soon as possible after handling the first request. func (t *Transport) disableKeepAlives() bool { - return t.t1 != nil && t.t1.DisableKeepAlives + return t.t1 != nil && t.t1.DisableKeepAlives() } func (t *Transport) expectContinueTimeout() time.Duration { if t.t1 == nil { return 0 } - return t.t1.ExpectContinueTimeout + return t.t1.ExpectContinueTimeout() } -func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { - return t.newClientConn(c, t.disableKeepAlives(), nil) +func (t *Transport) NewClientConn(c net.Conn, internalStateHook func()) (NetHTTPClientConn, error) { + cc, err := t.newClientConn(c, t.disableKeepAlives(), internalStateHook) + if err != nil { + return NetHTTPClientConn{}, err + } + + // RoundTrip should block when the conn is at its concurrency limit, + // not return an error. Setting strictMaxConcurrentStreams enables this. + cc.strictMaxConcurrentStreams = true + + return NetHTTPClientConn{cc}, nil } func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*ClientConn, error) { @@ -793,7 +773,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook nextStreamID: 1, maxFrameSize: 16 << 10, // spec default initialWindowSize: 65535, // spec default - initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, + initialStreamRecvWindowSize: int32(conf.MaxReceiveBufferPerStream), maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. strictMaxConcurrentStreams: conf.StrictMaxConcurrentRequests, peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead. @@ -828,16 +808,16 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook }) cc.br = bufio.NewReader(c) cc.fr = NewFramer(cc.bw, cc.br) - cc.fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) + cc.fr.SetMaxReadFrameSize(uint32(conf.MaxReadFrameSize)) if t.CountError != nil { cc.fr.countError = t.CountError } - maxHeaderTableSize := conf.MaxDecoderHeaderTableSize + maxHeaderTableSize := uint32(conf.MaxDecoderHeaderTableSize) cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil) cc.fr.MaxHeaderListSize = t.maxHeaderListSize() cc.henc = hpack.NewEncoder(&cc.hbuf) - cc.henc.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) + cc.henc.SetMaxDynamicTableSizeLimit(uint32(conf.MaxEncoderHeaderTableSize)) cc.peerMaxHeaderTableSize = initialHeaderTableSize if cs, ok := c.(connectionStater); ok { @@ -849,7 +829,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook {ID: SettingEnablePush, Val: 0}, {ID: SettingInitialWindowSize, Val: uint32(cc.initialStreamRecvWindowSize)}, } - initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: conf.MaxReadFrameSize}) + initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: uint32(conf.MaxReadFrameSize)}) if max := t.maxHeaderListSize(); max != 0 { initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max}) } @@ -859,8 +839,8 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook cc.bw.Write(clientPreface) cc.fr.WriteSettings(initialSettings...) - cc.fr.WriteWindowUpdate(0, uint32(conf.MaxUploadBufferPerConnection)) - cc.inflow.init(conf.MaxUploadBufferPerConnection + initialWindowSize) + cc.fr.WriteWindowUpdate(0, uint32(conf.MaxReceiveBufferPerConnection)) + cc.inflow.init(int32(conf.MaxReceiveBufferPerConnection) + initialWindowSize) cc.bw.Flush() if cc.werr != nil { cc.Close() @@ -1266,11 +1246,11 @@ func (cc *ClientConn) closeForLostPing() { // errRequestCanceled is a copy of net/http's errRequestCanceled because it's not // exported. At least they'll be DeepEqual for h1-vs-h2 comparisons tests. -var errRequestCanceled = errors.New("net/http: request canceled") +var errRequestCanceled = internal.ErrRequestCanceled func (cc *ClientConn) responseHeaderTimeout() time.Duration { if cc.t.t1 != nil { - return cc.t.t1.ResponseHeaderTimeout + return cc.t.t1.ResponseHeaderTimeout() } // No way to do this (yet?) with just an http2.Transport. Probably // no need. Request.Cancel this is the new way. We only need to support @@ -1282,8 +1262,8 @@ func (cc *ClientConn) responseHeaderTimeout() time.Duration { // actualContentLength returns a sanitized version of // req.ContentLength, where 0 actually means zero (not unknown) and -1 // means unknown. -func actualContentLength(req *http.Request) int64 { - if req.Body == nil || req.Body == http.NoBody { +func actualContentLength(req *ClientRequest) int64 { + if req.Body == nil || req.Body == NoBody { return 0 } if req.ContentLength != 0 { @@ -1304,13 +1284,13 @@ func (cc *ClientConn) decrStreamReservationsLocked() { } } -func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { +func (cc *ClientConn) RoundTrip(req *ClientRequest) (*ClientResponse, error) { return cc.roundTrip(req, nil) } -func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) { - ctx := req.Context() - cs := &clientStream{ +func (cc *ClientConn) roundTrip(req *ClientRequest, streamf func(*clientStream)) (*ClientResponse, error) { + ctx := req.Context + req.stream = clientStream{ cc: cc, ctx: ctx, reqCancel: req.Cancel, @@ -1322,7 +1302,9 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) abort: make(chan struct{}), respHeaderRecv: make(chan struct{}), donec: make(chan struct{}), + resTrailer: req.ResTrailer, } + cs := &req.stream cs.requestedGzip = httpcommon.IsRequestGzip(req.Method, req.Header, cc.t.disableCompression()) @@ -1339,7 +1321,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) } } - handleResponseHeaders := func() (*http.Response, error) { + handleResponseHeaders := func() (*ClientResponse, error) { res := cs.res if res.StatusCode > 299 { // On error or status code 3xx, 4xx, 5xx, etc abort any @@ -1353,9 +1335,8 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) // we can keep it. cs.abortRequestBodyWrite() } - res.Request = req res.TLS = cc.tlsState - if res.Body == noBody && actualContentLength(req) == 0 { + if res.Body == NoBody && actualContentLength(req) == 0 { // If there isn't a request or response body still being // written, then wait for the stream to be closed before // RoundTrip returns. @@ -1419,7 +1400,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) // doRequest runs for the duration of the request lifetime. // // It sends the request and performs post-request cleanup (closing Request.Body, etc.). -func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream)) { +func (cs *clientStream) doRequest(req *ClientRequest, streamf func(*clientStream)) { err := cs.writeRequest(req, streamf) cs.cleanupWriteRequest(err) } @@ -1433,7 +1414,7 @@ var errExtendedConnectNotSupported = errors.New("net/http: extended connect not // // It returns non-nil if the request ends otherwise. // If the returned error is StreamError, the error Code may be used in resetting the stream. -func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStream)) (err error) { +func (cs *clientStream) writeRequest(req *ClientRequest, streamf func(*clientStream)) (err error) { cc := cs.cc ctx := cs.ctx @@ -1577,7 +1558,7 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre } } -func (cs *clientStream) encodeAndWriteHeaders(req *http.Request) error { +func (cs *clientStream) encodeAndWriteHeaders(req *ClientRequest) error { cc := cs.cc ctx := cs.ctx @@ -1617,8 +1598,8 @@ func (cs *clientStream) encodeAndWriteHeaders(req *http.Request) error { return err } -func encodeRequestHeaders(req *http.Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) { - return httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{ +func encodeRequestHeaders(req *ClientRequest, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) { + return httpcommon.EncodeHeaders(req.Context, httpcommon.EncodeHeadersParam{ Request: httpcommon.Request{ Header: req.Header, Trailer: req.Trailer, @@ -1852,7 +1833,7 @@ func bufPoolIndex(size int) int { return index } -func (cs *clientStream) writeRequestBody(req *http.Request) (err error) { +func (cs *clientStream) writeRequestBody(req *ClientRequest) (err error) { cc := cs.cc body := cs.reqBody sentEnd := false // whether we sent the final DATA frame w/ END_STREAM @@ -2026,7 +2007,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) } // requires cc.wmu be held. -func (cc *ClientConn) encodeTrailers(trailer http.Header) ([]byte, error) { +func (cc *ClientConn) encodeTrailers(trailer Header) ([]byte, error) { cc.hbuf.Reset() hlSize := uint64(0) @@ -2065,7 +2046,7 @@ func (cc *ClientConn) writeHeader(name, value string) { type resAndError struct { _ incomparable - res *http.Response + res *ClientResponse err error } @@ -2359,7 +2340,6 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error { // (nil, nil) special case. See handleResponse docs. return nil } - cs.resTrailer = &res.Trailer cs.res = res close(cs.respHeaderRecv) if f.StreamEnded() { @@ -2374,7 +2354,7 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error { // // As a special case, handleResponse may return (nil, nil) to skip the // frame (currently only used for 1xx responses). -func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFrame) (*http.Response, error) { +func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFrame) (*ClientResponse, error) { if f.Truncated { return nil, errResponseHeaderListSize } @@ -2390,20 +2370,19 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra regularFields := f.RegularFields() strs := make([]string, len(regularFields)) - header := make(http.Header, len(regularFields)) - res := &http.Response{ - Proto: "HTTP/2.0", - ProtoMajor: 2, + header := make(Header, len(regularFields)) + res := &cs.staticResp + cs.staticResp = ClientResponse{ Header: header, StatusCode: statusCode, - Status: status + " " + http.StatusText(statusCode), + Status: status, } for _, hf := range regularFields { key := httpcommon.CanonicalHeader(hf.Name) if key == "Trailer" { t := res.Trailer if t == nil { - t = make(http.Header) + t = make(Header) res.Trailer = t } foreachHeaderElement(hf.Value, func(v string) { @@ -2445,8 +2424,8 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra // Use the larger limit of MaxHeaderListSize and // net/http.Transport.MaxResponseHeaderBytes. limit := int64(cs.cc.t.maxHeaderListSize()) - if t1 := cs.cc.t.t1; t1 != nil && t1.MaxResponseHeaderBytes > limit { - limit = t1.MaxResponseHeaderBytes + if t1 := cs.cc.t.t1; t1 != nil && t1.MaxResponseHeaderBytes() > limit { + limit = t1.MaxResponseHeaderBytes() } for _, h := range f.Fields { cs.totalHeaderSize += int64(h.Size()) @@ -2485,7 +2464,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra } if cs.isHead { - res.Body = noBody + res.Body = NoBody return res, nil } @@ -2493,7 +2472,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra if res.ContentLength > 0 { res.Body = missingBody{} } else { - res.Body = noBody + res.Body = NoBody } return res, nil } @@ -2529,7 +2508,7 @@ func (rl *clientConnReadLoop) processTrailers(cs *clientStream, f *MetaHeadersFr return ConnectionError(ErrCodeProtocol) } - trailer := make(http.Header) + trailer := make(Header) for _, hf := range f.RegularFields() { key := httpcommon.CanonicalHeader(hf.Name) trailer[key] = append(trailer[key], hf.Value) @@ -2808,7 +2787,7 @@ func (cs *clientStream) copyTrailers() { for k, vv := range cs.trailer { t := cs.resTrailer if *t == nil { - *t = make(http.Header) + *t = make(Header) } (*t)[k] = vv } @@ -3105,13 +3084,6 @@ func (t *Transport) logf(format string, args ...interface{}) { log.Printf(format, args...) } -var noBody io.ReadCloser = noBodyReader{} - -type noBodyReader struct{} - -func (noBodyReader) Close() error { return nil } -func (noBodyReader) Read([]byte) (int, error) { return 0, io.EOF } - type missingBody struct{} func (missingBody) Close() error { return nil } @@ -3128,8 +3100,8 @@ func strSliceContains(ss []string, s string) bool { type erringRoundTripper struct{ err error } -func (rt erringRoundTripper) RoundTripErr() error { return rt.err } -func (rt erringRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { return nil, rt.err } +func (rt erringRoundTripper) RoundTripErr() error { return rt.err } +func (rt erringRoundTripper) RoundTrip(*ClientRequest) (*ClientResponse, error) { return nil, rt.err } var errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body") @@ -3231,69 +3203,25 @@ func (gz *gzipReader) Close() error { // isConnectionCloseRequest reports whether req should use its own // connection for a single request and then close the connection. -func isConnectionCloseRequest(req *http.Request) bool { +func isConnectionCloseRequest(req *ClientRequest) bool { return req.Close || httpguts.HeaderValuesContainsToken(req.Header["Connection"], "close") } -// registerHTTPSProtocol calls Transport.RegisterProtocol but -// converting panics into errors. -func registerHTTPSProtocol(t *http.Transport, rt noDialH2RoundTripper) (err error) { - defer func() { - if e := recover(); e != nil { - err = fmt.Errorf("%v", e) - } - }() - t.RegisterProtocol("https", rt) - return nil -} - -// noDialH2RoundTripper is a RoundTripper which only tries to complete the request -// if there's already a cached connection to the host. -// (The field is exported so it can be accessed via reflect from net/http; tested -// by TestNoDialH2RoundTripperType) -// -// A noDialH2RoundTripper is registered with http1.Transport.RegisterProtocol, -// and the http1.Transport can use type assertions to call non-RoundTrip methods on it. -// This lets us expose, for example, NewClientConn to net/http. -type noDialH2RoundTripper struct{ *Transport } - -func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - res, err := rt.Transport.RoundTrip(req) - if isNoCachedConnError(err) { - return nil, http.ErrSkipAltProtocol - } - return res, err -} - -func (rt noDialH2RoundTripper) NewClientConn(conn net.Conn, internalStateHook func()) (http.RoundTripper, error) { - tr := rt.Transport - cc, err := tr.newClientConn(conn, tr.disableKeepAlives(), internalStateHook) - if err != nil { - return nil, err - } - - // RoundTrip should block when the conn is at its concurrency limit, - // not return an error. Setting strictMaxConcurrentStreams enables this. - cc.strictMaxConcurrentStreams = true - - return netHTTPClientConn{cc}, nil -} - // netHTTPClientConn wraps ClientConn and implements the interface net/http expects from // the RoundTripper returned by NewClientConn. -type netHTTPClientConn struct { +type NetHTTPClientConn struct { cc *ClientConn } -func (cc netHTTPClientConn) RoundTrip(req *http.Request) (*http.Response, error) { +func (cc NetHTTPClientConn) RoundTrip(req *ClientRequest) (*ClientResponse, error) { return cc.cc.RoundTrip(req) } -func (cc netHTTPClientConn) Close() error { +func (cc NetHTTPClientConn) Close() error { return cc.cc.Close() } -func (cc netHTTPClientConn) Err() error { +func (cc NetHTTPClientConn) Err() error { cc.cc.mu.Lock() defer cc.cc.mu.Unlock() if cc.cc.closed { @@ -3302,7 +3230,7 @@ func (cc netHTTPClientConn) Err() error { return nil } -func (cc netHTTPClientConn) Reserve() error { +func (cc NetHTTPClientConn) Reserve() error { defer cc.cc.maybeCallStateHook() cc.cc.mu.Lock() defer cc.cc.mu.Unlock() @@ -3313,7 +3241,7 @@ func (cc netHTTPClientConn) Reserve() error { return nil } -func (cc netHTTPClientConn) Release() { +func (cc NetHTTPClientConn) Release() { defer cc.cc.maybeCallStateHook() cc.cc.mu.Lock() defer cc.cc.mu.Unlock() @@ -3326,13 +3254,13 @@ func (cc netHTTPClientConn) Release() { } } -func (cc netHTTPClientConn) Available() int { +func (cc NetHTTPClientConn) Available() int { cc.cc.mu.Lock() defer cc.cc.mu.Unlock() return cc.cc.availableLocked() } -func (cc netHTTPClientConn) InFlight() int { +func (cc NetHTTPClientConn) InFlight() int { cc.cc.mu.Lock() defer cc.cc.mu.Unlock() return cc.cc.currentRequestCountLocked() @@ -3353,22 +3281,22 @@ func (t *Transport) idleConnTimeout() time.Duration { } if t.t1 != nil { - return t.t1.IdleConnTimeout + return t.t1.IdleConnTimeout() } return 0 } -func traceGetConn(req *http.Request, hostPort string) { - trace := httptrace.ContextClientTrace(req.Context()) +func traceGetConn(req *ClientRequest, hostPort string) { + trace := httptrace.ContextClientTrace(req.Context) if trace == nil || trace.GetConn == nil { return } trace.GetConn(hostPort) } -func traceGotConn(req *http.Request, cc *ClientConn, reused bool) { - trace := httptrace.ContextClientTrace(req.Context()) +func traceGotConn(req *ClientRequest, cc *ClientConn, reused bool) { + trace := httptrace.ContextClientTrace(req.Context) if trace == nil || trace.GotConn == nil { return } diff --git a/src/net/http/internal/http2/transport_internal_test.go b/src/net/http/internal/http2/transport_internal_test.go index 2f8532fd75..a6d67a9567 100644 --- a/src/net/http/internal/http2/transport_internal_test.go +++ b/src/net/http/internal/http2/transport_internal_test.go @@ -11,7 +11,6 @@ import ( "fmt" "io" "io/fs" - "net/http" "reflect" "strings" "testing" @@ -25,27 +24,27 @@ func (panicReader) Close() error { panic("unexpected Close") } func TestActualContentLength(t *testing.T) { tests := []struct { - req *http.Request + req *ClientRequest want int64 }{ // Verify we don't read from Body: 0: { - req: &http.Request{Body: panicReader{}}, + req: &ClientRequest{Body: panicReader{}}, want: -1, }, // nil Body means 0, regardless of ContentLength: 1: { - req: &http.Request{Body: nil, ContentLength: 5}, + req: &ClientRequest{Body: nil, ContentLength: 5}, want: 0, }, // ContentLength is used if set. 2: { - req: &http.Request{Body: panicReader{}, ContentLength: 5}, + req: &ClientRequest{Body: panicReader{}, ContentLength: 5}, want: 5, }, // http.NoBody means 0, not -1. 3: { - req: &http.Request{Body: http.NoBody}, + req: &ClientRequest{Body: NoBody}, want: 0, }, } @@ -200,7 +199,7 @@ func TestTransportUsesGetBodyWhenPresent(t *testing.T) { someBody := func() io.ReadCloser { return struct{ io.ReadCloser }{io.NopCloser(bytes.NewReader(nil))} } - req := &http.Request{ + req := &ClientRequest{ Body: someBody(), GetBody: func() (io.ReadCloser, error) { calls++ @@ -232,28 +231,6 @@ func TestTransportUsesGetBodyWhenPresent(t *testing.T) { } } -// Issue 22891: verify that the "https" altproto we register with net/http -// is a certain type: a struct with one field with our *http2.Transport in it. -func TestNoDialH2RoundTripperType(t *testing.T) { - t1 := new(http.Transport) - t2 := new(Transport) - rt := noDialH2RoundTripper{t2} - if err := registerHTTPSProtocol(t1, rt); err != nil { - t.Fatal(err) - } - rv := reflect.ValueOf(rt) - if rv.Type().Kind() != reflect.Struct { - t.Fatalf("kind = %v; net/http expects struct", rv.Type().Kind()) - } - if n := rv.Type().NumField(); n != 1 { - t.Fatalf("fields = %d; net/http expects 1", n) - } - v := rv.Field(0) - if _, ok := v.Interface().(*Transport); !ok { - t.Fatalf("wrong kind %T; want *Transport", v.Interface()) - } -} - func TestClientConnTooIdle(t *testing.T) { tests := []struct { cc func() *ClientConn diff --git a/src/net/http/internal/http2/transport_test.go b/src/net/http/internal/http2/transport_test.go index faae10e6a4..8f1a589624 100644 --- a/src/net/http/internal/http2/transport_test.go +++ b/src/net/http/internal/http2/transport_test.go @@ -36,6 +36,7 @@ import ( "time" . "net/http/internal/http2" + "net/http/internal/httpcommon" "golang.org/x/net/http2/hpack" ) @@ -62,7 +63,6 @@ func newTransport(t testing.TB, opts ...any) *http.Transport { Protocols: protocols("h2"), HTTP2: &http.HTTP2Config{}, } - ConfigureTransport(tr1) for _, o := range opts { switch o := o.(type) { case func(*http.Transport): @@ -90,34 +90,6 @@ func TestTransportExternal(t *testing.T) { res.Write(os.Stdout) } -type fakeTLSConn struct { - net.Conn -} - -func (c *fakeTLSConn) ConnectionState() tls.ConnectionState { - const cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F // defined in ciphers.go - return tls.ConnectionState{ - Version: tls.VersionTLS12, - CipherSuite: cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - } -} - -func startH2cServer(t *testing.T) net.Listener { - h2Server := &Server{} - l := newLocalListener(t) - go func() { - conn, err := l.Accept() - if err != nil { - t.Error(err) - return - } - h2Server.ServeConn(&fakeTLSConn{conn}, &ServeConnOpts{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %v, http: %v", r.URL.Path, r.TLS == nil) - })}) - }() - return l -} - func TestIdleConnTimeout(t *testing.T) { for _, test := range []struct { name string @@ -201,9 +173,12 @@ func TestIdleConnTimeout(t *testing.T) { } func TestTransportH2c(t *testing.T) { - l := startH2cServer(t) - defer l.Close() - req, err := http.NewRequest("GET", "http://"+l.Addr().String()+"/foobar", nil) + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %v, http: %v", r.URL.Path, r.TLS == nil) + }, func(s *http.Server) { + s.Protocols = protocols("h2c") + }) + req, err := http.NewRequest("GET", ts.URL+"/foobar", nil) if err != nil { t.Fatal(err) } @@ -217,7 +192,7 @@ func TestTransportH2c(t *testing.T) { } req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) tr := newTransport(t) - tr.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return net.Dial(network, addr) } tr.Protocols = protocols("h2c") @@ -1313,16 +1288,24 @@ func testTransportChecksRequestHeaderListSize(t testing.TB) { rt.wantStatus(http.StatusOK) } headerListSizeForRequest := func(req *http.Request) (size uint64) { - const addGzipHeader = true - const peerMaxHeaderListSize = 0xffffffffffffffff - _, err := EncodeRequestHeaders(req, addGzipHeader, peerMaxHeaderListSize, func(name, value string) { + _, err := httpcommon.EncodeHeaders(context.Background(), httpcommon.EncodeHeadersParam{ + Request: httpcommon.Request{ + Header: req.Header, + Trailer: req.Trailer, + URL: req.URL, + Host: req.Host, + Method: req.Method, + ActualContentLength: req.ContentLength, + }, + AddGzipHeader: true, + PeerMaxHeaderListSize: 0xffffffffffffffff, + }, func(name, value string) { hf := hpack.HeaderField{Name: name, Value: value} size += uint64(hf.Size()) }) if err != nil { t.Fatal(err) } - fmt.Println(size) return size } // Create a new Request for each test, rather than reusing the @@ -1330,17 +1313,19 @@ func testTransportChecksRequestHeaderListSize(t testing.TB) { // See https://github.com/golang/go/issues/21316 newRequest := func() *http.Request { // Body must be non-nil to enable writing trailers. - body := strings.NewReader("hello") + const bodytext = "hello" + body := strings.NewReader(bodytext) req, err := http.NewRequest("POST", "https://example.tld/", body) if err != nil { t.Fatalf("newRequest: NewRequest: %v", err) } + req.ContentLength = int64(len(bodytext)) + req.Header = http.Header{"User-Agent": nil} return req } // Pad headers & trailers, but stay under peerSize. req := newRequest() - req.Header = make(http.Header) req.Trailer = make(http.Header) filler := strings.Repeat("*", 1024) padHeaders(t, req.Trailer, peerSize, filler) @@ -1352,7 +1337,6 @@ func testTransportChecksRequestHeaderListSize(t testing.TB) { // Add enough header bytes to push us over peerSize. req = newRequest() - req.Header = make(http.Header) padHeaders(t, req.Header, peerSize, filler) checkRoundTrip(req, ErrRequestHeaderListSize, "Headers over limit") @@ -1365,7 +1349,6 @@ func testTransportChecksRequestHeaderListSize(t testing.TB) { // Send headers with a single large value. req = newRequest() filler = strings.Repeat("*", int(peerSize)) - req.Header = make(http.Header) req.Header.Set("Big", filler) checkRoundTrip(req, ErrRequestHeaderListSize, "Single large header") @@ -2596,10 +2579,18 @@ func TestTransportRequestPathPseudo(t *testing.T) { for i, tt := range tests { hbuf := &bytes.Buffer{} henc := hpack.NewEncoder(hbuf) - - const addGzipHeader = false - const peerMaxHeaderListSize = 0xffffffffffffffff - _, err := EncodeRequestHeaders(tt.req, addGzipHeader, peerMaxHeaderListSize, func(name, value string) { + _, err := httpcommon.EncodeHeaders(context.Background(), httpcommon.EncodeHeadersParam{ + Request: httpcommon.Request{ + Header: tt.req.Header, + Trailer: tt.req.Trailer, + URL: tt.req.URL, + Host: tt.req.Host, + Method: tt.req.Method, + ActualContentLength: tt.req.ContentLength, + }, + AddGzipHeader: false, + PeerMaxHeaderListSize: 0xffffffffffffffff, + }, func(name, value string) { henc.WriteField(hpack.HeaderField{Name: name, Value: value}) }) hdrs := hbuf.Bytes() @@ -3631,10 +3622,11 @@ func testClientConnCloseAtBody(t testing.TB) { ), }) tc.writeData(rt.streamID(), false, make([]byte, 64)) + resp := rt.response() tc.cc.Close() synctest.Wait() - if _, err := io.Copy(io.Discard, rt.response().Body); err == nil { + if _, err := io.Copy(io.Discard, resp.Body); err == nil { t.Error("expected a Copy error, got nil") } } @@ -4352,13 +4344,13 @@ func TestTransportCloseRequestBody(t *testing.T) { w.WriteHeader(statusCode) }) - tr := &Transport{TLSClientConfig: tlsConfigInsecure} - defer tr.CloseIdleConnections() + tr := newTransport(t) ctx := context.Background() - cc, err := tr.DialClientConn(ctx, ts.Listener.Addr().String(), false) + cc, err := tr.NewClientConn(ctx, "https", ts.Listener.Addr().String()) if err != nil { t.Fatal(err) } + defer cc.Close() for _, status := range []int{200, 401} { t.Run(fmt.Sprintf("status=%d", status), func(t *testing.T) { @@ -4966,39 +4958,40 @@ func testTransportDataAfter1xxHeader(t testing.TB) { } func TestIssue66763Race(t *testing.T) { - tr := &Transport{ - IdleConnTimeout: 1 * time.Nanosecond, - AllowHTTP: true, // issue 66763 only occurs when AllowHTTP is true - } - defer tr.CloseIdleConnections() + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}, + func(s *http.Server) { + s.Protocols = protocols("h2c") + }) + tr := newTransport(t) + tr.IdleConnTimeout = 1 * time.Nanosecond + tr.Protocols = protocols("h2c") - cli, srv := net.Pipe() donec := make(chan struct{}) go func() { // Creating the client conn may succeed or fail, // depending on when the idle timeout happens. // Either way, the idle timeout will close the net.Conn. - tr.NewClientConn(cli) + conn, err := tr.NewClientConn(t.Context(), "http", ts.URL) close(donec) + if err == nil { + conn.Close() + } }() // The client sends its preface and SETTINGS frame, // and then closes its conn after the idle timeout. - io.ReadAll(srv) - srv.Close() - <-donec } // Issue 67671: Sending a Connection: close request on a Transport with AllowHTTP // set caused a the transport to wedge. func TestIssue67671(t *testing.T) { - ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}) - tr := &Transport{ - TLSClientConfig: tlsConfigInsecure, - AllowHTTP: true, - } - defer tr.CloseIdleConnections() + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {}, + func(s *http.Server) { + s.Protocols = protocols("h2c") + }) + tr := newTransport(t) + tr.Protocols = protocols("h2c") req, _ := http.NewRequest("GET", ts.URL, nil) req.Close = true for i := 0; i < 2; i++ { @@ -5215,6 +5208,7 @@ func testTransportSendNoMoreThanOnePingWithReset(t testing.TB) { // because we haven't received a HEADERS or DATA frame from the server // since the last PING we sent. makeAndResetRequest() + tc.wantIdle() // Server belatedly responds to request 1. // The server has not responded to our first PING yet. @@ -5334,26 +5328,45 @@ func testTransportConnBecomesUnresponsive(t testing.TB) { rt2.response().Body.Close() } -// Test that the Transport can use a conn provided to it by a TLSNextProto hook. -func TestTransportTLSNextProtoConnOK(t *testing.T) { synctestTest(t, testTransportTLSNextProtoConnOK) } -func testTransportTLSNextProtoConnOK(t testing.TB) { - t1 := &http.Transport{} - t2, _ := ConfigureTransports(t1) - tt := newTestTransport(t, t2) +// newTestTransportWithUnusedConn creates a Transport, +// sends a request on the Transport, +// and then cancels the request before the resulting dial completes. +// It then waits for the dial to finish +// and returns the Transport with an unused conn in its pool. +func newTestTransportWithUnusedConn(t testing.TB, opts ...any) *testTransport { + tt := newTestTransport(t, opts...) - // Create a new, fake connection and pass it to the Transport via the TLSNextProto hook. - cli, _ := synctestNetPipe() - cliTLS := tls.Client(cli, tlsConfigInsecure) - go func() { - tt.tr.TestTransport().TLSNextProto["h2"]("dummy.tld", cliTLS) - }() + waitc := make(chan struct{}) + dialContext := tt.tr1.DialContext + tt.tr1.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { + <-waitc + return dialContext(ctx, network, address) + } + + req := Must(http.NewRequest("GET", "https://dummy.tld/", nil)) + rt := tt.roundTrip(req) + rt.cancel() + if rt.err() == nil { + t.Fatalf("RoundTrip still running after request is canceled") + } + + close(waitc) synctest.Wait() + return tt +} + +// Test that the Transport can use a conn created for one request, but never used by it. +func TestTransportUnusedConnOK(t *testing.T) { synctestTest(t, testTransportUnusedConnOK) } +func testTransportUnusedConnOK(t testing.TB) { + tt := newTestTransportWithUnusedConn(t) + + req := Must(http.NewRequest("GET", "https://dummy.tld/", nil)) tc := tt.getConn() - tc.greet() + tc.wantFrameType(FrameSettings) + tc.wantFrameType(FrameWindowUpdate) // Send a request on the Transport. // It uses the conn we provided. - req := Must(http.NewRequest("GET", "https://dummy.tld/", nil)) rt := tt.roundTrip(req) tc.wantHeaders(wantHeader{ streamID: 1, @@ -5364,6 +5377,11 @@ func testTransportTLSNextProtoConnOK(t testing.TB) { ":path": []string{"/"}, }, }) + + tc.writeSettings() + tc.writeSettingsAck() + tc.wantFrameType(FrameSettings) // acknowledgement + tc.writeHeaders(HeadersFrameParam{ StreamID: 1, EndHeaders: true, @@ -5376,26 +5394,16 @@ func testTransportTLSNextProtoConnOK(t testing.TB) { rt.wantBody(nil) } -// Test the case where a conn provided via a TLSNextProto hook immediately encounters an error. -func TestTransportTLSNextProtoConnImmediateFailureUsed(t *testing.T) { - synctestTest(t, testTransportTLSNextProtoConnImmediateFailureUsed) +// Test the case where an unused conn immediately encounters an error. +func TestTransportUnusedConnImmediateFailureUsed(t *testing.T) { + synctestTest(t, testTransportUnusedConnImmediateFailureUsed) } -func testTransportTLSNextProtoConnImmediateFailureUsed(t testing.TB) { - t1 := &http.Transport{} - t2, _ := ConfigureTransports(t1) - tt := newTestTransport(t, t2) - - // Create a new, fake connection and pass it to the Transport via the TLSNextProto hook. - cli, _ := synctestNetPipe() - cliTLS := tls.Client(cli, tlsConfigInsecure) - go func() { - t1.TLSNextProto["h2"]("dummy.tld", cliTLS) - }() - synctest.Wait() - tc := tt.getConn() +func testTransportUnusedConnImmediateFailureUsed(t testing.TB) { + tt := newTestTransportWithUnusedConn(t) // The connection encounters an error before we send a request that uses it. - tc.closeWrite() + tc1 := tt.getConn() + tc1.closeWrite() // Send a request on the Transport. // @@ -5407,33 +5415,24 @@ func testTransportTLSNextProtoConnImmediateFailureUsed(t testing.TB) { } // Send the request again. - // This time it should fail with ErrNoCachedConn, + // This time it is sent on a new conn // because the dead conn has been removed from the pool. - rt = tt.roundTrip(req) - if err := rt.err(); !errors.Is(err, ErrNoCachedConn) { - t.Fatalf("RoundTrip after broken conn is used: got %v, want ErrNoCachedConn", err) - } + _ = tt.roundTrip(req) + tc2 := tt.getConn() + tc2.wantFrameType(FrameSettings) + tc2.wantFrameType(FrameWindowUpdate) + tc2.wantFrameType(FrameHeaders) } -// Test the case where a conn provided via a TLSNextProto hook is closed for idleness -// before we use it. -func TestTransportTLSNextProtoConnIdleTimoutBeforeUse(t *testing.T) { - synctestTest(t, testTransportTLSNextProtoConnIdleTimoutBeforeUse) +// Test the case where an unused conn is closed for idleness before we use it. +func TestTransportUnusedConnIdleTimoutBeforeUse(t *testing.T) { + synctestTest(t, testTransportUnusedConnIdleTimoutBeforeUse) } -func testTransportTLSNextProtoConnIdleTimoutBeforeUse(t testing.TB) { - t1 := &http.Transport{ - IdleConnTimeout: 1 * time.Second, - } - t2, _ := ConfigureTransports(t1) - tt := newTestTransport(t, t2) +func testTransportUnusedConnIdleTimoutBeforeUse(t testing.TB) { + tt := newTestTransportWithUnusedConn(t, func(t1 *http.Transport) { + t1.IdleConnTimeout = 1 * time.Second + }) - // Create a new, fake connection and pass it to the Transport via the TLSNextProto hook. - cli, _ := synctestNetPipe() - cliTLS := tls.Client(cli, tlsConfigInsecure) - go func() { - t1.TLSNextProto["h2"]("dummy.tld", cliTLS) - }() - synctest.Wait() _ = tt.getConn() // The connection encounters an error before we send a request that uses it. @@ -5442,12 +5441,14 @@ func testTransportTLSNextProtoConnIdleTimoutBeforeUse(t testing.TB) { // Send a request on the Transport. // - // It should fail with ErrNoCachedConn. + // It is sent on a new conn + // because the old one has idled out and been removed from the pool. req := Must(http.NewRequest("GET", "https://dummy.tld/", nil)) - rt := tt.roundTrip(req) - if err := rt.err(); !errors.Is(err, ErrNoCachedConn) { - t.Fatalf("RoundTrip with conn closed for idleness: got %v, want ErrNoCachedConn", err) - } + _ = tt.roundTrip(req) + tc2 := tt.getConn() + tc2.wantFrameType(FrameSettings) + tc2.wantFrameType(FrameWindowUpdate) + tc2.wantFrameType(FrameHeaders) } // Test the case where a conn provided via a TLSNextProto hook immediately encounters an error, @@ -5456,21 +5457,13 @@ func TestTransportTLSNextProtoConnImmediateFailureUnused(t *testing.T) { synctestTest(t, testTransportTLSNextProtoConnImmediateFailureUnused) } func testTransportTLSNextProtoConnImmediateFailureUnused(t testing.TB) { - t1 := &http.Transport{} - t2, _ := ConfigureTransports(t1) - tt := newTestTransport(t, t2) - - // Create a new, fake connection and pass it to the Transport via the TLSNextProto hook. - cli, _ := synctestNetPipe() - cliTLS := tls.Client(cli, tlsConfigInsecure) - go func() { - t1.TLSNextProto["h2"]("dummy.tld", cliTLS) - }() - synctest.Wait() - tc := tt.getConn() + tt := newTestTransportWithUnusedConn(t, func(t1 *http.Transport) { + t1.IdleConnTimeout = 1 * time.Second + }) // The connection encounters an error before we send a request that uses it. - tc.closeWrite() + tc1 := tt.getConn() + tc1.closeWrite() // Some time passes. // The dead connection is removed from the pool. @@ -5478,12 +5471,13 @@ func testTransportTLSNextProtoConnImmediateFailureUnused(t testing.TB) { // Send a request on the Transport. // - // It should fail with ErrNoCachedConn, because the pool contains no conns. + // It is sent on a new conn. req := Must(http.NewRequest("GET", "https://dummy.tld/", nil)) - rt := tt.roundTrip(req) - if err := rt.err(); !errors.Is(err, ErrNoCachedConn) { - t.Fatalf("RoundTrip after broken conn expires: got %v, want ErrNoCachedConn", err) - } + _ = tt.roundTrip(req) + tc2 := tt.getConn() + tc2.wantFrameType(FrameSettings) + tc2.wantFrameType(FrameWindowUpdate) + tc2.wantFrameType(FrameHeaders) } func TestExtendedConnectClientWithServerSupport(t *testing.T) { diff --git a/src/net/http/internal/http2/unencrypted.go b/src/net/http/internal/http2/unencrypted.go deleted file mode 100644 index b2de211613..0000000000 --- a/src/net/http/internal/http2/unencrypted.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package http2 - -import ( - "crypto/tls" - "errors" - "net" -) - -const nextProtoUnencryptedHTTP2 = "unencrypted_http2" - -// unencryptedNetConnFromTLSConn retrieves a net.Conn wrapped in a *tls.Conn. -// -// TLSNextProto functions accept a *tls.Conn. -// -// When passing an unencrypted HTTP/2 connection to a TLSNextProto function, -// we pass a *tls.Conn with an underlying net.Conn containing the unencrypted connection. -// To be extra careful about mistakes (accidentally dropping TLS encryption in a place -// where we want it), the tls.Conn contains a net.Conn with an UnencryptedNetConn method -// that returns the actual connection we want to use. -func unencryptedNetConnFromTLSConn(tc *tls.Conn) (net.Conn, error) { - conner, ok := tc.NetConn().(interface { - UnencryptedNetConn() net.Conn - }) - if !ok { - return nil, errors.New("http2: TLS conn unexpectedly found in unencrypted handoff") - } - return conner.UnencryptedNetConn(), nil -} diff --git a/src/net/http/internal/http2/write.go b/src/net/http/internal/http2/write.go index 0691934f79..59c4e625a5 100644 --- a/src/net/http/internal/http2/write.go +++ b/src/net/http/internal/http2/write.go @@ -8,7 +8,6 @@ import ( "bytes" "fmt" "log" - "net/http" "net/http/internal/httpcommon" "net/url" @@ -189,9 +188,9 @@ func splitHeaderBlock(ctx writeContext, headerBlock []byte, fn func(ctx writeCon // for HTTP response headers or trailers from a server handler. type writeResHeaders struct { streamID uint32 - httpResCode int // 0 means no ":status" line - h http.Header // may be nil - trailers []string // if non-nil, which keys of h to write. nil means all. + httpResCode int // 0 means no ":status" line + h Header // may be nil + trailers []string // if non-nil, which keys of h to write. nil means all. endStream bool date string @@ -263,7 +262,7 @@ type writePushPromise struct { streamID uint32 // pusher stream method string // for :method url *url.URL // for :scheme, :authority, :path - h http.Header + h Header // Creates an ID for a pushed stream. This runs on serveG just before // the frame is written. The returned ID is copied to promisedID. @@ -341,7 +340,7 @@ func (wu writeWindowUpdate) writeFrame(ctx writeContext) error { // encodeHeaders encodes an http.Header. If keys is not nil, then (k, h[k]) // is encoded only if k is in keys. -func encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) { +func encodeHeaders(enc *hpack.Encoder, h Header, keys []string) { if keys == nil { sorter := sorterPool.Get().(*sorter) // Using defer here, since the returned keys from the diff --git a/src/net/http/omithttp2.go b/src/net/http/omithttp2.go index ca08ddfad8..f12c6afee7 100644 --- a/src/net/http/omithttp2.go +++ b/src/net/http/omithttp2.go @@ -6,74 +6,17 @@ package http -import ( - "errors" - "sync" - "time" -) - func init() { omitBundledHTTP2 = true } const noHTTP2 = "no bundled HTTP/2" // should never see this -var http2errRequestCanceled = errors.New("net/http: request canceled") - -var http2goAwayTimeout = 1 * time.Second - -const http2NextProtoTLS = "h2" - -type http2Transport struct { - MaxHeaderListSize uint32 - ConnPool any -} - -func (*http2Transport) RoundTrip(*Request) (*Response, error) { panic(noHTTP2) } -func (*http2Transport) CloseIdleConnections() {} - -type http2noDialH2RoundTripper struct{} - -func (http2noDialH2RoundTripper) RoundTrip(*Request) (*Response, error) { panic(noHTTP2) } - -type http2noDialClientConnPool struct { - http2clientConnPool http2clientConnPool -} - -type http2clientConnPool struct { - mu *sync.Mutex - conns map[string][]*http2clientConn -} - -type http2clientConn struct{} - -type http2clientConnIdleState struct { - canTakeNewRequest bool -} - -func (cc *http2clientConn) idleState() http2clientConnIdleState { return http2clientConnIdleState{} } - -func http2configureTransports(*Transport) (*http2Transport, error) { panic(noHTTP2) } - -func http2isNoCachedConnError(err error) bool { - _, ok := err.(interface{ IsHTTP2NoCachedConnError() }) - return ok -} - -type http2Server struct { - NewWriteScheduler func() http2WriteScheduler -} - -type http2WriteScheduler any - -func http2NewPriorityWriteScheduler(any) http2WriteScheduler { panic(noHTTP2) } - -func http2ConfigureServer(s *Server, conf *http2Server) error { panic(noHTTP2) } - -var http2ErrNoCachedConn = http2noCachedConnError{} +func (s *Server) configureHTTP2() {} +func (t *Transport) configureHTTP2(protocols Protocols) {} -type http2noCachedConnError struct{} +type http2Server struct{} -func (http2noCachedConnError) IsHTTP2NoCachedConnError() {} +type http2RoundTripper struct{} -func (http2noCachedConnError) Error() string { return "http2: no cached connection was available" } +func (http2RoundTripper) RoundTrip(*Request) (*Response, error) { panic(noHTTP2) } diff --git a/src/net/http/server.go b/src/net/http/server.go index 902d6d3367..5502ce7ac4 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -41,7 +41,7 @@ var ( // ErrBodyNotAllowed is returned by ResponseWriter.Write calls // when the HTTP method or response code does not permit a // body. - ErrBodyNotAllowed = errors.New("http: request method or response status code does not allow body") + ErrBodyNotAllowed = internal.ErrBodyNotAllowed // ErrHijacked is returned by ResponseWriter.Write calls when // the underlying connection has been hijacked using the @@ -1871,7 +1871,7 @@ func (e statusError) Error() string { return StatusText(e.code) + ": " + e.text // While any panic from ServeHTTP aborts the response to the client, // panicking with ErrAbortHandler also suppresses logging of a stack // trace to the server's error log. -var ErrAbortHandler = errors.New("net/http: abort Handler") +var ErrAbortHandler = internal.ErrAbortHandler // isCommonNetReadError reports whether err is a common error // encountered during reading a request off the network when the @@ -3096,6 +3096,7 @@ type Server struct { listeners map[*net.Listener]struct{} activeConn map[*conn]struct{} onShutdown []func() + h2 *http2Server listenerGroup sync.WaitGroup } @@ -3396,7 +3397,7 @@ func (s *Server) shouldConfigureHTTP2ForServe() bool { // passed this tls.Config to tls.NewListener. And if they did, // it's too late anyway to fix it. It would only be potentially racy. // See Issue 15908. - return slices.Contains(s.TLSConfig.NextProtos, http2NextProtoTLS) + return slices.Contains(s.TLSConfig.NextProtos, "h2") } // ErrServerClosed is returned by the [Server.Serve], [ServeTLS], [ListenAndServe], @@ -3867,8 +3868,7 @@ func (s *Server) onceSetNextProtoDefaults() { // to add it. return } - conf := &http2Server{} - s.nextProtoErr = http2ConfigureServer(s, conf) + s.configureHTTP2() } // TimeoutHandler returns a [Handler] that runs h with the given time limit. diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 32bbb04759..b7f68e6057 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -24,6 +24,7 @@ import ( "maps" "net" "net/http/httptrace" + "net/http/internal" "net/http/internal/ascii" "net/textproto" "net/url" @@ -471,34 +472,8 @@ func (t *Transport) onceSetNextProtoDefaults() { if omitBundledHTTP2 { return } - t2, err := http2configureTransports(t) - if err != nil { - log.Printf("Error enabling Transport HTTP/2 support: %v", err) - return - } - t.h2transport = t2 - - // Auto-configure the http2.Transport's MaxHeaderListSize from - // the http.Transport's MaxResponseHeaderBytes. They don't - // exactly mean the same thing, but they're close. - // - // TODO: also add this to x/net/http2.Configure Transport, behind - // a +build go1.7 build tag: - if limit1 := t.MaxResponseHeaderBytes; limit1 != 0 && t2.MaxHeaderListSize == 0 { - const h2max = 1<<32 - 1 - if limit1 >= h2max { - t2.MaxHeaderListSize = h2max - } else { - t2.MaxHeaderListSize = uint32(limit1) - } - } - // Server.ServeTLS clones the tls.Config before modifying it. - // Transport doesn't. We may want to make the two consistent some day. - // - // http2configureTransport will have already set NextProtos, but adjust it again - // here to remove HTTP/1.1 if the user has disabled it. - t.TLSClientConfig.NextProtos = adjustNextProtos(t.TLSClientConfig.NextProtos, protocols) + t.configureHTTP2(protocols) } func (t *Transport) protocols() Protocols { @@ -788,6 +763,11 @@ func (t *Transport) roundTrip(req *Request) (_ *Response, err error) { } } +func http2isNoCachedConnError(err error) bool { + _, ok := err.(interface{ IsHTTP2NoCachedConnError() }) + return ok +} + func awaitLegacyCancel(ctx context.Context, cancel context.CancelCauseFunc, req *Request) { select { case <-req.Cancel: @@ -903,7 +883,7 @@ func (pc *persistConn) shouldRetryRequest(req *Request, err error) bool { } // ErrSkipAltProtocol is a sentinel error value defined by Transport.RegisterProtocol. -var ErrSkipAltProtocol = errors.New("net/http: skip alternate protocol") +var ErrSkipAltProtocol = internal.ErrSkipAltProtocol // RegisterProtocol registers a new protocol with scheme. // The [Transport] will pass requests using the given scheme to rt. @@ -916,6 +896,12 @@ var ErrSkipAltProtocol = errors.New("net/http: skip alternate protocol") // handle the [Transport.RoundTrip] itself for that one request, as if the // protocol were not registered. func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) { + if err := t.registerProtocol(scheme, rt); err != nil { + panic(err) + } +} + +func (t *Transport) registerProtocol(scheme string, rt RoundTripper) error { t.altMu.Lock() defer t.altMu.Unlock() @@ -928,7 +914,7 @@ func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) { oldMap, _ := t.altProto.Load().(map[string]RoundTripper) if _, exists := oldMap[scheme]; exists { - panic("protocol " + scheme + " already registered") + return errors.New("protocol " + scheme + " already registered") } newMap := maps.Clone(oldMap) if newMap == nil { @@ -936,6 +922,7 @@ func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) { } newMap[scheme] = rt t.altProto.Store(newMap) + return nil } // CloseIdleConnections closes any connections which were previously @@ -2880,7 +2867,7 @@ var errTimeout error = &timeoutError{"net/http: timeout awaiting response header // errRequestCanceled is set to be identical to the one from h2 to facilitate // testing. -var errRequestCanceled = http2errRequestCanceled +var errRequestCanceled = internal.ErrRequestCanceled var errRequestCanceledConn = errors.New("net/http: request canceled while waiting for connection") // TODO: unify? // errRequestDone is used to cancel the round trip Context after a request is successfully done. diff --git a/src/net/http/transport_internal_test.go b/src/net/http/transport_internal_test.go index f86970b248..9884eaab30 100644 --- a/src/net/http/transport_internal_test.go +++ b/src/net/http/transport_internal_test.go @@ -13,6 +13,7 @@ import ( "errors" "io" "net" + "net/http/internal/http2" "net/http/internal/testcert" "strings" "testing" @@ -136,7 +137,7 @@ func TestTransportShouldRetryRequest(t *testing.T) { 2: { pc: &persistConn{reused: true}, req: dummyRequest("POST"), - err: http2ErrNoCachedConn, + err: http2.ErrNoCachedConn, want: true, }, 3: { @@ -243,7 +244,7 @@ func TestTransportBodyAltRewind(t *testing.T) { }, nil } roundTripped = true - return nil, http2noCachedConnError{} + return nil, http2.ErrNoCachedConn }) }, }, diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 799384e5ac..ea9a19ac9a 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -5655,11 +5655,7 @@ timeoutLoop: c := &Client{Transport: tr} idleConns := func() []string { - if mode == http2Mode { - return tr.IdleConnStrsForTesting_h2() - } else { - return tr.IdleConnStrsForTesting() - } + return tr.IdleConnStrsForTesting() } var conn string |
