aboutsummaryrefslogtreecommitdiff
path: root/src/net
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2026-03-03 08:12:25 -0800
committerGopher Robot <gobot@golang.org>2026-03-12 08:13:20 -0700
commit080aa8e9647e5211650f34f3a93fb493afbe396d (patch)
treeab00dcd761d3622f08ff5aa7e2a52ff8c1fd0591 /src/net
parent81908597a8787b09b1da90e7c6d3461b4302820f (diff)
downloadgo-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/net')
-rw-r--r--src/net/http/client.go2
-rw-r--r--src/net/http/client_test.go9
-rw-r--r--src/net/http/clientserver_test.go44
-rw-r--r--src/net/http/export_test.go34
-rw-r--r--src/net/http/h2_bundle.go12437
-rw-r--r--src/net/http/h2_error.go37
-rw-r--r--src/net/http/h2_error_test.go3
-rw-r--r--src/net/http/http.go2
-rw-r--r--src/net/http/http2.go340
-rw-r--r--src/net/http/http_test.go1
-rw-r--r--src/net/http/internal/common.go14
-rw-r--r--src/net/http/internal/http2/api.go158
-rw-r--r--src/net/http/internal/http2/client_conn_pool.go21
-rw-r--r--src/net/http/internal/http2/client_priority_go127.go11
-rw-r--r--src/net/http/internal/http2/clientconn_test.go120
-rw-r--r--src/net/http/internal/http2/config.go132
-rw-r--r--src/net/http/internal/http2/config_go126.go13
-rw-r--r--src/net/http/internal/http2/errors.go28
-rw-r--r--src/net/http/internal/http2/export_test.go19
-rw-r--r--src/net/http/internal/http2/http2.go10
-rw-r--r--src/net/http/internal/http2/http2_test.go56
-rw-r--r--src/net/http/internal/http2/netconn_test.go33
-rw-r--r--src/net/http/internal/http2/server.go358
-rw-r--r--src/net/http/internal/http2/server_internal_test.go17
-rw-r--r--src/net/http/internal/http2/server_test.go152
-rw-r--r--src/net/http/internal/http2/transport.go320
-rw-r--r--src/net/http/internal/http2/transport_internal_test.go35
-rw-r--r--src/net/http/internal/http2/transport_test.go276
-rw-r--r--src/net/http/internal/http2/unencrypted.go32
-rw-r--r--src/net/http/internal/http2/write.go11
-rw-r--r--src/net/http/omithttp2.go67
-rw-r--r--src/net/http/server.go10
-rw-r--r--src/net/http/transport.go47
-rw-r--r--src/net/http/transport_internal_test.go5
-rw-r--r--src/net/http/transport_test.go6
35 files changed, 1334 insertions, 13526 deletions
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