mirror of
https://github.com/golang/go.git
synced 2026-04-04 02:10:08 +09:00
net/http/internal/http2: initial import
This copies the contents of golang.org/x/net/http2 at 38019a2dbc2645a4c06a1e983681eefb041171c8 into net/http/internal/http2. Files which are not built at go1.27 (e.g., config_go125.go) are not included. A "//go:build ignore" comment has been added to each file. For #67810 Change-Id: If3e52767eea31adf4e2fdcff0dfa67e46a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/751300 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Nicholas Husin <husin@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Nicholas Husin <nsh@golang.org>
This commit is contained in:
committed by
Gopher Robot
parent
26d5e16075
commit
c5f43ab046
@@ -165,6 +165,7 @@ 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() {
|
||||
return nil
|
||||
|
||||
55
src/net/http/internal/http2/ascii.go
Normal file
55
src/net/http/internal/http2/ascii.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2021 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "strings"
|
||||
|
||||
// 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 asciiEqualFold(s, t string) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if lower(s[i]) != lower(t[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// lower returns the ASCII lowercase version of b.
|
||||
func lower(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 isASCIIPrint(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 asciiToLower(s string) (lower string, ok bool) {
|
||||
if !isASCIIPrint(s) {
|
||||
return "", false
|
||||
}
|
||||
return strings.ToLower(s), true
|
||||
}
|
||||
97
src/net/http/internal/http2/ascii_test.go
Normal file
97
src/net/http/internal/http2/ascii_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2021 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestASCIIEqualFold(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
a, b string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "simple match",
|
||||
a: "CHUNKED",
|
||||
b: "chunked",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "same string",
|
||||
a: "chunked",
|
||||
b: "chunked",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Unicode Kelvin symbol",
|
||||
a: "chunKed", // This "K" is 'KELVIN SIGN' (\u212A)
|
||||
b: "chunked",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := asciiEqualFold(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("AsciiEqualFold(%q,%q): got %v want %v", tt.a, tt.b, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIPrint(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "ASCII low",
|
||||
in: "This is a space: ' '",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "ASCII high",
|
||||
in: "This is a tilde: '~'",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "ASCII low non-print",
|
||||
in: "This is a unit separator: \x1F",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Ascii high non-print",
|
||||
in: "This is a Delete: \x7F",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Unicode letter",
|
||||
in: "Today it's 280K outside: it's freezing!", // This "K" is 'KELVIN SIGN' (\u212A)
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Unicode emoji",
|
||||
in: "Gophers like 🧀",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isASCIIPrint(tt.in); got != tt.want {
|
||||
t.Errorf("IsASCIIPrint(%q): got %v want %v", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
643
src/net/http/internal/http2/ciphers.go
Normal file
643
src/net/http/internal/http2/ciphers.go
Normal file
@@ -0,0 +1,643 @@
|
||||
// Copyright 2017 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
// A list of the possible cipher suite ids. Taken from
|
||||
// https://www.iana.org/assignments/tls-parameters/tls-parameters.txt
|
||||
|
||||
const (
|
||||
cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000
|
||||
cipher_TLS_RSA_WITH_NULL_MD5 uint16 = 0x0001
|
||||
cipher_TLS_RSA_WITH_NULL_SHA uint16 = 0x0002
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0003
|
||||
cipher_TLS_RSA_WITH_RC4_128_MD5 uint16 = 0x0004
|
||||
cipher_TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x0006
|
||||
cipher_TLS_RSA_WITH_IDEA_CBC_SHA uint16 = 0x0007
|
||||
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0008
|
||||
cipher_TLS_RSA_WITH_DES_CBC_SHA uint16 = 0x0009
|
||||
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000A
|
||||
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000B
|
||||
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA uint16 = 0x000C
|
||||
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x000D
|
||||
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000E
|
||||
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA uint16 = 0x000F
|
||||
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0010
|
||||
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0011
|
||||
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA uint16 = 0x0012
|
||||
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x0013
|
||||
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0014
|
||||
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA uint16 = 0x0015
|
||||
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0016
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0017
|
||||
cipher_TLS_DH_anon_WITH_RC4_128_MD5 uint16 = 0x0018
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0019
|
||||
cipher_TLS_DH_anon_WITH_DES_CBC_SHA uint16 = 0x001A
|
||||
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0x001B
|
||||
// Reserved uint16 = 0x001C-1D
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_SHA uint16 = 0x001E
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA uint16 = 0x001F
|
||||
cipher_TLS_KRB5_WITH_RC4_128_SHA uint16 = 0x0020
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA uint16 = 0x0021
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_MD5 uint16 = 0x0022
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5 uint16 = 0x0023
|
||||
cipher_TLS_KRB5_WITH_RC4_128_MD5 uint16 = 0x0024
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5 uint16 = 0x0025
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA uint16 = 0x0026
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA uint16 = 0x0027
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA uint16 = 0x0028
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 uint16 = 0x0029
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x002A
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5 uint16 = 0x002B
|
||||
cipher_TLS_PSK_WITH_NULL_SHA uint16 = 0x002C
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA uint16 = 0x002D
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA uint16 = 0x002E
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002F
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0030
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0031
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0032
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0033
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA uint16 = 0x0034
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0036
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0037
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0038
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0039
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA uint16 = 0x003A
|
||||
cipher_TLS_RSA_WITH_NULL_SHA256 uint16 = 0x003B
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003C
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x003D
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x003E
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003F
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x0040
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0041
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0042
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0043
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0044
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0045
|
||||
cipher_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
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x0067
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x0068
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x0069
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x006A
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x006B
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256 uint16 = 0x006C
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256 uint16 = 0x006D
|
||||
// Unassigned uint16 = 0x006E-83
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0084
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0085
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0086
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0087
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0088
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0089
|
||||
cipher_TLS_PSK_WITH_RC4_128_SHA uint16 = 0x008A
|
||||
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008B
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA uint16 = 0x008C
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA uint16 = 0x008D
|
||||
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA uint16 = 0x008E
|
||||
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008F
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0090
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0091
|
||||
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA uint16 = 0x0092
|
||||
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x0093
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0094
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0095
|
||||
cipher_TLS_RSA_WITH_SEED_CBC_SHA uint16 = 0x0096
|
||||
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA uint16 = 0x0097
|
||||
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA uint16 = 0x0098
|
||||
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA uint16 = 0x0099
|
||||
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA uint16 = 0x009A
|
||||
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA uint16 = 0x009B
|
||||
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009C
|
||||
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009D
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009E
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009F
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x00A0
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x00A1
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A2
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A3
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A4
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A5
|
||||
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256 uint16 = 0x00A6
|
||||
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384 uint16 = 0x00A7
|
||||
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00A8
|
||||
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00A9
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AA
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AB
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AC
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AD
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00AE
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00AF
|
||||
cipher_TLS_PSK_WITH_NULL_SHA256 uint16 = 0x00B0
|
||||
cipher_TLS_PSK_WITH_NULL_SHA384 uint16 = 0x00B1
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B2
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B3
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA256 uint16 = 0x00B4
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA384 uint16 = 0x00B5
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B6
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B7
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA256 uint16 = 0x00B8
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA384 uint16 = 0x00B9
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BA
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BB
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BC
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BD
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BE
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BF
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C0
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C1
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C2
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C3
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C4
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C5
|
||||
// Unassigned uint16 = 0x00C6-FE
|
||||
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV uint16 = 0x00FF
|
||||
// Unassigned uint16 = 0x01-55,*
|
||||
cipher_TLS_FALLBACK_SCSV uint16 = 0x5600
|
||||
// Unassigned uint16 = 0x5601 - 0xC000
|
||||
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA uint16 = 0xC001
|
||||
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA uint16 = 0xC002
|
||||
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC003
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC004
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC005
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA uint16 = 0xC006
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xC007
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC008
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC009
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC00A
|
||||
cipher_TLS_ECDH_RSA_WITH_NULL_SHA uint16 = 0xC00B
|
||||
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA uint16 = 0xC00C
|
||||
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC00D
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC00E
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC00F
|
||||
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA uint16 = 0xC010
|
||||
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xC011
|
||||
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC012
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC013
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC014
|
||||
cipher_TLS_ECDH_anon_WITH_NULL_SHA uint16 = 0xC015
|
||||
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA uint16 = 0xC016
|
||||
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0xC017
|
||||
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA uint16 = 0xC018
|
||||
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA uint16 = 0xC019
|
||||
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01A
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01B
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01C
|
||||
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA uint16 = 0xC01D
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC01E
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA uint16 = 0xC01F
|
||||
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA uint16 = 0xC020
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC021
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA uint16 = 0xC022
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC023
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC024
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC025
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC026
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC027
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC028
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC029
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC02A
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02B
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02C
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02D
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02E
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02F
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC030
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC031
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC032
|
||||
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA uint16 = 0xC033
|
||||
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0xC034
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0xC035
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0xC036
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0xC037
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0xC038
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA uint16 = 0xC039
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256 uint16 = 0xC03A
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384 uint16 = 0xC03B
|
||||
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03C
|
||||
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03D
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03E
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03F
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC040
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC041
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC042
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC043
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC044
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC045
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC046
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC047
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC048
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC049
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04A
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04B
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04C
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04D
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04E
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04F
|
||||
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC050
|
||||
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC051
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC052
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC053
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC054
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC055
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC056
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC057
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC058
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC059
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05A
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05B
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05C
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05D
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05E
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05F
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC060
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC061
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC062
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC063
|
||||
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC064
|
||||
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC065
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC066
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC067
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC068
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC069
|
||||
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06A
|
||||
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06B
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06C
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06D
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06E
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06F
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC070
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC071
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC072
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC073
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC074
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC075
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC076
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC077
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC078
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC079
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07A
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07B
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07C
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07D
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07E
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07F
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC080
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC081
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC082
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC083
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC084
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC085
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC086
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC087
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC088
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC089
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08A
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08B
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08C
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08D
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08E
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08F
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC090
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC091
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC092
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC093
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC094
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC095
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC096
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC097
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC098
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC099
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC09A
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC09B
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM uint16 = 0xC09C
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM uint16 = 0xC09D
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CCM uint16 = 0xC09E
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CCM uint16 = 0xC09F
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A0
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A1
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A2
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A3
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM uint16 = 0xC0A4
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM uint16 = 0xC0A5
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CCM uint16 = 0xC0A6
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CCM uint16 = 0xC0A7
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM_8 uint16 = 0xC0A8
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM_8 uint16 = 0xC0A9
|
||||
cipher_TLS_PSK_DHE_WITH_AES_128_CCM_8 uint16 = 0xC0AA
|
||||
cipher_TLS_PSK_DHE_WITH_AES_256_CCM_8 uint16 = 0xC0AB
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM uint16 = 0xC0AC
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM uint16 = 0xC0AD
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 uint16 = 0xC0AE
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 uint16 = 0xC0AF
|
||||
// Unassigned uint16 = 0xC0B0-FF
|
||||
// Unassigned uint16 = 0xC1-CB,*
|
||||
// Unassigned uint16 = 0xCC00-A7
|
||||
cipher_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA8
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA9
|
||||
cipher_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAA
|
||||
cipher_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAB
|
||||
cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAC
|
||||
cipher_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAD
|
||||
cipher_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 isBadCipher(cipher uint16) bool {
|
||||
switch cipher {
|
||||
case cipher_TLS_NULL_WITH_NULL_NULL,
|
||||
cipher_TLS_RSA_WITH_NULL_MD5,
|
||||
cipher_TLS_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_RSA_WITH_RC4_128_MD5,
|
||||
cipher_TLS_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||
cipher_TLS_RSA_WITH_IDEA_CBC_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_DH_anon_WITH_RC4_128_MD5,
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_RC4_128_SHA,
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_MD5,
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5,
|
||||
cipher_TLS_KRB5_WITH_RC4_128_MD5,
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_NULL_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM,
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM,
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM_8,
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM_8,
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM,
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM,
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM_8,
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM_8:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
311
src/net/http/internal/http2/ciphers_test.go
Normal file
311
src/net/http/internal/http2/ciphers_test.go
Normal file
@@ -0,0 +1,311 @@
|
||||
// Copyright 2017 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIsBadCipherBad(t *testing.T) {
|
||||
for _, c := range badCiphers {
|
||||
if !isBadCipher(c) {
|
||||
t.Errorf("Wrong result for isBadCipher(%d), want true", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verify we don't give false positives on ciphers not on blacklist
|
||||
func TestIsBadCipherGood(t *testing.T) {
|
||||
goodCiphers := map[uint16]string{
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CCM: "cipher_TLS_DHE_RSA_WITH_AES_256_CCM",
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM: "cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
|
||||
cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: "cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
|
||||
}
|
||||
for c, name := range goodCiphers {
|
||||
if isBadCipher(c) {
|
||||
t.Errorf("Wrong result for isBadCipher(%d) %s, want false", c, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copied from https://httpwg.org/specs/rfc7540.html#BadCipherSuites,
|
||||
var badCiphers = []uint16{
|
||||
cipher_TLS_NULL_WITH_NULL_NULL,
|
||||
cipher_TLS_RSA_WITH_NULL_MD5,
|
||||
cipher_TLS_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_RSA_WITH_RC4_128_MD5,
|
||||
cipher_TLS_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||
cipher_TLS_RSA_WITH_IDEA_CBC_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_DH_anon_WITH_RC4_128_MD5,
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_RC4_128_SHA,
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_MD5,
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5,
|
||||
cipher_TLS_KRB5_WITH_RC4_128_MD5,
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_NULL_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM,
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM,
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM_8,
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM_8,
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM,
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM,
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM_8,
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM_8,
|
||||
}
|
||||
313
src/net/http/internal/http2/client_conn_pool.go
Normal file
313
src/net/http/internal/http2/client_conn_pool.go
Normal file
@@ -0,0 +1,313 @@
|
||||
// Copyright 2015 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 ignore
|
||||
|
||||
// Transport code's client connection pooling.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||
type ClientConnPool 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 *http.Request, addr string) (*ClientConn, error)
|
||||
MarkDead(*ClientConn)
|
||||
}
|
||||
|
||||
// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
|
||||
// implementations which can close their idle connections.
|
||||
type clientConnPoolIdleCloser interface {
|
||||
ClientConnPool
|
||||
closeIdleConnections()
|
||||
}
|
||||
|
||||
var (
|
||||
_ clientConnPoolIdleCloser = (*clientConnPool)(nil)
|
||||
_ clientConnPoolIdleCloser = noDialClientConnPool{}
|
||||
)
|
||||
|
||||
// TODO: use singleflight for dialing and addConnCalls?
|
||||
type clientConnPool struct {
|
||||
t *Transport
|
||||
|
||||
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][]*ClientConn // key is host:port
|
||||
dialing map[string]*dialCall // currently in-flight dials
|
||||
keys map[*ClientConn][]string
|
||||
addConnCalls map[string]*addConnCall // in-flight addConnIfNeeded calls
|
||||
}
|
||||
|
||||
func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||
return p.getClientConn(req, addr, dialOnMiss)
|
||||
}
|
||||
|
||||
const (
|
||||
dialOnMiss = true
|
||||
noDialOnMiss = false
|
||||
)
|
||||
|
||||
func (p *clientConnPool) getClientConn(req *http.Request, 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)
|
||||
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 {
|
||||
traceGetConn(req, addr)
|
||||
}
|
||||
cc.getConnCalled = false
|
||||
p.mu.Unlock()
|
||||
return cc, nil
|
||||
}
|
||||
}
|
||||
if !dialOnMiss {
|
||||
p.mu.Unlock()
|
||||
return nil, ErrNoCachedConn
|
||||
}
|
||||
traceGetConn(req, addr)
|
||||
call := p.getStartDialLocked(req.Context(), addr)
|
||||
p.mu.Unlock()
|
||||
<-call.done
|
||||
if shouldRetryDial(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 dialCall struct {
|
||||
_ incomparable
|
||||
p *clientConnPool
|
||||
// the context associated with the request
|
||||
// that created this dialCall
|
||||
ctx context.Context
|
||||
done chan struct{} // closed when done
|
||||
res *ClientConn // valid after done is closed
|
||||
err error // valid after done is closed
|
||||
}
|
||||
|
||||
// requires p.mu is held.
|
||||
func (p *clientConnPool) getStartDialLocked(ctx context.Context, addr string) *dialCall {
|
||||
if call, ok := p.dialing[addr]; ok {
|
||||
// A dial is already in-flight. Don't start another.
|
||||
return call
|
||||
}
|
||||
call := &dialCall{p: p, done: make(chan struct{}), ctx: ctx}
|
||||
if p.dialing == nil {
|
||||
p.dialing = make(map[string]*dialCall)
|
||||
}
|
||||
p.dialing[addr] = call
|
||||
go call.dial(call.ctx, addr)
|
||||
return call
|
||||
}
|
||||
|
||||
// run in its own goroutine.
|
||||
func (c *dialCall) 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 *clientConnPool) addConnIfNeeded(key string, t *Transport, 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]*addConnCall)
|
||||
}
|
||||
call = &addConnCall{
|
||||
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 addConnCall struct {
|
||||
_ incomparable
|
||||
p *clientConnPool
|
||||
done chan struct{} // closed when done
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *addConnCall) run(t *Transport, 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 *clientConnPool) addConnLocked(key string, cc *ClientConn) {
|
||||
for _, v := range p.conns[key] {
|
||||
if v == cc {
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.conns == nil {
|
||||
p.conns = make(map[string][]*ClientConn)
|
||||
}
|
||||
if p.keys == nil {
|
||||
p.keys = make(map[*ClientConn][]string)
|
||||
}
|
||||
p.conns[key] = append(p.conns[key], cc)
|
||||
p.keys[cc] = append(p.keys[cc], key)
|
||||
}
|
||||
|
||||
func (p *clientConnPool) MarkDead(cc *ClientConn) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
for _, key := range p.keys[cc] {
|
||||
vv, ok := p.conns[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
newList := filterOutClientConn(vv, cc)
|
||||
if len(newList) > 0 {
|
||||
p.conns[key] = newList
|
||||
} else {
|
||||
delete(p.conns, key)
|
||||
}
|
||||
}
|
||||
delete(p.keys, cc)
|
||||
}
|
||||
|
||||
func (p *clientConnPool) 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 filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
|
||||
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 noDialClientConnPool struct{ *clientConnPool }
|
||||
|
||||
func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||
return p.getClientConn(req, addr, noDialOnMiss)
|
||||
}
|
||||
|
||||
// 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 shouldRetryDial(call *dialCall, req *http.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
|
||||
}
|
||||
13
src/net/http/internal/http2/client_priority_go127.go
Normal file
13
src/net/http/internal/http2/client_priority_go127.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "net/http"
|
||||
|
||||
func clientPriorityDisabled(s *http.Server) bool {
|
||||
return s.DisableClientPriority
|
||||
}
|
||||
574
src/net/http/internal/http2/clientconn_test.go
Normal file
574
src/net/http/internal/http2/clientconn_test.go
Normal file
@@ -0,0 +1,574 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
// Infrastructure for testing ClientConn.RoundTrip.
|
||||
// Put actual tests in transport_test.go.
|
||||
|
||||
package http2_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
. "golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/net/internal/gate"
|
||||
)
|
||||
|
||||
// TestTestClientConn demonstrates usage of testClientConn.
|
||||
func TestTestClientConn(t *testing.T) { synctestTest(t, testTestClientConn) }
|
||||
func testTestClientConn(t testing.TB) {
|
||||
// newTestClientConn creates a *ClientConn and surrounding test infrastructure.
|
||||
tc := newTestClientConn(t)
|
||||
|
||||
// tc.greet reads the client's initial SETTINGS and WINDOW_UPDATE frames,
|
||||
// and sends a SETTINGS frame to the client.
|
||||
//
|
||||
// Additional settings may be provided as optional parameters to greet.
|
||||
tc.greet()
|
||||
|
||||
// Request bodies must either be constant (bytes.Buffer, strings.Reader)
|
||||
// or created with newRequestBody.
|
||||
body := tc.newRequestBody()
|
||||
body.writeBytes(10) // 10 arbitrary bytes...
|
||||
body.closeWithError(io.EOF) // ...followed by EOF.
|
||||
|
||||
// tc.roundTrip calls RoundTrip, but does not wait for it to return.
|
||||
// It returns a testRoundTrip.
|
||||
req, _ := http.NewRequest("PUT", "https://dummy.tld/", body)
|
||||
rt := tc.roundTrip(req)
|
||||
|
||||
// tc has a number of methods to check for expected frames sent.
|
||||
// Here, we look for headers and the request body.
|
||||
tc.wantHeaders(wantHeader{
|
||||
streamID: rt.streamID(),
|
||||
endStream: false,
|
||||
header: http.Header{
|
||||
":authority": []string{"dummy.tld"},
|
||||
":method": []string{"PUT"},
|
||||
":path": []string{"/"},
|
||||
},
|
||||
})
|
||||
// Expect 10 bytes of request body in DATA frames.
|
||||
tc.wantData(wantData{
|
||||
streamID: rt.streamID(),
|
||||
endStream: true,
|
||||
size: 10,
|
||||
multiple: true,
|
||||
})
|
||||
|
||||
// tc.writeHeaders sends a HEADERS frame back to the client.
|
||||
tc.writeHeaders(HeadersFrameParam{
|
||||
StreamID: rt.streamID(),
|
||||
EndHeaders: true,
|
||||
EndStream: true,
|
||||
BlockFragment: tc.makeHeaderBlockFragment(
|
||||
":status", "200",
|
||||
),
|
||||
})
|
||||
|
||||
// Now that we've received headers, RoundTrip has finished.
|
||||
// testRoundTrip has various methods to examine the response,
|
||||
// or to fetch the response and/or error returned by RoundTrip
|
||||
rt.wantStatus(200)
|
||||
rt.wantBody(nil)
|
||||
}
|
||||
|
||||
// A testClientConn allows testing ClientConn.RoundTrip against a fake server.
|
||||
//
|
||||
// A test using testClientConn consists of:
|
||||
// - actions on the client (calling RoundTrip, making data available to Request.Body);
|
||||
// - validation of frames sent by the client to the server; and
|
||||
// - providing frames from the server to the client.
|
||||
//
|
||||
// testClientConn manages synchronization, so tests can generally be written as
|
||||
// a linear sequence of actions and validations without additional synchronization.
|
||||
type testClientConn struct {
|
||||
t testing.TB
|
||||
|
||||
tr *Transport
|
||||
fr *Framer
|
||||
cc *ClientConn
|
||||
testConnFramer
|
||||
|
||||
encbuf bytes.Buffer
|
||||
enc *hpack.Encoder
|
||||
|
||||
roundtrips []*testRoundTrip
|
||||
|
||||
netconn *synctestNetConn
|
||||
}
|
||||
|
||||
func newTestClientConnFromClientConn(t testing.TB, tr *Transport, cc *ClientConn) *testClientConn {
|
||||
tc := &testClientConn{
|
||||
t: t,
|
||||
tr: tr,
|
||||
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
|
||||
}
|
||||
|
||||
srv.SetReadDeadline(time.Now())
|
||||
srv.autoWait = true
|
||||
tc.netconn = srv
|
||||
tc.enc = hpack.NewEncoder(&tc.encbuf)
|
||||
tc.fr = NewFramer(srv, srv)
|
||||
tc.testConnFramer = testConnFramer{
|
||||
t: t,
|
||||
fr: tc.fr,
|
||||
dec: hpack.NewDecoder(InitialHeaderTableSize, nil),
|
||||
}
|
||||
tc.fr.SetMaxReadFrameSize(10 << 20)
|
||||
t.Cleanup(func() {
|
||||
tc.closeWrite()
|
||||
})
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *testClientConn) readClientPreface() {
|
||||
tc.t.Helper()
|
||||
// Read the client's HTTP/2 preface, sent prior to any HTTP/2 frames.
|
||||
buf := make([]byte, len(ClientPreface))
|
||||
if _, err := io.ReadFull(tc.netconn, buf); err != nil {
|
||||
tc.t.Fatalf("reading preface: %v", err)
|
||||
}
|
||||
if !bytes.Equal(buf, []byte(ClientPreface)) {
|
||||
tc.t.Fatalf("client preface: %q, want %q", buf, ClientPreface)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestClientConn(t testing.TB, opts ...any) *testClientConn {
|
||||
t.Helper()
|
||||
|
||||
tt := newTestTransport(t, opts...)
|
||||
const singleUse = false
|
||||
_, err := tt.tr.TestNewClientConn(nil, singleUse, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("newClientConn: %v", err)
|
||||
}
|
||||
|
||||
return tt.getConn()
|
||||
}
|
||||
|
||||
// hasFrame reports whether a frame is available to be read.
|
||||
func (tc *testClientConn) hasFrame() bool {
|
||||
return len(tc.netconn.Peek()) > 0
|
||||
}
|
||||
|
||||
// isClosed reports whether the peer has closed the connection.
|
||||
func (tc *testClientConn) isClosed() bool {
|
||||
return tc.netconn.IsClosedByPeer()
|
||||
}
|
||||
|
||||
// closeWrite causes the net.Conn used by the ClientConn to return a error
|
||||
// from Read calls.
|
||||
func (tc *testClientConn) closeWrite() {
|
||||
tc.netconn.Close()
|
||||
}
|
||||
|
||||
// closeWrite causes the net.Conn used by the ClientConn to return a error
|
||||
// from Write calls.
|
||||
func (tc *testClientConn) closeWriteWithError(err error) {
|
||||
tc.netconn.loc.setReadError(io.EOF)
|
||||
tc.netconn.loc.setWriteError(err)
|
||||
}
|
||||
|
||||
// testRequestBody is a Request.Body for use in tests.
|
||||
type testRequestBody struct {
|
||||
tc *testClientConn
|
||||
gate gate.Gate
|
||||
|
||||
// At most one of buf or bytes can be set at any given time:
|
||||
buf bytes.Buffer // specific bytes to read from the body
|
||||
bytes int // body contains this many arbitrary bytes
|
||||
|
||||
err error // read error (comes after any available bytes)
|
||||
}
|
||||
|
||||
func (tc *testClientConn) newRequestBody() *testRequestBody {
|
||||
b := &testRequestBody{
|
||||
tc: tc,
|
||||
gate: gate.New(false),
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *testRequestBody) unlock() {
|
||||
b.gate.Unlock(b.buf.Len() > 0 || b.bytes > 0 || b.err != nil)
|
||||
}
|
||||
|
||||
// Read is called by the ClientConn to read from a request body.
|
||||
func (b *testRequestBody) Read(p []byte) (n int, _ error) {
|
||||
if err := b.gate.WaitAndLock(context.Background()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer b.unlock()
|
||||
switch {
|
||||
case b.buf.Len() > 0:
|
||||
return b.buf.Read(p)
|
||||
case b.bytes > 0:
|
||||
if len(p) > b.bytes {
|
||||
p = p[:b.bytes]
|
||||
}
|
||||
b.bytes -= len(p)
|
||||
for i := range p {
|
||||
p[i] = 'A'
|
||||
}
|
||||
return len(p), nil
|
||||
default:
|
||||
return 0, b.err
|
||||
}
|
||||
}
|
||||
|
||||
// Close is called by the ClientConn when it is done reading from a request body.
|
||||
func (b *testRequestBody) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeBytes adds n arbitrary bytes to the body.
|
||||
func (b *testRequestBody) writeBytes(n int) {
|
||||
defer synctest.Wait()
|
||||
b.gate.Lock()
|
||||
defer b.unlock()
|
||||
b.bytes += n
|
||||
b.checkWrite()
|
||||
synctest.Wait()
|
||||
}
|
||||
|
||||
// Write adds bytes to the body.
|
||||
func (b *testRequestBody) Write(p []byte) (int, error) {
|
||||
defer synctest.Wait()
|
||||
b.gate.Lock()
|
||||
defer b.unlock()
|
||||
n, err := b.buf.Write(p)
|
||||
b.checkWrite()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (b *testRequestBody) checkWrite() {
|
||||
if b.bytes > 0 && b.buf.Len() > 0 {
|
||||
b.tc.t.Fatalf("can't interleave Write and writeBytes on request body")
|
||||
}
|
||||
if b.err != nil {
|
||||
b.tc.t.Fatalf("can't write to request body after closeWithError")
|
||||
}
|
||||
}
|
||||
|
||||
// closeWithError sets an error which will be returned by Read.
|
||||
func (b *testRequestBody) closeWithError(err error) {
|
||||
defer synctest.Wait()
|
||||
b.gate.Lock()
|
||||
defer b.unlock()
|
||||
b.err = err
|
||||
}
|
||||
|
||||
// roundTrip starts a RoundTrip call.
|
||||
//
|
||||
// (Note that the RoundTrip won't complete until response headers are received,
|
||||
// the request times out, or some other terminal condition is reached.)
|
||||
func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
req = req.WithContext(ctx)
|
||||
rt := &testRoundTrip{
|
||||
t: tc.t,
|
||||
donec: make(chan struct{}),
|
||||
cancel: cancel,
|
||||
}
|
||||
tc.roundtrips = append(tc.roundtrips, rt)
|
||||
go func() {
|
||||
defer close(rt.donec)
|
||||
rt.resp, rt.respErr = tc.cc.TestRoundTrip(req, func(streamID uint32) {
|
||||
rt.id.Store(streamID)
|
||||
})
|
||||
}()
|
||||
synctest.Wait()
|
||||
|
||||
tc.t.Cleanup(func() {
|
||||
if !rt.done() {
|
||||
return
|
||||
}
|
||||
res, _ := rt.result()
|
||||
if res != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
})
|
||||
|
||||
return rt
|
||||
}
|
||||
|
||||
func (tc *testClientConn) greet(settings ...Setting) {
|
||||
tc.wantFrameType(FrameSettings)
|
||||
tc.wantFrameType(FrameWindowUpdate)
|
||||
tc.writeSettings(settings...)
|
||||
tc.writeSettingsAck()
|
||||
tc.wantFrameType(FrameSettings) // acknowledgement
|
||||
}
|
||||
|
||||
// makeHeaderBlockFragment encodes headers in a form suitable for inclusion
|
||||
// in a HEADERS or CONTINUATION frame.
|
||||
//
|
||||
// It takes a list of alternating names and values.
|
||||
func (tc *testClientConn) makeHeaderBlockFragment(s ...string) []byte {
|
||||
if len(s)%2 != 0 {
|
||||
tc.t.Fatalf("uneven list of header name/value pairs")
|
||||
}
|
||||
tc.encbuf.Reset()
|
||||
for i := 0; i < len(s); i += 2 {
|
||||
tc.enc.WriteField(hpack.HeaderField{Name: s[i], Value: s[i+1]})
|
||||
}
|
||||
return tc.encbuf.Bytes()
|
||||
}
|
||||
|
||||
// inflowWindow returns the amount of inbound flow control available for a stream,
|
||||
// or for the connection if streamID is 0.
|
||||
func (tc *testClientConn) inflowWindow(streamID uint32) int32 {
|
||||
w, err := tc.cc.TestInflowWindow(streamID)
|
||||
if err != nil {
|
||||
tc.t.Error(err)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// testRoundTrip manages a RoundTrip in progress.
|
||||
type testRoundTrip struct {
|
||||
t testing.TB
|
||||
resp *http.Response
|
||||
respErr error
|
||||
donec chan struct{}
|
||||
id atomic.Uint32
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// streamID returns the HTTP/2 stream ID of the request.
|
||||
func (rt *testRoundTrip) streamID() uint32 {
|
||||
id := rt.id.Load()
|
||||
if id == 0 {
|
||||
panic("stream ID unknown")
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// done reports whether RoundTrip has returned.
|
||||
func (rt *testRoundTrip) done() bool {
|
||||
select {
|
||||
case <-rt.donec:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// result returns the result of the RoundTrip.
|
||||
func (rt *testRoundTrip) result() (*http.Response, error) {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
synctest.Wait()
|
||||
select {
|
||||
case <-rt.donec:
|
||||
default:
|
||||
t.Fatalf("RoundTrip is not done; want it to be")
|
||||
}
|
||||
return rt.resp, rt.respErr
|
||||
}
|
||||
|
||||
// response returns the response of a successful RoundTrip.
|
||||
// If the RoundTrip unexpectedly failed, it calls t.Fatal.
|
||||
func (rt *testRoundTrip) response() *http.Response {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
resp, err := rt.result()
|
||||
if err != nil {
|
||||
t.Fatalf("RoundTrip returned unexpected error: %v", rt.respErr)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatalf("RoundTrip returned nil *Response and nil error")
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// err returns the (possibly nil) error result of RoundTrip.
|
||||
func (rt *testRoundTrip) err() error {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
_, err := rt.result()
|
||||
return err
|
||||
}
|
||||
|
||||
// wantStatus indicates the expected response StatusCode.
|
||||
func (rt *testRoundTrip) wantStatus(want int) {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
if got := rt.response().StatusCode; got != want {
|
||||
t.Fatalf("got response status %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// readBody reads the contents of the response body.
|
||||
func (rt *testRoundTrip) readBody() ([]byte, error) {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
return io.ReadAll(rt.response().Body)
|
||||
}
|
||||
|
||||
// wantBody indicates the expected response body.
|
||||
// (Note that this consumes the body.)
|
||||
func (rt *testRoundTrip) wantBody(want []byte) {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
got, err := rt.readBody()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error reading response body: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected response body:\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// wantHeaders indicates the expected response headers.
|
||||
func (rt *testRoundTrip) wantHeaders(want http.Header) {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
res := rt.response()
|
||||
if diff := diffHeaders(res.Header, want); diff != "" {
|
||||
t.Fatalf("unexpected response headers:\n%v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// wantTrailers indicates the expected response trailers.
|
||||
func (rt *testRoundTrip) wantTrailers(want http.Header) {
|
||||
t := rt.t
|
||||
t.Helper()
|
||||
res := rt.response()
|
||||
if diff := diffHeaders(res.Trailer, want); diff != "" {
|
||||
t.Fatalf("unexpected response trailers:\n%v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func diffHeaders(got, want http.Header) string {
|
||||
// nil and 0-length non-nil are equal.
|
||||
if len(got) == 0 && len(want) == 0 {
|
||||
return ""
|
||||
}
|
||||
// We could do a more sophisticated diff here.
|
||||
// DeepEqual is good enough for now.
|
||||
if reflect.DeepEqual(got, want) {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("got: %v\nwant: %v", got, want)
|
||||
}
|
||||
|
||||
// A testTransport allows testing Transport.RoundTrip against fake servers.
|
||||
// Tests that aren't specifically exercising RoundTrip's retry loop or connection pooling
|
||||
// should use testClientConn instead.
|
||||
type testTransport struct {
|
||||
t testing.TB
|
||||
tr *Transport
|
||||
|
||||
ccs []*testClientConn
|
||||
}
|
||||
|
||||
func newTestTransport(t testing.TB, opts ...any) *testTransport {
|
||||
tt := &testTransport{
|
||||
t: t,
|
||||
}
|
||||
|
||||
tr := &Transport{}
|
||||
for _, o := range opts {
|
||||
switch o := o.(type) {
|
||||
case func(*http.Transport):
|
||||
o(tr.TestTransport())
|
||||
case func(*Transport):
|
||||
o(tr)
|
||||
case *Transport:
|
||||
tr = o
|
||||
}
|
||||
}
|
||||
tt.tr = tr
|
||||
|
||||
tr.TestSetNewClientConnHook(func(cc *ClientConn) {
|
||||
tc := newTestClientConnFromClientConn(t, tr, cc)
|
||||
tt.ccs = append(tt.ccs, tc)
|
||||
})
|
||||
|
||||
t.Cleanup(func() {
|
||||
synctest.Wait()
|
||||
if len(tt.ccs) > 0 {
|
||||
t.Fatalf("%v test ClientConns created, but not examined by test", len(tt.ccs))
|
||||
}
|
||||
})
|
||||
|
||||
return tt
|
||||
}
|
||||
|
||||
func (tt *testTransport) hasConn() bool {
|
||||
return len(tt.ccs) > 0
|
||||
}
|
||||
|
||||
func (tt *testTransport) getConn() *testClientConn {
|
||||
tt.t.Helper()
|
||||
if len(tt.ccs) == 0 {
|
||||
tt.t.Fatalf("no new ClientConns created; wanted one")
|
||||
}
|
||||
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 {
|
||||
rt := &testRoundTrip{
|
||||
t: tt.t,
|
||||
donec: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
defer close(rt.donec)
|
||||
rt.resp, rt.respErr = tt.tr.RoundTrip(req)
|
||||
}()
|
||||
synctest.Wait()
|
||||
|
||||
tt.t.Cleanup(func() {
|
||||
if !rt.done() {
|
||||
return
|
||||
}
|
||||
res, _ := rt.result()
|
||||
if res != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
})
|
||||
|
||||
return rt
|
||||
}
|
||||
171
src/net/http/internal/http2/config.go
Normal file
171
src/net/http/internal/http2/config.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
fillNetHTTPConfig(&conf, h1.HTTP2)
|
||||
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,
|
||||
}
|
||||
|
||||
// Unlike most config fields, where out-of-range values revert to the default,
|
||||
// Transport.MaxReadFrameSize clips.
|
||||
if conf.MaxReadFrameSize < minMaxFrameSize {
|
||||
conf.MaxReadFrameSize = minMaxFrameSize
|
||||
} else if conf.MaxReadFrameSize > maxFrameSize {
|
||||
conf.MaxReadFrameSize = maxFrameSize
|
||||
}
|
||||
|
||||
if h2.t1 != nil {
|
||||
fillNetHTTPConfig(&conf, h2.t1.HTTP2)
|
||||
}
|
||||
setConfigDefaults(&conf, false)
|
||||
return conf
|
||||
}
|
||||
|
||||
func setDefault[T ~int | ~int32 | ~uint32 | ~int64](v *T, minval, maxval, defval T) {
|
||||
if *v < minval || *v > maxval {
|
||||
*v = 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)
|
||||
if server {
|
||||
setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, 1<<20)
|
||||
} else {
|
||||
setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, transportDefaultConnFlow)
|
||||
}
|
||||
if server {
|
||||
setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, 1<<20)
|
||||
} else {
|
||||
setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, transportDefaultStreamFlow)
|
||||
}
|
||||
setDefault(&conf.MaxReadFrameSize, minMaxFrameSize, maxFrameSize, defaultMaxReadFrameSize)
|
||||
setDefault(&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 adjustHTTP1MaxHeaderSize(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 fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) {
|
||||
if h2 == nil {
|
||||
return
|
||||
}
|
||||
if h2.MaxConcurrentStreams != 0 {
|
||||
conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
|
||||
}
|
||||
if http2ConfigStrictMaxConcurrentRequests(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
|
||||
}
|
||||
}
|
||||
15
src/net/http/internal/http2/config_go126.go
Normal file
15
src/net/http/internal/http2/config_go126.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func http2ConfigStrictMaxConcurrentRequests(h2 *http.HTTP2Config) bool {
|
||||
return h2.StrictMaxConcurrentRequests
|
||||
}
|
||||
101
src/net/http/internal/http2/config_test.go
Normal file
101
src/net/http/internal/http2/config_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func TestConfigServerSettings(t *testing.T) { synctestTest(t, testConfigServerSettings) }
|
||||
func testConfigServerSettings(t testing.TB) {
|
||||
config := &http.HTTP2Config{
|
||||
MaxConcurrentStreams: 1,
|
||||
MaxDecoderHeaderTableSize: 1<<20 + 2,
|
||||
MaxEncoderHeaderTableSize: 1<<20 + 3,
|
||||
MaxReadFrameSize: 1<<20 + 4,
|
||||
MaxReceiveBufferPerConnection: 64<<10 + 5,
|
||||
MaxReceiveBufferPerStream: 64<<10 + 6,
|
||||
}
|
||||
const maxHeaderBytes = 4096 + 7
|
||||
st := newServerTester(t, nil, func(s *http.Server) {
|
||||
s.MaxHeaderBytes = maxHeaderBytes
|
||||
s.HTTP2 = config
|
||||
})
|
||||
st.writePreface()
|
||||
st.writeSettings()
|
||||
st.wantSettings(map[SettingID]uint32{
|
||||
SettingMaxConcurrentStreams: uint32(config.MaxConcurrentStreams),
|
||||
SettingHeaderTableSize: uint32(config.MaxDecoderHeaderTableSize),
|
||||
SettingInitialWindowSize: uint32(config.MaxReceiveBufferPerStream),
|
||||
SettingMaxFrameSize: uint32(config.MaxReadFrameSize),
|
||||
SettingMaxHeaderListSize: maxHeaderBytes + (32 * 10),
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigTransportSettings(t *testing.T) { synctestTest(t, testConfigTransportSettings) }
|
||||
func testConfigTransportSettings(t testing.TB) {
|
||||
config := &http.HTTP2Config{
|
||||
MaxConcurrentStreams: 1, // ignored by Transport
|
||||
MaxDecoderHeaderTableSize: 1<<20 + 2,
|
||||
MaxEncoderHeaderTableSize: 1<<20 + 3,
|
||||
MaxReadFrameSize: 1<<20 + 4,
|
||||
MaxReceiveBufferPerConnection: 64<<10 + 5,
|
||||
MaxReceiveBufferPerStream: 64<<10 + 6,
|
||||
}
|
||||
const maxHeaderBytes = 4096 + 7
|
||||
tc := newTestClientConn(t, func(tr *http.Transport) {
|
||||
tr.HTTP2 = config
|
||||
tr.MaxResponseHeaderBytes = maxHeaderBytes
|
||||
})
|
||||
tc.wantSettings(map[SettingID]uint32{
|
||||
SettingHeaderTableSize: uint32(config.MaxDecoderHeaderTableSize),
|
||||
SettingInitialWindowSize: uint32(config.MaxReceiveBufferPerStream),
|
||||
SettingMaxFrameSize: uint32(config.MaxReadFrameSize),
|
||||
SettingMaxHeaderListSize: maxHeaderBytes + (32 * 10),
|
||||
})
|
||||
tc.wantWindowUpdate(0, uint32(config.MaxReceiveBufferPerConnection))
|
||||
}
|
||||
|
||||
func TestConfigPingTimeoutServer(t *testing.T) { synctestTest(t, testConfigPingTimeoutServer) }
|
||||
func testConfigPingTimeoutServer(t testing.TB) {
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
}, func(s *Server) {
|
||||
s.ReadIdleTimeout = 2 * time.Second
|
||||
s.PingTimeout = 3 * time.Second
|
||||
})
|
||||
st.greet()
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
_ = readFrame[*PingFrame](t, st)
|
||||
time.Sleep(3 * time.Second)
|
||||
st.wantClosed()
|
||||
}
|
||||
|
||||
func TestConfigPingTimeoutTransport(t *testing.T) { synctestTest(t, testConfigPingTimeoutTransport) }
|
||||
func testConfigPingTimeoutTransport(t testing.TB) {
|
||||
tc := newTestClientConn(t, func(tr *Transport) {
|
||||
tr.ReadIdleTimeout = 2 * time.Second
|
||||
tr.PingTimeout = 3 * time.Second
|
||||
})
|
||||
tc.greet()
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
|
||||
rt := tc.roundTrip(req)
|
||||
tc.wantFrameType(FrameHeaders)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
tc.wantFrameType(FramePing)
|
||||
time.Sleep(3 * time.Second)
|
||||
err := rt.err()
|
||||
if err == nil {
|
||||
t.Fatalf("expected connection to close")
|
||||
}
|
||||
}
|
||||
440
src/net/http/internal/http2/connframes_test.go
Normal file
440
src/net/http/internal/http2/connframes_test.go
Normal file
@@ -0,0 +1,440 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
. "golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
)
|
||||
|
||||
type testConnFramer struct {
|
||||
t testing.TB
|
||||
fr *Framer
|
||||
dec *hpack.Decoder
|
||||
}
|
||||
|
||||
// readFrame reads the next frame.
|
||||
// It returns nil if the conn is closed or no frames are available.
|
||||
func (tf *testConnFramer) readFrame() Frame {
|
||||
tf.t.Helper()
|
||||
fr, err := tf.fr.ReadFrame()
|
||||
if err == io.EOF || err == os.ErrDeadlineExceeded {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
tf.t.Fatalf("ReadFrame: %v", err)
|
||||
}
|
||||
return fr
|
||||
}
|
||||
|
||||
type readFramer interface {
|
||||
readFrame() Frame
|
||||
}
|
||||
|
||||
// readFrame reads a frame of a specific type.
|
||||
func readFrame[T any](t testing.TB, framer readFramer) T {
|
||||
t.Helper()
|
||||
var v T
|
||||
fr := framer.readFrame()
|
||||
if fr == nil {
|
||||
t.Fatalf("got no frame, want frame %T", v)
|
||||
}
|
||||
v, ok := fr.(T)
|
||||
if !ok {
|
||||
t.Fatalf("got frame %T, want %T", fr, v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// wantFrameType reads the next frame.
|
||||
// It produces an error if the frame type is not the expected value.
|
||||
func (tf *testConnFramer) wantFrameType(want FrameType) {
|
||||
tf.t.Helper()
|
||||
fr := tf.readFrame()
|
||||
if fr == nil {
|
||||
tf.t.Fatalf("got no frame, want frame %v", want)
|
||||
}
|
||||
if got := fr.Header().Type; got != want {
|
||||
tf.t.Fatalf("got frame %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// wantUnorderedFrames reads frames until every condition in want has been satisfied.
|
||||
//
|
||||
// want is a list of func(*SomeFrame) bool.
|
||||
// wantUnorderedFrames will call each func with frames of the appropriate type
|
||||
// until the func returns true.
|
||||
// It calls t.Fatal if an unexpected frame is received (no func has that frame type,
|
||||
// or all funcs with that type have returned true), or if the framer runs out of frames
|
||||
// with unsatisfied funcs.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Read a SETTINGS frame, and any number of DATA frames for a stream.
|
||||
// // The SETTINGS frame may appear anywhere in the sequence.
|
||||
// // The last DATA frame must indicate the end of the stream.
|
||||
// tf.wantUnorderedFrames(
|
||||
// func(f *SettingsFrame) bool {
|
||||
// return true
|
||||
// },
|
||||
// func(f *DataFrame) bool {
|
||||
// return f.StreamEnded()
|
||||
// },
|
||||
// )
|
||||
func (tf *testConnFramer) wantUnorderedFrames(want ...any) {
|
||||
tf.t.Helper()
|
||||
want = slices.Clone(want)
|
||||
seen := 0
|
||||
frame:
|
||||
for seen < len(want) && !tf.t.Failed() {
|
||||
fr := tf.readFrame()
|
||||
if fr == nil {
|
||||
break
|
||||
}
|
||||
for i, f := range want {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
typ := reflect.TypeOf(f)
|
||||
if typ.Kind() != reflect.Func ||
|
||||
typ.NumIn() != 1 ||
|
||||
typ.NumOut() != 1 ||
|
||||
typ.Out(0) != reflect.TypeFor[bool]() {
|
||||
tf.t.Fatalf("expected func(*SomeFrame) bool, got %T", f)
|
||||
}
|
||||
if typ.In(0) == reflect.TypeOf(fr) {
|
||||
out := reflect.ValueOf(f).Call([]reflect.Value{reflect.ValueOf(fr)})
|
||||
if out[0].Bool() {
|
||||
want[i] = nil
|
||||
seen++
|
||||
}
|
||||
continue frame
|
||||
}
|
||||
}
|
||||
tf.t.Errorf("got unexpected frame type %T", fr)
|
||||
}
|
||||
if seen < len(want) {
|
||||
for _, f := range want {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
tf.t.Errorf("did not see expected frame: %v", reflect.TypeOf(f).In(0))
|
||||
}
|
||||
tf.t.Fatalf("did not see %v expected frame types", len(want)-seen)
|
||||
}
|
||||
}
|
||||
|
||||
type wantHeader struct {
|
||||
streamID uint32
|
||||
endStream bool
|
||||
header http.Header
|
||||
}
|
||||
|
||||
// wantHeaders reads a HEADERS frame and potential CONTINUATION frames,
|
||||
// and asserts that they contain the expected headers.
|
||||
func (tf *testConnFramer) wantHeaders(want wantHeader) {
|
||||
tf.t.Helper()
|
||||
|
||||
hf := readFrame[*HeadersFrame](tf.t, tf)
|
||||
if got, want := hf.StreamID, want.streamID; got != want {
|
||||
tf.t.Fatalf("got stream ID %v, want %v", got, want)
|
||||
}
|
||||
if got, want := hf.StreamEnded(), want.endStream; got != want {
|
||||
tf.t.Fatalf("got stream ended %v, want %v", got, want)
|
||||
}
|
||||
|
||||
gotHeader := make(http.Header)
|
||||
tf.dec.SetEmitFunc(func(hf hpack.HeaderField) {
|
||||
gotHeader[hf.Name] = append(gotHeader[hf.Name], hf.Value)
|
||||
})
|
||||
defer tf.dec.SetEmitFunc(nil)
|
||||
if _, err := tf.dec.Write(hf.HeaderBlockFragment()); err != nil {
|
||||
tf.t.Fatalf("decoding HEADERS frame: %v", err)
|
||||
}
|
||||
headersEnded := hf.HeadersEnded()
|
||||
for !headersEnded {
|
||||
cf := readFrame[*ContinuationFrame](tf.t, tf)
|
||||
if cf == nil {
|
||||
tf.t.Fatalf("got end of frames, want CONTINUATION")
|
||||
}
|
||||
if _, err := tf.dec.Write(cf.HeaderBlockFragment()); err != nil {
|
||||
tf.t.Fatalf("decoding CONTINUATION frame: %v", err)
|
||||
}
|
||||
headersEnded = cf.HeadersEnded()
|
||||
}
|
||||
if err := tf.dec.Close(); err != nil {
|
||||
tf.t.Fatalf("hpack decoding error: %v", err)
|
||||
}
|
||||
|
||||
for k, v := range want.header {
|
||||
if !reflect.DeepEqual(v, gotHeader[k]) {
|
||||
tf.t.Fatalf("got header %q = %q; want %q", k, v, gotHeader[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decodeHeader supports some older server tests.
|
||||
// TODO: rewrite those tests to use newer, more convenient test APIs.
|
||||
func (tf *testConnFramer) decodeHeader(headerBlock []byte) (pairs [][2]string) {
|
||||
tf.dec.SetEmitFunc(func(hf hpack.HeaderField) {
|
||||
if hf.Name == "date" {
|
||||
return
|
||||
}
|
||||
pairs = append(pairs, [2]string{hf.Name, hf.Value})
|
||||
})
|
||||
defer tf.dec.SetEmitFunc(nil)
|
||||
if _, err := tf.dec.Write(headerBlock); err != nil {
|
||||
tf.t.Fatalf("hpack decoding error: %v", err)
|
||||
}
|
||||
if err := tf.dec.Close(); err != nil {
|
||||
tf.t.Fatalf("hpack decoding error: %v", err)
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
type wantData struct {
|
||||
streamID uint32
|
||||
endStream bool
|
||||
size int
|
||||
data []byte
|
||||
multiple bool // data may be spread across multiple DATA frames
|
||||
}
|
||||
|
||||
// wantData reads zero or more DATA frames, and asserts that they match the expectation.
|
||||
func (tf *testConnFramer) wantData(want wantData) {
|
||||
tf.t.Helper()
|
||||
gotSize := 0
|
||||
gotEndStream := false
|
||||
if want.data != nil {
|
||||
want.size = len(want.data)
|
||||
}
|
||||
var gotData []byte
|
||||
for {
|
||||
fr := tf.readFrame()
|
||||
if fr == nil {
|
||||
break
|
||||
}
|
||||
data, ok := fr.(*DataFrame)
|
||||
if !ok {
|
||||
tf.t.Fatalf("got frame %T, want DataFrame", fr)
|
||||
}
|
||||
if want.data != nil {
|
||||
gotData = append(gotData, data.Data()...)
|
||||
}
|
||||
gotSize += len(data.Data())
|
||||
if data.StreamEnded() {
|
||||
gotEndStream = true
|
||||
break
|
||||
}
|
||||
if !want.endStream && gotSize >= want.size {
|
||||
break
|
||||
}
|
||||
if !want.multiple {
|
||||
break
|
||||
}
|
||||
}
|
||||
if gotSize != want.size {
|
||||
tf.t.Fatalf("got %v bytes of DATA frames, want %v", gotSize, want.size)
|
||||
}
|
||||
if gotEndStream != want.endStream {
|
||||
tf.t.Fatalf("after %v bytes of DATA frames, got END_STREAM=%v; want %v", gotSize, gotEndStream, want.endStream)
|
||||
}
|
||||
if want.data != nil && !bytes.Equal(gotData, want.data) {
|
||||
tf.t.Fatalf("got data %q, want %q", gotData, want.data)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) wantRSTStream(streamID uint32, code ErrCode) {
|
||||
tf.t.Helper()
|
||||
fr := readFrame[*RSTStreamFrame](tf.t, tf)
|
||||
if fr.StreamID != streamID || fr.ErrCode != code {
|
||||
tf.t.Fatalf("got %v, want RST_STREAM StreamID=%v, code=%v", SummarizeFrame(fr), streamID, code)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) wantSettings(want map[SettingID]uint32) {
|
||||
fr := readFrame[*SettingsFrame](tf.t, tf)
|
||||
if fr.Header().Flags.Has(FlagSettingsAck) {
|
||||
tf.t.Errorf("got SETTINGS frame with ACK set, want no ACK")
|
||||
}
|
||||
for wantID, wantVal := range want {
|
||||
gotVal, ok := fr.Value(wantID)
|
||||
if !ok {
|
||||
tf.t.Errorf("SETTINGS: %v is not set, want %v", wantID, wantVal)
|
||||
} else if gotVal != wantVal {
|
||||
tf.t.Errorf("SETTINGS: %v is %v, want %v", wantID, gotVal, wantVal)
|
||||
}
|
||||
}
|
||||
if tf.t.Failed() {
|
||||
tf.t.Fatalf("%v", fr)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) wantSettingsAck() {
|
||||
tf.t.Helper()
|
||||
fr := readFrame[*SettingsFrame](tf.t, tf)
|
||||
if !fr.Header().Flags.Has(FlagSettingsAck) {
|
||||
tf.t.Fatal("Settings Frame didn't have ACK set")
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) wantGoAway(maxStreamID uint32, code ErrCode) {
|
||||
tf.t.Helper()
|
||||
fr := readFrame[*GoAwayFrame](tf.t, tf)
|
||||
if fr.LastStreamID != maxStreamID || fr.ErrCode != code {
|
||||
tf.t.Fatalf("got %v, want GOAWAY LastStreamID=%v, code=%v", SummarizeFrame(fr), maxStreamID, code)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) wantWindowUpdate(streamID, incr uint32) {
|
||||
tf.t.Helper()
|
||||
wu := readFrame[*WindowUpdateFrame](tf.t, tf)
|
||||
if wu.FrameHeader.StreamID != streamID {
|
||||
tf.t.Fatalf("WindowUpdate StreamID = %d; want %d", wu.FrameHeader.StreamID, streamID)
|
||||
}
|
||||
if wu.Increment != incr {
|
||||
tf.t.Fatalf("WindowUpdate increment = %d; want %d", wu.Increment, incr)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) wantClosed() {
|
||||
tf.t.Helper()
|
||||
fr, err := tf.fr.ReadFrame()
|
||||
if err == nil {
|
||||
tf.t.Fatalf("got unexpected frame (want closed connection): %v", fr)
|
||||
}
|
||||
if err == os.ErrDeadlineExceeded {
|
||||
tf.t.Fatalf("connection is not closed; want it to be")
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) wantIdle() {
|
||||
tf.t.Helper()
|
||||
fr, err := tf.fr.ReadFrame()
|
||||
if err == nil {
|
||||
tf.t.Fatalf("got unexpected frame (want idle connection): %v", fr)
|
||||
}
|
||||
if err != os.ErrDeadlineExceeded {
|
||||
tf.t.Fatalf("got unexpected frame error (want idle connection): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeSettings(settings ...Setting) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteSettings(settings...); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeSettingsAck() {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteSettingsAck(); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeData(streamID uint32, endStream bool, data []byte) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteData(streamID, endStream, data); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeDataPadded(streamID uint32, endStream bool, data, pad []byte) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteDataPadded(streamID, endStream, data, pad); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeHeaders(p HeadersFrameParam) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteHeaders(p); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// writeHeadersMode writes header frames, as modified by mode:
|
||||
//
|
||||
// - noHeader: Don't write the header.
|
||||
// - oneHeader: Write a single HEADERS frame.
|
||||
// - splitHeader: Write a HEADERS frame and CONTINUATION frame.
|
||||
func (tf *testConnFramer) writeHeadersMode(mode headerType, p HeadersFrameParam) {
|
||||
tf.t.Helper()
|
||||
switch mode {
|
||||
case noHeader:
|
||||
case oneHeader:
|
||||
tf.writeHeaders(p)
|
||||
case splitHeader:
|
||||
if len(p.BlockFragment) < 2 {
|
||||
panic("too small")
|
||||
}
|
||||
contData := p.BlockFragment[1:]
|
||||
contEnd := p.EndHeaders
|
||||
p.BlockFragment = p.BlockFragment[:1]
|
||||
p.EndHeaders = false
|
||||
tf.writeHeaders(p)
|
||||
tf.writeContinuation(p.StreamID, contEnd, contData)
|
||||
default:
|
||||
panic("bogus mode")
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteContinuation(streamID, endHeaders, headerBlockFragment); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writePriority(id uint32, p PriorityParam) {
|
||||
if err := tf.fr.WritePriority(id, p); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writePriorityUpdate(id uint32, p string) {
|
||||
if err := tf.fr.WritePriorityUpdate(id, p); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeRSTStream(streamID uint32, code ErrCode) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteRSTStream(streamID, code); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writePing(ack bool, data [8]byte) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WritePing(ack, data); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeGoAway(maxStreamID uint32, code ErrCode, debugData []byte) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteGoAway(maxStreamID, code, debugData); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *testConnFramer) writeWindowUpdate(streamID, incr uint32) {
|
||||
tf.t.Helper()
|
||||
if err := tf.fr.WriteWindowUpdate(streamID, incr); err != nil {
|
||||
tf.t.Fatal(err)
|
||||
}
|
||||
}
|
||||
151
src/net/http/internal/http2/databuffer.go
Normal file
151
src/net/http/internal/http2/databuffer.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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 dataChunkPools = [...]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 getDataBufferChunk(size int64) []byte {
|
||||
switch {
|
||||
case size <= 1<<10:
|
||||
return dataChunkPools[0].Get().(*[1 << 10]byte)[:]
|
||||
case size <= 2<<10:
|
||||
return dataChunkPools[1].Get().(*[2 << 10]byte)[:]
|
||||
case size <= 4<<10:
|
||||
return dataChunkPools[2].Get().(*[4 << 10]byte)[:]
|
||||
case size <= 8<<10:
|
||||
return dataChunkPools[3].Get().(*[8 << 10]byte)[:]
|
||||
default:
|
||||
return dataChunkPools[4].Get().(*[16 << 10]byte)[:]
|
||||
}
|
||||
}
|
||||
|
||||
func putDataBufferChunk(p []byte) {
|
||||
switch len(p) {
|
||||
case 1 << 10:
|
||||
dataChunkPools[0].Put((*[1 << 10]byte)(p))
|
||||
case 2 << 10:
|
||||
dataChunkPools[1].Put((*[2 << 10]byte)(p))
|
||||
case 4 << 10:
|
||||
dataChunkPools[2].Put((*[4 << 10]byte)(p))
|
||||
case 8 << 10:
|
||||
dataChunkPools[3].Put((*[8 << 10]byte)(p))
|
||||
case 16 << 10:
|
||||
dataChunkPools[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 dataBuffer 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 errReadEmpty = 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 *dataBuffer) Read(p []byte) (int, error) {
|
||||
if b.size == 0 {
|
||||
return 0, errReadEmpty
|
||||
}
|
||||
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]) {
|
||||
putDataBufferChunk(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 *dataBuffer) 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 *dataBuffer) Len() int {
|
||||
return b.size
|
||||
}
|
||||
|
||||
// Write appends p to the buffer.
|
||||
func (b *dataBuffer) 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 *dataBuffer) lastChunkOrAlloc(want int64) []byte {
|
||||
if len(b.chunks) != 0 {
|
||||
last := b.chunks[len(b.chunks)-1]
|
||||
if b.w < len(last) {
|
||||
return last
|
||||
}
|
||||
}
|
||||
chunk := getDataBufferChunk(want)
|
||||
b.chunks = append(b.chunks, chunk)
|
||||
b.w = 0
|
||||
return chunk
|
||||
}
|
||||
157
src/net/http/internal/http2/databuffer_test.go
Normal file
157
src/net/http/internal/http2/databuffer_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2017 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func fmtDataChunk(chunk []byte) string {
|
||||
out := ""
|
||||
var last byte
|
||||
var count int
|
||||
for _, c := range chunk {
|
||||
if c != last {
|
||||
if count > 0 {
|
||||
out += fmt.Sprintf(" x %d ", count)
|
||||
count = 0
|
||||
}
|
||||
out += string([]byte{c})
|
||||
last = c
|
||||
}
|
||||
count++
|
||||
}
|
||||
if count > 0 {
|
||||
out += fmt.Sprintf(" x %d", count)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func fmtDataChunks(chunks [][]byte) string {
|
||||
var out string
|
||||
for _, chunk := range chunks {
|
||||
out += fmt.Sprintf("{%q}", fmtDataChunk(chunk))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func testDataBuffer(t *testing.T, wantBytes []byte, setup func(t *testing.T) *dataBuffer) {
|
||||
// Run setup, then read the remaining bytes from the dataBuffer and check
|
||||
// that they match wantBytes. We use different read sizes to check corner
|
||||
// cases in Read.
|
||||
for _, readSize := range []int{1, 2, 1 * 1024, 32 * 1024} {
|
||||
t.Run(fmt.Sprintf("ReadSize=%d", readSize), func(t *testing.T) {
|
||||
b := setup(t)
|
||||
buf := make([]byte, readSize)
|
||||
var gotRead bytes.Buffer
|
||||
for {
|
||||
n, err := b.Read(buf)
|
||||
gotRead.Write(buf[:n])
|
||||
if err == errReadEmpty {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("error after %v bytes: %v", gotRead.Len(), err)
|
||||
}
|
||||
}
|
||||
if got, want := gotRead.Bytes(), wantBytes; !bytes.Equal(got, want) {
|
||||
t.Errorf("FinalRead=%q, want %q", fmtDataChunk(got), fmtDataChunk(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataBufferAllocation(t *testing.T) {
|
||||
writes := [][]byte{
|
||||
bytes.Repeat([]byte("a"), 1*1024-1),
|
||||
[]byte("a"),
|
||||
bytes.Repeat([]byte("b"), 4*1024-1),
|
||||
[]byte("b"),
|
||||
bytes.Repeat([]byte("c"), 8*1024-1),
|
||||
[]byte("c"),
|
||||
bytes.Repeat([]byte("d"), 16*1024-1),
|
||||
[]byte("d"),
|
||||
bytes.Repeat([]byte("e"), 32*1024),
|
||||
}
|
||||
var wantRead bytes.Buffer
|
||||
for _, p := range writes {
|
||||
wantRead.Write(p)
|
||||
}
|
||||
|
||||
testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer {
|
||||
b := &dataBuffer{}
|
||||
for _, p := range writes {
|
||||
if n, err := b.Write(p); n != len(p) || err != nil {
|
||||
t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p))
|
||||
}
|
||||
}
|
||||
want := [][]byte{
|
||||
bytes.Repeat([]byte("a"), 1*1024),
|
||||
bytes.Repeat([]byte("b"), 4*1024),
|
||||
bytes.Repeat([]byte("c"), 8*1024),
|
||||
bytes.Repeat([]byte("d"), 16*1024),
|
||||
bytes.Repeat([]byte("e"), 16*1024),
|
||||
bytes.Repeat([]byte("e"), 16*1024),
|
||||
}
|
||||
if !reflect.DeepEqual(b.chunks, want) {
|
||||
t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want))
|
||||
}
|
||||
return b
|
||||
})
|
||||
}
|
||||
|
||||
func TestDataBufferAllocationWithExpected(t *testing.T) {
|
||||
writes := [][]byte{
|
||||
bytes.Repeat([]byte("a"), 1*1024), // allocates 16KB
|
||||
bytes.Repeat([]byte("b"), 14*1024),
|
||||
bytes.Repeat([]byte("c"), 15*1024), // allocates 16KB more
|
||||
bytes.Repeat([]byte("d"), 2*1024),
|
||||
bytes.Repeat([]byte("e"), 1*1024), // overflows 32KB expectation, allocates just 1KB
|
||||
}
|
||||
var wantRead bytes.Buffer
|
||||
for _, p := range writes {
|
||||
wantRead.Write(p)
|
||||
}
|
||||
|
||||
testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer {
|
||||
b := &dataBuffer{expected: 32 * 1024}
|
||||
for _, p := range writes {
|
||||
if n, err := b.Write(p); n != len(p) || err != nil {
|
||||
t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p))
|
||||
}
|
||||
}
|
||||
want := [][]byte{
|
||||
append(bytes.Repeat([]byte("a"), 1*1024), append(bytes.Repeat([]byte("b"), 14*1024), bytes.Repeat([]byte("c"), 1*1024)...)...),
|
||||
append(bytes.Repeat([]byte("c"), 14*1024), bytes.Repeat([]byte("d"), 2*1024)...),
|
||||
bytes.Repeat([]byte("e"), 1*1024),
|
||||
}
|
||||
if !reflect.DeepEqual(b.chunks, want) {
|
||||
t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want))
|
||||
}
|
||||
return b
|
||||
})
|
||||
}
|
||||
|
||||
func TestDataBufferWriteAfterPartialRead(t *testing.T) {
|
||||
testDataBuffer(t, []byte("cdxyz"), func(t *testing.T) *dataBuffer {
|
||||
b := &dataBuffer{}
|
||||
if n, err := b.Write([]byte("abcd")); n != 4 || err != nil {
|
||||
t.Fatalf("Write(\"abcd\")=%v,%v want 4,nil", n, err)
|
||||
}
|
||||
p := make([]byte, 2)
|
||||
if n, err := b.Read(p); n != 2 || err != nil || !bytes.Equal(p, []byte("ab")) {
|
||||
t.Fatalf("Read()=%q,%v,%v want \"ab\",2,nil", p, n, err)
|
||||
}
|
||||
if n, err := b.Write([]byte("xyz")); n != 3 || err != nil {
|
||||
t.Fatalf("Write(\"xyz\")=%v,%v want 3,nil", n, err)
|
||||
}
|
||||
return b
|
||||
})
|
||||
}
|
||||
147
src/net/http/internal/http2/errors.go
Normal file
147
src/net/http/internal/http2/errors.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// An ErrCode is an unsigned 32-bit error code as defined in the HTTP/2 spec.
|
||||
type ErrCode uint32
|
||||
|
||||
const (
|
||||
ErrCodeNo ErrCode = 0x0
|
||||
ErrCodeProtocol ErrCode = 0x1
|
||||
ErrCodeInternal ErrCode = 0x2
|
||||
ErrCodeFlowControl ErrCode = 0x3
|
||||
ErrCodeSettingsTimeout ErrCode = 0x4
|
||||
ErrCodeStreamClosed ErrCode = 0x5
|
||||
ErrCodeFrameSize ErrCode = 0x6
|
||||
ErrCodeRefusedStream ErrCode = 0x7
|
||||
ErrCodeCancel ErrCode = 0x8
|
||||
ErrCodeCompression ErrCode = 0x9
|
||||
ErrCodeConnect ErrCode = 0xa
|
||||
ErrCodeEnhanceYourCalm ErrCode = 0xb
|
||||
ErrCodeInadequateSecurity ErrCode = 0xc
|
||||
ErrCodeHTTP11Required ErrCode = 0xd
|
||||
)
|
||||
|
||||
var errCodeName = map[ErrCode]string{
|
||||
ErrCodeNo: "NO_ERROR",
|
||||
ErrCodeProtocol: "PROTOCOL_ERROR",
|
||||
ErrCodeInternal: "INTERNAL_ERROR",
|
||||
ErrCodeFlowControl: "FLOW_CONTROL_ERROR",
|
||||
ErrCodeSettingsTimeout: "SETTINGS_TIMEOUT",
|
||||
ErrCodeStreamClosed: "STREAM_CLOSED",
|
||||
ErrCodeFrameSize: "FRAME_SIZE_ERROR",
|
||||
ErrCodeRefusedStream: "REFUSED_STREAM",
|
||||
ErrCodeCancel: "CANCEL",
|
||||
ErrCodeCompression: "COMPRESSION_ERROR",
|
||||
ErrCodeConnect: "CONNECT_ERROR",
|
||||
ErrCodeEnhanceYourCalm: "ENHANCE_YOUR_CALM",
|
||||
ErrCodeInadequateSecurity: "INADEQUATE_SECURITY",
|
||||
ErrCodeHTTP11Required: "HTTP_1_1_REQUIRED",
|
||||
}
|
||||
|
||||
func (e ErrCode) String() string {
|
||||
if s, ok := errCodeName[e]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("unknown error code 0x%x", uint32(e))
|
||||
}
|
||||
|
||||
func (e ErrCode) stringToken() string {
|
||||
if s, ok := errCodeName[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 ConnectionError ErrCode
|
||||
|
||||
func (e ConnectionError) Error() string { return fmt.Sprintf("connection error: %s", ErrCode(e)) }
|
||||
|
||||
// StreamError is an error that only affects one stream within an
|
||||
// HTTP/2 connection.
|
||||
type StreamError struct {
|
||||
StreamID uint32
|
||||
Code ErrCode
|
||||
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 errFromPeer = errors.New("received from peer")
|
||||
|
||||
func streamError(id uint32, code ErrCode) StreamError {
|
||||
return StreamError{StreamID: id, Code: code}
|
||||
}
|
||||
|
||||
func (e StreamError) 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 goAwayFlowError struct{}
|
||||
|
||||
func (goAwayFlowError) 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 connError struct {
|
||||
Code ErrCode // the ConnectionError error code
|
||||
Reason string // additional reason
|
||||
}
|
||||
|
||||
func (e connError) Error() string {
|
||||
return fmt.Sprintf("http2: connection error: %v: %v", e.Code, e.Reason)
|
||||
}
|
||||
|
||||
type pseudoHeaderError string
|
||||
|
||||
func (e pseudoHeaderError) Error() string {
|
||||
return fmt.Sprintf("invalid pseudo-header %q", string(e))
|
||||
}
|
||||
|
||||
type duplicatePseudoHeaderError string
|
||||
|
||||
func (e duplicatePseudoHeaderError) Error() string {
|
||||
return fmt.Sprintf("duplicate pseudo-header %q", string(e))
|
||||
}
|
||||
|
||||
type headerFieldNameError string
|
||||
|
||||
func (e headerFieldNameError) Error() string {
|
||||
return fmt.Sprintf("invalid header field name %q", string(e))
|
||||
}
|
||||
|
||||
type headerFieldValueError string
|
||||
|
||||
func (e headerFieldValueError) Error() string {
|
||||
return fmt.Sprintf("invalid header field value for %q", string(e))
|
||||
}
|
||||
|
||||
var (
|
||||
errMixPseudoHeaderTypes = errors.New("mix of request and response pseudo headers")
|
||||
errPseudoAfterRegular = errors.New("pseudo header field after regular")
|
||||
)
|
||||
26
src/net/http/internal/http2/errors_test.go
Normal file
26
src/net/http/internal/http2/errors_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestErrCodeString(t *testing.T) {
|
||||
tests := []struct {
|
||||
err ErrCode
|
||||
want string
|
||||
}{
|
||||
{ErrCodeProtocol, "PROTOCOL_ERROR"},
|
||||
{0xd, "HTTP_1_1_REQUIRED"},
|
||||
{0xf, "unknown error code 0xf"},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := tt.err.String()
|
||||
if got != tt.want {
|
||||
t.Errorf("%d. Error = %q; want %q", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
243
src/net/http/internal/http2/export_test.go
Normal file
243
src/net/http/internal/http2/export_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
// 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/net/internal/httpcommon"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxReadFrameSize = defaultMaxReadFrameSize
|
||||
DefaultMaxStreams = defaultMaxStreams
|
||||
InflowMinRefresh = inflowMinRefresh
|
||||
InitialHeaderTableSize = initialHeaderTableSize
|
||||
InitialMaxConcurrentStreams = initialMaxConcurrentStreams
|
||||
InitialWindowSize = initialWindowSize
|
||||
MaxFrameSize = maxFrameSize
|
||||
MaxQueuedControlFrames = maxQueuedControlFrames
|
||||
MinMaxFrameSize = minMaxFrameSize
|
||||
)
|
||||
|
||||
type (
|
||||
ServerConn = serverConn
|
||||
Stream = stream
|
||||
StreamState = streamState
|
||||
|
||||
PseudoHeaderError = pseudoHeaderError
|
||||
HeaderFieldNameError = headerFieldNameError
|
||||
HeaderFieldValueError = headerFieldValueError
|
||||
)
|
||||
|
||||
const (
|
||||
StateIdle = stateIdle
|
||||
StateOpen = stateOpen
|
||||
StateHalfClosedLocal = stateHalfClosedLocal
|
||||
StateHalfClosedRemote = stateHalfClosedRemote
|
||||
StateClosed = stateClosed
|
||||
)
|
||||
|
||||
var (
|
||||
ErrClientConnForceClosed = errClientConnForceClosed
|
||||
ErrClientConnNotEstablished = errClientConnNotEstablished
|
||||
ErrClientConnUnusable = errClientConnUnusable
|
||||
ErrExtendedConnectNotSupported = errExtendedConnectNotSupported
|
||||
ErrReqBodyTooLong = errReqBodyTooLong
|
||||
ErrRequestHeaderListSize = errRequestHeaderListSize
|
||||
ErrResponseHeaderListSize = errResponseHeaderListSize
|
||||
)
|
||||
|
||||
func (s *Server) TestServeConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) {
|
||||
s.serveConn(c, opts, newf)
|
||||
}
|
||||
|
||||
func (sc *serverConn) TestFlowControlConsumed() (consumed int32) {
|
||||
conf := configFromServer(sc.hs, sc.srv)
|
||||
donec := make(chan struct{})
|
||||
sc.sendServeMsg(func(sc *serverConn) {
|
||||
defer close(donec)
|
||||
initial := conf.MaxUploadBufferPerConnection
|
||||
avail := sc.inflow.avail + sc.inflow.unsent
|
||||
consumed = initial - avail
|
||||
})
|
||||
<-donec
|
||||
return consumed
|
||||
}
|
||||
|
||||
func (sc *serverConn) TestStreamExists(id uint32) bool {
|
||||
ch := make(chan bool, 1)
|
||||
sc.serveMsgCh <- func(int) {
|
||||
ch <- (sc.streams[id] != nil)
|
||||
}
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func (sc *serverConn) TestStreamState(id uint32) streamState {
|
||||
ch := make(chan streamState, 1)
|
||||
sc.serveMsgCh <- func(int) {
|
||||
state, _ := sc.state(id)
|
||||
ch <- state
|
||||
}
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func (sc *serverConn) StartGracefulShutdown() { sc.startGracefulShutdown() }
|
||||
|
||||
func (sc *serverConn) TestHPACKEncoder() *hpack.Encoder {
|
||||
return sc.hpackEncoder
|
||||
}
|
||||
|
||||
func (sc *serverConn) TestFramerMaxHeaderStringLen() int {
|
||||
return sc.framer.maxHeaderStringLen()
|
||||
}
|
||||
|
||||
func (t *Transport) DialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
||||
return t.dialClientConn(ctx, addr, singleUse)
|
||||
}
|
||||
|
||||
func (t *Transport) TestNewClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*ClientConn, error) {
|
||||
return t.newClientConn(c, singleUse, internalStateHook)
|
||||
}
|
||||
|
||||
func (t *Transport) TestSetNewClientConnHook(f func(*ClientConn)) {
|
||||
t.transportTestHooks = &transportTestHooks{
|
||||
newclientconn: f,
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return cc.roundTrip(req, func(cs *clientStream) {
|
||||
f(cs.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func (cc *ClientConn) TestHPACKEncoder() *hpack.Encoder {
|
||||
return cc.henc
|
||||
}
|
||||
|
||||
func (cc *ClientConn) TestPeerMaxHeaderTableSize() uint32 {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
return cc.peerMaxHeaderTableSize
|
||||
}
|
||||
|
||||
func (cc *ClientConn) TestInflowWindow(streamID uint32) (int32, error) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if streamID == 0 {
|
||||
return cc.inflow.avail + cc.inflow.unsent, nil
|
||||
}
|
||||
cs := cc.streams[streamID]
|
||||
if cs == nil {
|
||||
return -1, fmt.Errorf("no stream with id %v", streamID)
|
||||
}
|
||||
return cs.inflow.avail + cs.inflow.unsent, nil
|
||||
}
|
||||
|
||||
func (fr *Framer) TestSetDebugReadLoggerf(f func(string, ...any)) {
|
||||
fr.logReads = true
|
||||
fr.debugReadLoggerf = f
|
||||
}
|
||||
|
||||
func (fr *Framer) TestSetDebugWriteLoggerf(f func(string, ...any)) {
|
||||
fr.logWrites = true
|
||||
fr.debugWriteLoggerf = f
|
||||
}
|
||||
|
||||
func SummarizeFrame(f Frame) string {
|
||||
return summarizeFrame(f)
|
||||
}
|
||||
|
||||
func SetTestHookGetServerConn(t testing.TB, f func(*serverConn)) {
|
||||
SetForTest(t, &testHookGetServerConn, f)
|
||||
}
|
||||
|
||||
func init() {
|
||||
testHookOnPanicMu = new(sync.Mutex)
|
||||
}
|
||||
|
||||
func SetTestHookOnPanic(t testing.TB, f func(sc *serverConn, panicVal interface{}) (rePanic bool)) {
|
||||
testHookOnPanicMu.Lock()
|
||||
defer testHookOnPanicMu.Unlock()
|
||||
old := testHookOnPanic
|
||||
testHookOnPanic = f
|
||||
t.Cleanup(func() {
|
||||
testHookOnPanicMu.Lock()
|
||||
defer testHookOnPanicMu.Unlock()
|
||||
testHookOnPanic = old
|
||||
})
|
||||
}
|
||||
|
||||
func SetTestHookGot1xx(t testing.TB, f func(int, textproto.MIMEHeader) error) {
|
||||
SetForTest(t, &got1xxFuncForTests, f)
|
||||
}
|
||||
|
||||
func SetDisableExtendedConnectProtocol(t testing.TB, v bool) {
|
||||
SetForTest(t, &disableExtendedConnectProtocol, v)
|
||||
}
|
||||
|
||||
func LogFrameReads() bool { return logFrameReads }
|
||||
func LogFrameWrites() bool { return logFrameWrites }
|
||||
|
||||
const GoAwayTimeout = 25 * time.Millisecond
|
||||
|
||||
func init() {
|
||||
goAwayTimeout = GoAwayTimeout
|
||||
}
|
||||
|
||||
func EncodeHeaderRaw(t testing.TB, headers ...string) []byte {
|
||||
return encodeHeaderRaw(t, headers...)
|
||||
}
|
||||
|
||||
func NewPriorityWriteSchedulerRFC7540(cfg *PriorityWriteSchedulerConfig) WriteScheduler {
|
||||
return newPriorityWriteSchedulerRFC7540(cfg)
|
||||
}
|
||||
|
||||
func NewPriorityWriteSchedulerRFC9218() WriteScheduler {
|
||||
return newPriorityWriteSchedulerRFC9218()
|
||||
}
|
||||
|
||||
func NewRoundRobinWriteScheduler() WriteScheduler {
|
||||
return newRoundRobinWriteScheduler()
|
||||
}
|
||||
|
||||
func DisableGoroutineTracking(t testing.TB) {
|
||||
disableDebugGoroutines.Store(true)
|
||||
t.Cleanup(func() {
|
||||
disableDebugGoroutines.Store(false)
|
||||
})
|
||||
}
|
||||
|
||||
func InvalidHTTP1LookingFrameHeader() FrameHeader {
|
||||
return invalidHTTP1LookingFrameHeader()
|
||||
}
|
||||
|
||||
func NewNoDialClientConnPool() ClientConnPool {
|
||||
return noDialClientConnPool{new(clientConnPool)}
|
||||
}
|
||||
|
||||
func EncodeRequestHeaders(req *http.Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) {
|
||||
return encodeRequestHeaders(req, addGzipHeader, peerMaxHeaderListSize, headerf)
|
||||
}
|
||||
122
src/net/http/internal/http2/flow.go
Normal file
122
src/net/http/internal/http2/flow.go
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
// Flow control
|
||||
|
||||
package http2
|
||||
|
||||
// inflowMinRefresh is the minimum number of bytes we'll send for a
|
||||
// flow control window update.
|
||||
const inflowMinRefresh = 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 inflow struct {
|
||||
avail int32
|
||||
unsent int32
|
||||
}
|
||||
|
||||
// init sets the initial window.
|
||||
func (f *inflow) 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 *inflow) 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 < inflowMinRefresh && 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 *inflow) 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 takeInflows(f1, f2 *inflow, 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 outflow struct {
|
||||
_ incomparable
|
||||
|
||||
// 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 *outflow
|
||||
}
|
||||
|
||||
func (f *outflow) setConnFlow(cf *outflow) { f.conn = cf }
|
||||
|
||||
func (f *outflow) available() int32 {
|
||||
n := f.n
|
||||
if f.conn != nil && f.conn.n < n {
|
||||
n = f.conn.n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (f *outflow) 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 *outflow) add(n int32) bool {
|
||||
sum := f.n + n
|
||||
if (sum > n) == (f.n > 0) {
|
||||
f.n = sum
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
141
src/net/http/internal/http2/flow_test.go
Normal file
141
src/net/http/internal/http2/flow_test.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInFlowTake(t *testing.T) {
|
||||
var f inflow
|
||||
f.init(100)
|
||||
if !f.take(40) {
|
||||
t.Fatalf("f.take(40) from 100: got false, want true")
|
||||
}
|
||||
if !f.take(40) {
|
||||
t.Fatalf("f.take(40) from 60: got false, want true")
|
||||
}
|
||||
if f.take(40) {
|
||||
t.Fatalf("f.take(40) from 20: got true, want false")
|
||||
}
|
||||
if !f.take(20) {
|
||||
t.Fatalf("f.take(20) from 20: got false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInflowAddSmall(t *testing.T) {
|
||||
var f inflow
|
||||
f.init(0)
|
||||
// Adding even a small amount when there is no flow causes an immediate send.
|
||||
if got, want := f.add(1), int32(1); got != want {
|
||||
t.Fatalf("f.add(1) to 1 = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInflowAdd(t *testing.T) {
|
||||
var f inflow
|
||||
f.init(10 * inflowMinRefresh)
|
||||
if got, want := f.add(inflowMinRefresh-1), int32(0); got != want {
|
||||
t.Fatalf("f.add(minRefresh - 1) = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := f.add(1), int32(inflowMinRefresh); got != want {
|
||||
t.Fatalf("f.add(minRefresh) = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTakeInflows(t *testing.T) {
|
||||
var a, b inflow
|
||||
a.init(10)
|
||||
b.init(20)
|
||||
if !takeInflows(&a, &b, 5) {
|
||||
t.Fatalf("takeInflows(a, b, 5) from 10, 20: got false, want true")
|
||||
}
|
||||
if takeInflows(&a, &b, 6) {
|
||||
t.Fatalf("takeInflows(a, b, 6) from 5, 15: got true, want false")
|
||||
}
|
||||
if !takeInflows(&a, &b, 5) {
|
||||
t.Fatalf("takeInflows(a, b, 5) from 5, 15: got false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutFlow(t *testing.T) {
|
||||
var st outflow
|
||||
var conn outflow
|
||||
st.add(3)
|
||||
conn.add(2)
|
||||
|
||||
if got, want := st.available(), int32(3); got != want {
|
||||
t.Errorf("available = %d; want %d", got, want)
|
||||
}
|
||||
st.setConnFlow(&conn)
|
||||
if got, want := st.available(), int32(2); got != want {
|
||||
t.Errorf("after parent setup, available = %d; want %d", got, want)
|
||||
}
|
||||
|
||||
st.take(2)
|
||||
if got, want := conn.available(), int32(0); got != want {
|
||||
t.Errorf("after taking 2, conn = %d; want %d", got, want)
|
||||
}
|
||||
if got, want := st.available(), int32(0); got != want {
|
||||
t.Errorf("after taking 2, stream = %d; want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutFlowAdd(t *testing.T) {
|
||||
var f outflow
|
||||
if !f.add(1) {
|
||||
t.Fatal("failed to add 1")
|
||||
}
|
||||
if !f.add(-1) {
|
||||
t.Fatal("failed to add -1")
|
||||
}
|
||||
if got, want := f.available(), int32(0); got != want {
|
||||
t.Fatalf("size = %d; want %d", got, want)
|
||||
}
|
||||
if !f.add(1<<31 - 1) {
|
||||
t.Fatal("failed to add 2^31-1")
|
||||
}
|
||||
if got, want := f.available(), int32(1<<31-1); got != want {
|
||||
t.Fatalf("size = %d; want %d", got, want)
|
||||
}
|
||||
if f.add(1) {
|
||||
t.Fatal("adding 1 to max shouldn't be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutFlowAddOverflow(t *testing.T) {
|
||||
var f outflow
|
||||
if !f.add(0) {
|
||||
t.Fatal("failed to add 0")
|
||||
}
|
||||
if !f.add(-1) {
|
||||
t.Fatal("failed to add -1")
|
||||
}
|
||||
if !f.add(0) {
|
||||
t.Fatal("failed to add 0")
|
||||
}
|
||||
if !f.add(1) {
|
||||
t.Fatal("failed to add 1")
|
||||
}
|
||||
if !f.add(1) {
|
||||
t.Fatal("failed to add 1")
|
||||
}
|
||||
if !f.add(0) {
|
||||
t.Fatal("failed to add 0")
|
||||
}
|
||||
if !f.add(-3) {
|
||||
t.Fatal("failed to add -3")
|
||||
}
|
||||
if got, want := f.available(), int32(-2); got != want {
|
||||
t.Fatalf("size = %d; want %d", got, want)
|
||||
}
|
||||
if !f.add(1<<31 - 1) {
|
||||
t.Fatal("failed to add 2^31-1")
|
||||
}
|
||||
if got, want := f.available(), int32(1+-3+(1<<31-1)); got != want {
|
||||
t.Fatalf("size = %d; want %d", got, want)
|
||||
}
|
||||
|
||||
}
|
||||
1873
src/net/http/internal/http2/frame.go
Normal file
1873
src/net/http/internal/http2/frame.go
Normal file
File diff suppressed because it is too large
Load Diff
1609
src/net/http/internal/http2/frame_test.go
Normal file
1609
src/net/http/internal/http2/frame_test.go
Normal file
File diff suppressed because it is too large
Load Diff
183
src/net/http/internal/http2/gotrack.go
Normal file
183
src/net/http/internal/http2/gotrack.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
// Defensive debug-only utility to track that functions run on the
|
||||
// goroutine that they're supposed to.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var DebugGoroutines = 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 disableDebugGoroutines atomic.Bool
|
||||
|
||||
type goroutineLock uint64
|
||||
|
||||
func newGoroutineLock() goroutineLock {
|
||||
if !DebugGoroutines || disableDebugGoroutines.Load() {
|
||||
return 0
|
||||
}
|
||||
return goroutineLock(curGoroutineID())
|
||||
}
|
||||
|
||||
func (g goroutineLock) check() {
|
||||
if !DebugGoroutines || disableDebugGoroutines.Load() {
|
||||
return
|
||||
}
|
||||
if curGoroutineID() != uint64(g) {
|
||||
panic("running on the wrong goroutine")
|
||||
}
|
||||
}
|
||||
|
||||
func (g goroutineLock) checkNotOn() {
|
||||
if !DebugGoroutines || disableDebugGoroutines.Load() {
|
||||
return
|
||||
}
|
||||
if curGoroutineID() == uint64(g) {
|
||||
panic("running on the wrong goroutine")
|
||||
}
|
||||
}
|
||||
|
||||
var goroutineSpace = []byte("goroutine ")
|
||||
|
||||
func curGoroutineID() uint64 {
|
||||
bp := littleBuf.Get().(*[]byte)
|
||||
defer littleBuf.Put(bp)
|
||||
b := *bp
|
||||
b = b[:runtime.Stack(b, false)]
|
||||
// Parse the 4707 out of "goroutine 4707 ["
|
||||
b = bytes.TrimPrefix(b, goroutineSpace)
|
||||
i := bytes.IndexByte(b, ' ')
|
||||
if i < 0 {
|
||||
panic(fmt.Sprintf("No space found in %q", b))
|
||||
}
|
||||
b = b[:i]
|
||||
n, err := parseUintBytes(b, 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
var littleBuf = sync.Pool{
|
||||
New: func() interface{} {
|
||||
buf := make([]byte, 64)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
// parseUintBytes is like strconv.ParseUint, but using a []byte.
|
||||
func parseUintBytes(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 = cutoff64(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 cutoff64(base int) uint64 {
|
||||
if base < 2 {
|
||||
return 0
|
||||
}
|
||||
return (1<<64-1)/uint64(base) + 1
|
||||
}
|
||||
31
src/net/http/internal/http2/gotrack_test.go
Normal file
31
src/net/http/internal/http2/gotrack_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGoroutineLock(t *testing.T) {
|
||||
g := newGoroutineLock()
|
||||
g.check()
|
||||
|
||||
sawPanic := make(chan interface{})
|
||||
go func() {
|
||||
defer func() { sawPanic <- recover() }()
|
||||
g.check() // should panic
|
||||
}()
|
||||
e := <-sawPanic
|
||||
if e == nil {
|
||||
t.Fatal("did not see panic from check in other goroutine")
|
||||
}
|
||||
if !strings.Contains(fmt.Sprint(e), "wrong goroutine") {
|
||||
t.Errorf("expected on see panic about running on the wrong goroutine; got %v", e)
|
||||
}
|
||||
}
|
||||
413
src/net/http/internal/http2/http2.go
Normal file
413
src/net/http/internal/http2/http2.go
Normal file
@@ -0,0 +1,413 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
// 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 http2 // import "golang.org/x/net/http2"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
var (
|
||||
VerboseLogs bool
|
||||
logFrameWrites bool
|
||||
logFrameReads 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.
|
||||
disableExtendedConnectProtocol = true
|
||||
)
|
||||
|
||||
func init() {
|
||||
e := os.Getenv("GODEBUG")
|
||||
if strings.Contains(e, "http2debug=1") {
|
||||
VerboseLogs = true
|
||||
}
|
||||
if strings.Contains(e, "http2debug=2") {
|
||||
VerboseLogs = true
|
||||
logFrameWrites = true
|
||||
logFrameReads = true
|
||||
}
|
||||
if strings.Contains(e, "http2xconnect=1") {
|
||||
disableExtendedConnectProtocol = false
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// ClientPreface is the string that must be sent by new
|
||||
// connections from clients.
|
||||
ClientPreface = "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
|
||||
initialMaxFrameSize = 16384
|
||||
|
||||
// NextProtoTLS is the NPN/ALPN protocol negotiated during
|
||||
// HTTP/2's TLS setup.
|
||||
NextProtoTLS = "h2"
|
||||
|
||||
// https://httpwg.org/specs/rfc7540.html#SettingValues
|
||||
initialHeaderTableSize = 4096
|
||||
|
||||
initialWindowSize = 65535 // 6.9.2 Initial Flow Control Window Size
|
||||
|
||||
defaultMaxReadFrameSize = 1 << 20
|
||||
)
|
||||
|
||||
var (
|
||||
clientPreface = []byte(ClientPreface)
|
||||
)
|
||||
|
||||
type streamState 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 (
|
||||
stateIdle streamState = iota
|
||||
stateOpen
|
||||
stateHalfClosedLocal
|
||||
stateHalfClosedRemote
|
||||
stateClosed
|
||||
)
|
||||
|
||||
var stateName = [...]string{
|
||||
stateIdle: "Idle",
|
||||
stateOpen: "Open",
|
||||
stateHalfClosedLocal: "HalfClosedLocal",
|
||||
stateHalfClosedRemote: "HalfClosedRemote",
|
||||
stateClosed: "Closed",
|
||||
}
|
||||
|
||||
func (st streamState) String() string {
|
||||
return stateName[st]
|
||||
}
|
||||
|
||||
// Setting is a setting parameter: which setting it is, and its value.
|
||||
type Setting struct {
|
||||
// ID is which setting is being set.
|
||||
// See https://httpwg.org/specs/rfc7540.html#SettingFormat
|
||||
ID SettingID
|
||||
|
||||
// Val is the value.
|
||||
Val uint32
|
||||
}
|
||||
|
||||
func (s Setting) String() string {
|
||||
return fmt.Sprintf("[%v = %d]", s.ID, s.Val)
|
||||
}
|
||||
|
||||
// Valid reports whether the setting is valid.
|
||||
func (s Setting) Valid() error {
|
||||
// Limits and error codes from 6.5.2 Defined SETTINGS Parameters
|
||||
switch s.ID {
|
||||
case SettingEnablePush:
|
||||
if s.Val != 1 && s.Val != 0 {
|
||||
return ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
case SettingInitialWindowSize:
|
||||
if s.Val > 1<<31-1 {
|
||||
return ConnectionError(ErrCodeFlowControl)
|
||||
}
|
||||
case SettingMaxFrameSize:
|
||||
if s.Val < 16384 || s.Val > 1<<24-1 {
|
||||
return ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
case SettingEnableConnectProtocol:
|
||||
if s.Val != 1 && s.Val != 0 {
|
||||
return ConnectionError(ErrCodeProtocol)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A SettingID is an HTTP/2 setting as defined in
|
||||
// https://httpwg.org/specs/rfc7540.html#iana-settings
|
||||
type SettingID uint16
|
||||
|
||||
const (
|
||||
SettingHeaderTableSize SettingID = 0x1
|
||||
SettingEnablePush SettingID = 0x2
|
||||
SettingMaxConcurrentStreams SettingID = 0x3
|
||||
SettingInitialWindowSize SettingID = 0x4
|
||||
SettingMaxFrameSize SettingID = 0x5
|
||||
SettingMaxHeaderListSize SettingID = 0x6
|
||||
SettingEnableConnectProtocol SettingID = 0x8
|
||||
SettingNoRFC7540Priorities SettingID = 0x9
|
||||
)
|
||||
|
||||
var settingName = map[SettingID]string{
|
||||
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
|
||||
SettingEnablePush: "ENABLE_PUSH",
|
||||
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
|
||||
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
|
||||
SettingMaxFrameSize: "MAX_FRAME_SIZE",
|
||||
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
|
||||
SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL",
|
||||
SettingNoRFC7540Priorities: "NO_RFC7540_PRIORITIES",
|
||||
}
|
||||
|
||||
func (s SettingID) String() string {
|
||||
if v, ok := settingName[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 validWireHeaderFieldName(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 httpCodeString(code int) string {
|
||||
switch code {
|
||||
case 200:
|
||||
return "200"
|
||||
case 404:
|
||||
return "404"
|
||||
}
|
||||
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{}
|
||||
|
||||
// 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 *closeWaiter) Init() {
|
||||
*cw = make(chan struct{})
|
||||
}
|
||||
|
||||
// Close marks the closeWaiter as closed and unblocks any waiters.
|
||||
func (cw closeWaiter) Close() {
|
||||
close(cw)
|
||||
}
|
||||
|
||||
// Wait waits for the closeWaiter to become closed.
|
||||
func (cw closeWaiter) 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 bufferedWriter struct {
|
||||
_ incomparable
|
||||
conn net.Conn // immutable
|
||||
bw *bufio.Writer // non-nil when data is buffered
|
||||
byteTimeout time.Duration // immutable, WriteByteTimeout
|
||||
}
|
||||
|
||||
func newBufferedWriter(conn net.Conn, timeout time.Duration) *bufferedWriter {
|
||||
return &bufferedWriter{
|
||||
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 bufWriterPoolBufferSize = 4 << 10
|
||||
|
||||
var bufWriterPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bufio.NewWriterSize(nil, bufWriterPoolBufferSize)
|
||||
},
|
||||
}
|
||||
|
||||
func (w *bufferedWriter) Available() int {
|
||||
if w.bw == nil {
|
||||
return bufWriterPoolBufferSize
|
||||
}
|
||||
return w.bw.Available()
|
||||
}
|
||||
|
||||
func (w *bufferedWriter) Write(p []byte) (n int, err error) {
|
||||
if w.bw == nil {
|
||||
bw := bufWriterPool.Get().(*bufio.Writer)
|
||||
bw.Reset((*bufferedWriterTimeoutWriter)(w))
|
||||
w.bw = bw
|
||||
}
|
||||
return w.bw.Write(p)
|
||||
}
|
||||
|
||||
func (w *bufferedWriter) Flush() error {
|
||||
bw := w.bw
|
||||
if bw == nil {
|
||||
return nil
|
||||
}
|
||||
err := bw.Flush()
|
||||
bw.Reset(nil)
|
||||
bufWriterPool.Put(bw)
|
||||
w.bw = nil
|
||||
return err
|
||||
}
|
||||
|
||||
type bufferedWriterTimeoutWriter bufferedWriter
|
||||
|
||||
func (w *bufferedWriterTimeoutWriter) Write(p []byte) (n int, err error) {
|
||||
return writeWithByteTimeout(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 writeWithByteTimeout(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 mustUint31(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 bodyAllowedForStatus(status int) bool {
|
||||
switch {
|
||||
case status >= 100 && status <= 199:
|
||||
return false
|
||||
case status == 204:
|
||||
return false
|
||||
case status == 304:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type httpError struct {
|
||||
_ incomparable
|
||||
msg string
|
||||
timeout bool
|
||||
}
|
||||
|
||||
func (e *httpError) Error() string { return e.msg }
|
||||
func (e *httpError) Timeout() bool { return e.timeout }
|
||||
func (e *httpError) Temporary() bool { return true }
|
||||
|
||||
var errTimeout error = &httpError{msg: "http2: timeout awaiting response headers", timeout: true}
|
||||
|
||||
type connectionStater interface {
|
||||
ConnectionState() tls.ConnectionState
|
||||
}
|
||||
|
||||
var sorterPool = sync.Pool{New: func() interface{} { return new(sorter) }}
|
||||
|
||||
type sorter struct {
|
||||
v []string // owned by sorter
|
||||
}
|
||||
|
||||
func (s *sorter) Len() int { return len(s.v) }
|
||||
func (s *sorter) Swap(i, j int) { s.v[i], s.v[j] = s.v[j], s.v[i] }
|
||||
func (s *sorter) 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 *sorter) Keys(h http.Header) []string {
|
||||
keys := s.v[:0]
|
||||
for k := range h {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
s.v = keys
|
||||
sort.Sort(s)
|
||||
return keys
|
||||
}
|
||||
|
||||
func (s *sorter) 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 incomparable [0]func()
|
||||
233
src/net/http/internal/http2/http2_test.go
Normal file
233
src/net/http/internal/http2/http2_test.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var knownFailing = flag.Bool("known_failing", false, "Run known-failing tests.")
|
||||
|
||||
func condSkipFailingTest(t *testing.T) {
|
||||
if !*knownFailing {
|
||||
t.Skip("Skipping known-failing test without --known_failing")
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
DebugGoroutines = true
|
||||
flag.BoolVar(&VerboseLogs, "verboseh2", VerboseLogs, "Verbose HTTP/2 debug logging")
|
||||
}
|
||||
|
||||
func TestSettingString(t *testing.T) {
|
||||
tests := []struct {
|
||||
s Setting
|
||||
want string
|
||||
}{
|
||||
{Setting{SettingMaxFrameSize, 123}, "[MAX_FRAME_SIZE = 123]"},
|
||||
{Setting{1<<16 - 1, 123}, "[UNKNOWN_SETTING_65535 = 123]"},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := fmt.Sprint(tt.s)
|
||||
if got != tt.want {
|
||||
t.Errorf("%d. for %#v, string = %q; want %q", i, tt.s, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSorterPoolAllocs(t *testing.T) {
|
||||
ss := []string{"a", "b", "c"}
|
||||
h := http.Header{
|
||||
"a": nil,
|
||||
"b": nil,
|
||||
"c": nil,
|
||||
}
|
||||
sorter := new(sorter)
|
||||
|
||||
if allocs := testing.AllocsPerRun(100, func() {
|
||||
sorter.SortStrings(ss)
|
||||
}); allocs >= 1 {
|
||||
t.Logf("SortStrings allocs = %v; want <1", allocs)
|
||||
}
|
||||
|
||||
if allocs := testing.AllocsPerRun(5, func() {
|
||||
if len(sorter.Keys(h)) != 3 {
|
||||
t.Fatal("wrong result")
|
||||
}
|
||||
}); allocs > 0 {
|
||||
t.Logf("Keys allocs = %v; want <1", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
// waitCondition reports whether fn eventually returned true,
|
||||
// checking immediately and then every checkEvery amount,
|
||||
// until waitFor has elapsed, at which point it returns false.
|
||||
func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool {
|
||||
deadline := time.Now().Add(waitFor)
|
||||
for time.Now().Before(deadline) {
|
||||
if fn() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(checkEvery)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// waitErrCondition is like waitCondition but with errors instead of bools.
|
||||
func waitErrCondition(waitFor, checkEvery time.Duration, fn func() error) error {
|
||||
deadline := time.Now().Add(waitFor)
|
||||
var err error
|
||||
for time.Now().Before(deadline) {
|
||||
if err = fn(); err == nil {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(checkEvery)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func equalError(a, b error) bool {
|
||||
if a == nil {
|
||||
return b == nil
|
||||
}
|
||||
if b == nil {
|
||||
return a == nil
|
||||
}
|
||||
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,
|
||||
"Title": true,
|
||||
"ToLower": true,
|
||||
"ToLowerSpecial": true,
|
||||
"ToTitle": true,
|
||||
"ToTitleSpecial": true,
|
||||
"ToUpper": true,
|
||||
"ToUpperSpecial": true,
|
||||
|
||||
// Functions that use Unicode-aware spaces.
|
||||
"Fields": true,
|
||||
"TrimSpace": true,
|
||||
}
|
||||
|
||||
// TestNoUnicodeStrings checks that nothing in net/http uses the Unicode-aware
|
||||
// strings and bytes package functions. HTTP is mostly ASCII based, and doing
|
||||
// Unicode-aware case folding or space stripping can introduce vulnerabilities.
|
||||
func TestNoUnicodeStrings(t *testing.T) {
|
||||
re := regexp.MustCompile(`(strings|bytes).([A-Za-z]+)`)
|
||||
if err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if path == "h2i" || path == "h2c" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !strings.HasSuffix(path, ".go") ||
|
||||
strings.HasSuffix(path, "_test.go") ||
|
||||
path == "ascii.go" || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
contents, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for lineNum, line := range strings.Split(string(contents), "\n") {
|
||||
for _, match := range re.FindAllStringSubmatch(line, -1) {
|
||||
if !forbiddenStringsFunctions[match[2]] {
|
||||
continue
|
||||
}
|
||||
t.Errorf("disallowed call to %s at %s:%d", match[0], path, lineNum+1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetForTest sets *p = v, and restores its original value in t.Cleanup.
|
||||
func SetForTest[T any](t testing.TB, p *T, v T) {
|
||||
orig := *p
|
||||
t.Cleanup(func() {
|
||||
*p = orig
|
||||
})
|
||||
*p = v
|
||||
}
|
||||
|
||||
// Must returns v if err is nil, or panics otherwise.
|
||||
func Must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
357
src/net/http/internal/http2/netconn_test.go
Normal file
357
src/net/http/internal/http2/netconn_test.go
Normal file
@@ -0,0 +1,357 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
// synctestNetPipe creates an in-memory, full duplex network connection.
|
||||
// Read and write timeouts are managed by the synctest group.
|
||||
//
|
||||
// Unlike net.Pipe, the connection is not synchronous.
|
||||
// Writes are made to a buffer, and return immediately.
|
||||
// By default, the buffer size is unlimited.
|
||||
func synctestNetPipe() (r, w *synctestNetConn) {
|
||||
s1addr := net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:8000"))
|
||||
s2addr := net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:8001"))
|
||||
s1 := newSynctestNetConnHalf(s1addr)
|
||||
s2 := newSynctestNetConnHalf(s2addr)
|
||||
r = &synctestNetConn{loc: s1, rem: s2}
|
||||
w = &synctestNetConn{loc: s2, rem: s1}
|
||||
r.peer = w
|
||||
w.peer = r
|
||||
return r, w
|
||||
}
|
||||
|
||||
// A synctestNetConn is one endpoint of the connection created by synctestNetPipe.
|
||||
type synctestNetConn struct {
|
||||
// local and remote connection halves.
|
||||
// Each half contains a buffer.
|
||||
// Reads pull from the local buffer, and writes push to the remote buffer.
|
||||
loc, rem *synctestNetConnHalf
|
||||
|
||||
// When set, group.Wait is automatically called before reads and after writes.
|
||||
autoWait bool
|
||||
|
||||
// peer is the other endpoint.
|
||||
peer *synctestNetConn
|
||||
}
|
||||
|
||||
// Read reads data from the connection.
|
||||
func (c *synctestNetConn) Read(b []byte) (n int, err error) {
|
||||
if c.autoWait {
|
||||
synctest.Wait()
|
||||
}
|
||||
return c.loc.read(b)
|
||||
}
|
||||
|
||||
// Peek returns the available unread read buffer,
|
||||
// without consuming its contents.
|
||||
func (c *synctestNetConn) Peek() []byte {
|
||||
if c.autoWait {
|
||||
synctest.Wait()
|
||||
}
|
||||
return c.loc.peek()
|
||||
}
|
||||
|
||||
// Write writes data to the connection.
|
||||
func (c *synctestNetConn) Write(b []byte) (n int, err error) {
|
||||
if c.autoWait {
|
||||
defer synctest.Wait()
|
||||
}
|
||||
return c.rem.write(b)
|
||||
}
|
||||
|
||||
// IsClosedByPeer reports whether the peer has closed its end of the connection.
|
||||
func (c *synctestNetConn) IsClosedByPeer() bool {
|
||||
if c.autoWait {
|
||||
synctest.Wait()
|
||||
}
|
||||
return c.loc.isClosedByPeer()
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c *synctestNetConn) Close() error {
|
||||
c.loc.setWriteError(errors.New("connection closed by peer"))
|
||||
c.rem.setReadError(io.EOF)
|
||||
if c.autoWait {
|
||||
synctest.Wait()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the (fake) local network address.
|
||||
func (c *synctestNetConn) LocalAddr() net.Addr {
|
||||
return c.loc.addr
|
||||
}
|
||||
|
||||
// RemoteAddr returns the (fake) remote network address.
|
||||
func (c *synctestNetConn) RemoteAddr() net.Addr {
|
||||
return c.rem.addr
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines for the connection.
|
||||
func (c *synctestNetConn) SetDeadline(t time.Time) error {
|
||||
c.SetReadDeadline(t)
|
||||
c.SetWriteDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the read deadline for the connection.
|
||||
func (c *synctestNetConn) SetReadDeadline(t time.Time) error {
|
||||
c.loc.rctx.setDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the write deadline for the connection.
|
||||
func (c *synctestNetConn) SetWriteDeadline(t time.Time) error {
|
||||
c.rem.wctx.setDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadBufferSize sets the read buffer limit for the connection.
|
||||
// Writes by the peer will block so long as the buffer is full.
|
||||
func (c *synctestNetConn) SetReadBufferSize(size int) {
|
||||
c.loc.setReadBufferSize(size)
|
||||
}
|
||||
|
||||
// synctestNetConnHalf is one data flow in the connection created by synctestNetPipe.
|
||||
// Each half contains a buffer. Writes to the half push to the buffer, and reads pull from it.
|
||||
type synctestNetConnHalf struct {
|
||||
addr net.Addr
|
||||
|
||||
// Read and write timeouts.
|
||||
rctx, wctx deadlineContext
|
||||
|
||||
// A half can be readable and/or writable.
|
||||
//
|
||||
// These four channels act as a lock,
|
||||
// and allow waiting for readability/writability.
|
||||
// When the half is unlocked, exactly one channel contains a value.
|
||||
// When the half is locked, all channels are empty.
|
||||
lockr chan struct{} // readable
|
||||
lockw chan struct{} // writable
|
||||
lockrw chan struct{} // readable and writable
|
||||
lockc chan struct{} // neither readable nor writable
|
||||
|
||||
bufMax int // maximum buffer size
|
||||
buf bytes.Buffer
|
||||
readErr error // error returned by reads
|
||||
writeErr error // error returned by writes
|
||||
}
|
||||
|
||||
func newSynctestNetConnHalf(addr net.Addr) *synctestNetConnHalf {
|
||||
h := &synctestNetConnHalf{
|
||||
addr: addr,
|
||||
lockw: make(chan struct{}, 1),
|
||||
lockr: make(chan struct{}, 1),
|
||||
lockrw: make(chan struct{}, 1),
|
||||
lockc: make(chan struct{}, 1),
|
||||
bufMax: math.MaxInt, // unlimited
|
||||
}
|
||||
h.unlock()
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) lock() {
|
||||
select {
|
||||
case <-h.lockw:
|
||||
case <-h.lockr:
|
||||
case <-h.lockrw:
|
||||
case <-h.lockc:
|
||||
}
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) unlock() {
|
||||
canRead := h.readErr != nil || h.buf.Len() > 0
|
||||
canWrite := h.writeErr != nil || h.bufMax > h.buf.Len()
|
||||
switch {
|
||||
case canRead && canWrite:
|
||||
h.lockrw <- struct{}{}
|
||||
case canRead:
|
||||
h.lockr <- struct{}{}
|
||||
case canWrite:
|
||||
h.lockw <- struct{}{}
|
||||
default:
|
||||
h.lockc <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) readWaitAndLock() error {
|
||||
select {
|
||||
case <-h.lockr:
|
||||
return nil
|
||||
case <-h.lockrw:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
ctx := h.rctx.context()
|
||||
select {
|
||||
case <-h.lockr:
|
||||
return nil
|
||||
case <-h.lockrw:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return context.Cause(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) writeWaitAndLock() error {
|
||||
select {
|
||||
case <-h.lockw:
|
||||
return nil
|
||||
case <-h.lockrw:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
ctx := h.wctx.context()
|
||||
select {
|
||||
case <-h.lockw:
|
||||
return nil
|
||||
case <-h.lockrw:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return context.Cause(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) peek() []byte {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
return h.buf.Bytes()
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) isClosedByPeer() bool {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
return h.readErr != nil
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) read(b []byte) (n int, err error) {
|
||||
if err := h.readWaitAndLock(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer h.unlock()
|
||||
if h.buf.Len() == 0 && h.readErr != nil {
|
||||
return 0, h.readErr
|
||||
}
|
||||
return h.buf.Read(b)
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) setReadBufferSize(size int) {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
h.bufMax = size
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) write(b []byte) (n int, err error) {
|
||||
for n < len(b) {
|
||||
nn, err := h.writePartial(b[n:])
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) writePartial(b []byte) (n int, err error) {
|
||||
if err := h.writeWaitAndLock(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer h.unlock()
|
||||
if h.writeErr != nil {
|
||||
return 0, h.writeErr
|
||||
}
|
||||
writeMax := h.bufMax - h.buf.Len()
|
||||
if writeMax < len(b) {
|
||||
b = b[:writeMax]
|
||||
}
|
||||
return h.buf.Write(b)
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) setReadError(err error) {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
if h.readErr == nil {
|
||||
h.readErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (h *synctestNetConnHalf) setWriteError(err error) {
|
||||
h.lock()
|
||||
defer h.unlock()
|
||||
if h.writeErr == nil {
|
||||
h.writeErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// deadlineContext converts a changeable deadline (as in net.Conn.SetDeadline) into a Context.
|
||||
type deadlineContext struct {
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
cancel context.CancelCauseFunc
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// context returns a Context which expires when the deadline does.
|
||||
func (t *deadlineContext) context() context.Context {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.ctx == nil {
|
||||
t.ctx, t.cancel = context.WithCancelCause(context.Background())
|
||||
}
|
||||
return t.ctx
|
||||
}
|
||||
|
||||
// setDeadline sets the current deadline.
|
||||
func (t *deadlineContext) setDeadline(deadline time.Time) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
// If t.ctx is non-nil and t.cancel is nil, then t.ctx was canceled
|
||||
// and we should create a new one.
|
||||
if t.ctx == nil || t.cancel == nil {
|
||||
t.ctx, t.cancel = context.WithCancelCause(context.Background())
|
||||
}
|
||||
// Stop any existing deadline from expiring.
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
}
|
||||
if deadline.IsZero() {
|
||||
// No deadline.
|
||||
return
|
||||
}
|
||||
if !deadline.After(time.Now()) {
|
||||
// Deadline has already expired.
|
||||
t.cancel(os.ErrDeadlineExceeded)
|
||||
t.cancel = nil
|
||||
return
|
||||
}
|
||||
if t.timer != nil {
|
||||
// Reuse existing deadline timer.
|
||||
t.timer.Reset(deadline.Sub(time.Now()))
|
||||
return
|
||||
}
|
||||
// Create a new timer to cancel the context at the deadline.
|
||||
t.timer = time.AfterFunc(deadline.Sub(time.Now()), func() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.cancel(os.ErrDeadlineExceeded)
|
||||
t.cancel = nil
|
||||
})
|
||||
}
|
||||
186
src/net/http/internal/http2/pipe.go
Normal file
186
src/net/http/internal/http2/pipe.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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 pipe struct {
|
||||
mu sync.Mutex
|
||||
c sync.Cond // c.L lazily initialized to &p.mu
|
||||
b pipeBuffer // 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 pipeBuffer interface {
|
||||
Len() int
|
||||
io.Writer
|
||||
io.Reader
|
||||
}
|
||||
|
||||
// setBuffer initializes the pipe buffer.
|
||||
// It has no effect if the pipe is already closed.
|
||||
func (p *pipe) setBuffer(b pipeBuffer) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.err != nil || p.breakErr != nil {
|
||||
return
|
||||
}
|
||||
p.b = b
|
||||
}
|
||||
|
||||
func (p *pipe) 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 *pipe) 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 (
|
||||
errClosedPipeWrite = errors.New("write on closed buffer")
|
||||
errUninitializedPipeWrite = 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 *pipe) 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, errClosedPipeWrite
|
||||
}
|
||||
// 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, errUninitializedPipeWrite
|
||||
}
|
||||
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 *pipe) 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 *pipe) 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 *pipe) closeWithErrorAndCode(err error, fn func()) { p.closeWithError(&p.err, err, fn) }
|
||||
|
||||
func (p *pipe) 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 *pipe) 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 *pipe) 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 *pipe) 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
|
||||
}
|
||||
143
src/net/http/internal/http2/pipe_test.go
Normal file
143
src/net/http/internal/http2/pipe_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPipeClose(t *testing.T) {
|
||||
var p pipe
|
||||
p.b = new(bytes.Buffer)
|
||||
a := errors.New("a")
|
||||
b := errors.New("b")
|
||||
p.CloseWithError(a)
|
||||
p.CloseWithError(b)
|
||||
_, err := p.Read(make([]byte, 1))
|
||||
if err != a {
|
||||
t.Errorf("err = %v want %v", err, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeDoneChan(t *testing.T) {
|
||||
var p pipe
|
||||
done := p.Done()
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatal("done too soon")
|
||||
default:
|
||||
}
|
||||
p.CloseWithError(io.EOF)
|
||||
select {
|
||||
case <-done:
|
||||
default:
|
||||
t.Fatal("should be done")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeDoneChan_ErrFirst(t *testing.T) {
|
||||
var p pipe
|
||||
p.CloseWithError(io.EOF)
|
||||
done := p.Done()
|
||||
select {
|
||||
case <-done:
|
||||
default:
|
||||
t.Fatal("should be done")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeDoneChan_Break(t *testing.T) {
|
||||
var p pipe
|
||||
done := p.Done()
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatal("done too soon")
|
||||
default:
|
||||
}
|
||||
p.BreakWithError(io.EOF)
|
||||
select {
|
||||
case <-done:
|
||||
default:
|
||||
t.Fatal("should be done")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeDoneChan_Break_ErrFirst(t *testing.T) {
|
||||
var p pipe
|
||||
p.BreakWithError(io.EOF)
|
||||
done := p.Done()
|
||||
select {
|
||||
case <-done:
|
||||
default:
|
||||
t.Fatal("should be done")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeCloseWithError(t *testing.T) {
|
||||
p := &pipe{b: new(bytes.Buffer)}
|
||||
const body = "foo"
|
||||
io.WriteString(p, body)
|
||||
a := errors.New("test error")
|
||||
p.CloseWithError(a)
|
||||
all, err := io.ReadAll(p)
|
||||
if string(all) != body {
|
||||
t.Errorf("read bytes = %q; want %q", all, body)
|
||||
}
|
||||
if err != a {
|
||||
t.Logf("read error = %v, %v", err, a)
|
||||
}
|
||||
if p.Len() != 0 {
|
||||
t.Errorf("pipe should have 0 unread bytes")
|
||||
}
|
||||
// Read and Write should fail.
|
||||
if n, err := p.Write([]byte("abc")); err != errClosedPipeWrite || n != 0 {
|
||||
t.Errorf("Write(abc) after close\ngot %v, %v\nwant 0, %v", n, err, errClosedPipeWrite)
|
||||
}
|
||||
if n, err := p.Read(make([]byte, 1)); err == nil || n != 0 {
|
||||
t.Errorf("Read() after close\ngot %v, nil\nwant 0, %v", n, errClosedPipeWrite)
|
||||
}
|
||||
if p.Len() != 0 {
|
||||
t.Errorf("pipe should have 0 unread bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipeBreakWithError(t *testing.T) {
|
||||
p := &pipe{b: new(bytes.Buffer)}
|
||||
io.WriteString(p, "foo")
|
||||
a := errors.New("test err")
|
||||
p.BreakWithError(a)
|
||||
all, err := io.ReadAll(p)
|
||||
if string(all) != "" {
|
||||
t.Errorf("read bytes = %q; want empty string", all)
|
||||
}
|
||||
if err != a {
|
||||
t.Logf("read error = %v, %v", err, a)
|
||||
}
|
||||
if p.b != nil {
|
||||
t.Errorf("buffer should be nil after BreakWithError")
|
||||
}
|
||||
if p.Len() != 3 {
|
||||
t.Errorf("pipe should have 3 unread bytes")
|
||||
}
|
||||
// Write should fail.
|
||||
if n, err := p.Write([]byte("abc")); err != errClosedPipeWrite || n != 0 {
|
||||
t.Errorf("Write(abc) after break\ngot %v, %v\nwant 0, errClosedPipeWrite", n, err)
|
||||
}
|
||||
if p.b != nil {
|
||||
t.Errorf("buffer should be nil after Write")
|
||||
}
|
||||
if p.Len() != 3 {
|
||||
t.Errorf("pipe should have 6 unread bytes")
|
||||
}
|
||||
// Read should fail.
|
||||
if n, err := p.Read(make([]byte, 1)); err == nil || n != 0 {
|
||||
t.Errorf("Read() after close\ngot %v, nil\nwant 0, not nil", n)
|
||||
}
|
||||
}
|
||||
3413
src/net/http/internal/http2/server.go
Normal file
3413
src/net/http/internal/http2/server.go
Normal file
File diff suppressed because it is too large
Load Diff
86
src/net/http/internal/http2/server_internal_test.go
Normal file
86
src/net/http/internal/http2/server_internal_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckValidHTTP2Request(t *testing.T) {
|
||||
tests := []struct {
|
||||
h http.Header
|
||||
want error
|
||||
}{
|
||||
{
|
||||
h: http.Header{"Te": {"trailers"}},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
h: http.Header{"Te": {"trailers", "bogus"}},
|
||||
want: errors.New(`request header "TE" may only be "trailers" in HTTP/2`),
|
||||
},
|
||||
{
|
||||
h: http.Header{"Foo": {""}},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
h: http.Header{"Connection": {""}},
|
||||
want: errors.New(`request header "Connection" is not valid in HTTP/2`),
|
||||
},
|
||||
{
|
||||
h: http.Header{"Proxy-Connection": {""}},
|
||||
want: errors.New(`request header "Proxy-Connection" is not valid in HTTP/2`),
|
||||
},
|
||||
{
|
||||
h: http.Header{"Keep-Alive": {""}},
|
||||
want: errors.New(`request header "Keep-Alive" is not valid in HTTP/2`),
|
||||
},
|
||||
{
|
||||
h: http.Header{"Upgrade": {""}},
|
||||
want: errors.New(`request header "Upgrade" is not valid in HTTP/2`),
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := checkValidHTTP2RequestHeaders(tt.h)
|
||||
if !equalError(got, tt.want) {
|
||||
t.Errorf("%d. checkValidHTTP2Request = %v; want %v", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCanonicalHeaderCacheGrowth verifies that the canonical header cache
|
||||
// size is capped to a reasonable level.
|
||||
func TestCanonicalHeaderCacheGrowth(t *testing.T) {
|
||||
for _, size := range []int{1, (1 << 20) - 10} {
|
||||
base := strings.Repeat("X", size)
|
||||
sc := &serverConn{
|
||||
serveG: newGoroutineLock(),
|
||||
}
|
||||
count := 0
|
||||
added := 0
|
||||
for added < 10*maxCachedCanonicalHeadersKeysSize {
|
||||
h := fmt.Sprintf("%v-%v", base, count)
|
||||
c := sc.canonicalHeader(h)
|
||||
if len(h) != len(c) {
|
||||
t.Errorf("sc.canonicalHeader(%q) = %q, want same length", h, c)
|
||||
}
|
||||
count++
|
||||
added += len(h)
|
||||
}
|
||||
total := 0
|
||||
for k, v := range sc.canonHeader {
|
||||
total += len(k) + len(v) + 100
|
||||
}
|
||||
if total > maxCachedCanonicalHeadersKeysSize {
|
||||
t.Errorf("after adding %v ~%v-byte headers, canonHeader cache is ~%v bytes, want <%v", count, size, total, maxCachedCanonicalHeadersKeysSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
574
src/net/http/internal/http2/server_push_test.go
Normal file
574
src/net/http/internal/http2/server_push_test.go
Normal file
@@ -0,0 +1,574 @@
|
||||
// Copyright 2016 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 ignore
|
||||
|
||||
package http2_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
. "golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func TestServer_Push_Success(t *testing.T) { synctestTest(t, testServer_Push_Success) }
|
||||
func testServer_Push_Success(t testing.TB) {
|
||||
const (
|
||||
mainBody = "<html>index page</html>"
|
||||
pushedBody = "<html>pushed page</html>"
|
||||
userAgent = "testagent"
|
||||
cookie = "testcookie"
|
||||
)
|
||||
|
||||
var stURL string
|
||||
checkPromisedReq := func(r *http.Request, wantMethod string, wantH http.Header) error {
|
||||
if got, want := r.Method, wantMethod; got != want {
|
||||
return fmt.Errorf("promised Req.Method=%q, want %q", got, want)
|
||||
}
|
||||
if got, want := r.Header, wantH; !reflect.DeepEqual(got, want) {
|
||||
return fmt.Errorf("promised Req.Header=%q, want %q", got, want)
|
||||
}
|
||||
if got, want := "https://"+r.Host, stURL; got != want {
|
||||
return fmt.Errorf("promised Req.Host=%q, want %q", got, want)
|
||||
}
|
||||
if r.Body == nil {
|
||||
return fmt.Errorf("nil Body")
|
||||
}
|
||||
if buf, err := io.ReadAll(r.Body); err != nil || len(buf) != 0 {
|
||||
return fmt.Errorf("ReadAll(Body)=%q,%v, want '',nil", buf, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
errc := make(chan error, 3)
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.RequestURI() {
|
||||
case "/":
|
||||
// Push "/pushed?get" as a GET request, using an absolute URL.
|
||||
opt := &http.PushOptions{
|
||||
Header: http.Header{
|
||||
"User-Agent": {userAgent},
|
||||
},
|
||||
}
|
||||
if err := w.(http.Pusher).Push(stURL+"/pushed?get", opt); err != nil {
|
||||
errc <- fmt.Errorf("error pushing /pushed?get: %v", err)
|
||||
return
|
||||
}
|
||||
// Push "/pushed?head" as a HEAD request, using a path.
|
||||
opt = &http.PushOptions{
|
||||
Method: "HEAD",
|
||||
Header: http.Header{
|
||||
"User-Agent": {userAgent},
|
||||
"Cookie": {cookie},
|
||||
},
|
||||
}
|
||||
if err := w.(http.Pusher).Push("/pushed?head", opt); err != nil {
|
||||
errc <- fmt.Errorf("error pushing /pushed?head: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(mainBody)))
|
||||
w.WriteHeader(200)
|
||||
io.WriteString(w, mainBody)
|
||||
errc <- nil
|
||||
|
||||
case "/pushed?get":
|
||||
wantH := http.Header{}
|
||||
wantH.Set("User-Agent", userAgent)
|
||||
if err := checkPromisedReq(r, "GET", wantH); err != nil {
|
||||
errc <- fmt.Errorf("/pushed?get: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(pushedBody)))
|
||||
w.WriteHeader(200)
|
||||
io.WriteString(w, pushedBody)
|
||||
errc <- nil
|
||||
|
||||
case "/pushed?head":
|
||||
wantH := http.Header{}
|
||||
wantH.Set("User-Agent", userAgent)
|
||||
wantH.Set("Cookie", cookie)
|
||||
if err := checkPromisedReq(r, "HEAD", wantH); err != nil {
|
||||
errc <- fmt.Errorf("/pushed?head: %v", err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
errc <- nil
|
||||
|
||||
default:
|
||||
errc <- fmt.Errorf("unknown RequestURL %q", r.URL.RequestURI())
|
||||
}
|
||||
})
|
||||
stURL = "https://" + st.authority()
|
||||
|
||||
// Send one request, which should push two responses.
|
||||
st.greet()
|
||||
getSlash(st)
|
||||
for k := 0; k < 3; k++ {
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("timeout waiting for handler %d to finish", k)
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkPushPromise := func(f Frame, promiseID uint32, wantH [][2]string) error {
|
||||
pp, ok := f.(*PushPromiseFrame)
|
||||
if !ok {
|
||||
return fmt.Errorf("got a %T; want *PushPromiseFrame", f)
|
||||
}
|
||||
if !pp.HeadersEnded() {
|
||||
return fmt.Errorf("want END_HEADERS flag in PushPromiseFrame")
|
||||
}
|
||||
if got, want := pp.PromiseID, promiseID; got != want {
|
||||
return fmt.Errorf("got PromiseID %v; want %v", got, want)
|
||||
}
|
||||
gotH := st.decodeHeader(pp.HeaderBlockFragment())
|
||||
if !reflect.DeepEqual(gotH, wantH) {
|
||||
return fmt.Errorf("got promised headers %v; want %v", gotH, wantH)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
checkHeaders := func(f Frame, wantH [][2]string) error {
|
||||
hf, ok := f.(*HeadersFrame)
|
||||
if !ok {
|
||||
return fmt.Errorf("got a %T; want *HeadersFrame", f)
|
||||
}
|
||||
gotH := st.decodeHeader(hf.HeaderBlockFragment())
|
||||
if !reflect.DeepEqual(gotH, wantH) {
|
||||
return fmt.Errorf("got response headers %v; want %v", gotH, wantH)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
checkData := func(f Frame, wantData string) error {
|
||||
df, ok := f.(*DataFrame)
|
||||
if !ok {
|
||||
return fmt.Errorf("got a %T; want *DataFrame", f)
|
||||
}
|
||||
if gotData := string(df.Data()); gotData != wantData {
|
||||
return fmt.Errorf("got response data %q; want %q", gotData, wantData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream 1 has 2 PUSH_PROMISE + HEADERS + DATA
|
||||
// Stream 2 has HEADERS + DATA
|
||||
// Stream 4 has HEADERS
|
||||
expected := map[uint32][]func(Frame) error{
|
||||
1: {
|
||||
func(f Frame) error {
|
||||
return checkPushPromise(f, 2, [][2]string{
|
||||
{":method", "GET"},
|
||||
{":scheme", "https"},
|
||||
{":authority", st.authority()},
|
||||
{":path", "/pushed?get"},
|
||||
{"user-agent", userAgent},
|
||||
})
|
||||
},
|
||||
func(f Frame) error {
|
||||
return checkPushPromise(f, 4, [][2]string{
|
||||
{":method", "HEAD"},
|
||||
{":scheme", "https"},
|
||||
{":authority", st.authority()},
|
||||
{":path", "/pushed?head"},
|
||||
{"cookie", cookie},
|
||||
{"user-agent", userAgent},
|
||||
})
|
||||
},
|
||||
func(f Frame) error {
|
||||
return checkHeaders(f, [][2]string{
|
||||
{":status", "200"},
|
||||
{"content-type", "text/html"},
|
||||
{"content-length", strconv.Itoa(len(mainBody))},
|
||||
})
|
||||
},
|
||||
func(f Frame) error {
|
||||
return checkData(f, mainBody)
|
||||
},
|
||||
},
|
||||
2: {
|
||||
func(f Frame) error {
|
||||
return checkHeaders(f, [][2]string{
|
||||
{":status", "200"},
|
||||
{"content-type", "text/html"},
|
||||
{"content-length", strconv.Itoa(len(pushedBody))},
|
||||
})
|
||||
},
|
||||
func(f Frame) error {
|
||||
return checkData(f, pushedBody)
|
||||
},
|
||||
},
|
||||
4: {
|
||||
func(f Frame) error {
|
||||
return checkHeaders(f, [][2]string{
|
||||
{":status", "204"},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
consumed := map[uint32]int{}
|
||||
for k := 0; len(expected) > 0; k++ {
|
||||
f := st.readFrame()
|
||||
if f == nil {
|
||||
for id, left := range expected {
|
||||
t.Errorf("stream %d: missing %d frames", id, len(left))
|
||||
}
|
||||
break
|
||||
}
|
||||
id := f.Header().StreamID
|
||||
label := fmt.Sprintf("stream %d, frame %d", id, consumed[id])
|
||||
if len(expected[id]) == 0 {
|
||||
t.Fatalf("%s: unexpected frame %#+v", label, f)
|
||||
}
|
||||
check := expected[id][0]
|
||||
expected[id] = expected[id][1:]
|
||||
if len(expected[id]) == 0 {
|
||||
delete(expected, id)
|
||||
}
|
||||
if err := check(f); err != nil {
|
||||
t.Fatalf("%s: %v", label, err)
|
||||
}
|
||||
consumed[id]++
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_Push_SuccessNoRace(t *testing.T) { synctestTest(t, testServer_Push_SuccessNoRace) }
|
||||
func testServer_Push_SuccessNoRace(t testing.TB) {
|
||||
// Regression test for issue #18326. Ensure the request handler can mutate
|
||||
// pushed request headers without racing with the PUSH_PROMISE write.
|
||||
errc := make(chan error, 2)
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.RequestURI() {
|
||||
case "/":
|
||||
opt := &http.PushOptions{
|
||||
Header: http.Header{"User-Agent": {"testagent"}},
|
||||
}
|
||||
if err := w.(http.Pusher).Push("/pushed", opt); err != nil {
|
||||
errc <- fmt.Errorf("error pushing: %v", err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
errc <- nil
|
||||
|
||||
case "/pushed":
|
||||
// Update request header, ensure there is no race.
|
||||
r.Header.Set("User-Agent", "newagent")
|
||||
r.Header.Set("Cookie", "cookie")
|
||||
w.WriteHeader(200)
|
||||
errc <- nil
|
||||
|
||||
default:
|
||||
errc <- fmt.Errorf("unknown RequestURL %q", r.URL.RequestURI())
|
||||
}
|
||||
})
|
||||
|
||||
// Send one request, which should push one response.
|
||||
st.greet()
|
||||
getSlash(st)
|
||||
for k := 0; k < 2; k++ {
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("timeout waiting for handler %d to finish", k)
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectRecursivePush(t *testing.T) {
|
||||
synctestTest(t, testServer_Push_RejectRecursivePush)
|
||||
}
|
||||
func testServer_Push_RejectRecursivePush(t testing.TB) {
|
||||
// Expect two requests, but might get three if there's a bug and the second push succeeds.
|
||||
errc := make(chan error, 3)
|
||||
handler := func(w http.ResponseWriter, r *http.Request) error {
|
||||
baseURL := "https://" + r.Host
|
||||
switch r.URL.Path {
|
||||
case "/":
|
||||
if err := w.(http.Pusher).Push(baseURL+"/push1", nil); err != nil {
|
||||
return fmt.Errorf("first Push()=%v, want nil", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
case "/push1":
|
||||
if got, want := w.(http.Pusher).Push(baseURL+"/push2", nil), ErrRecursivePush; got != want {
|
||||
return fmt.Errorf("Push()=%v, want %v", got, want)
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected path: %q", r.URL.Path)
|
||||
}
|
||||
}
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
errc <- handler(w, r)
|
||||
})
|
||||
defer st.Close()
|
||||
st.greet()
|
||||
getSlash(st)
|
||||
if err := <-errc; err != nil {
|
||||
t.Errorf("First request failed: %v", err)
|
||||
}
|
||||
if err := <-errc; err != nil {
|
||||
t.Errorf("Second request failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testServer_Push_RejectSingleRequest(t *testing.T, doPush func(http.Pusher, *http.Request) error, settings ...Setting) {
|
||||
synctestTest(t, func(t testing.TB) {
|
||||
testServer_Push_RejectSingleRequest_Bubble(t, doPush, settings...)
|
||||
})
|
||||
}
|
||||
func testServer_Push_RejectSingleRequest_Bubble(t testing.TB, doPush func(http.Pusher, *http.Request) error, settings ...Setting) {
|
||||
// Expect one request, but might get two if there's a bug and the push succeeds.
|
||||
errc := make(chan error, 2)
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
errc <- doPush(w.(http.Pusher), r)
|
||||
})
|
||||
defer st.Close()
|
||||
st.greet()
|
||||
if err := st.fr.WriteSettings(settings...); err != nil {
|
||||
st.t.Fatalf("WriteSettings: %v", err)
|
||||
}
|
||||
st.wantSettingsAck()
|
||||
getSlash(st)
|
||||
if err := <-errc; err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// Should not get a PUSH_PROMISE frame.
|
||||
st.wantHeaders(wantHeader{
|
||||
streamID: 1,
|
||||
endStream: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectIfDisabled(t *testing.T) {
|
||||
testServer_Push_RejectSingleRequest(t,
|
||||
func(p http.Pusher, r *http.Request) error {
|
||||
if got, want := p.Push("https://"+r.Host+"/pushed", nil), http.ErrNotSupported; got != want {
|
||||
return fmt.Errorf("Push()=%v, want %v", got, want)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Setting{SettingEnablePush, 0})
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectWhenNoConcurrentStreams(t *testing.T) {
|
||||
testServer_Push_RejectSingleRequest(t,
|
||||
func(p http.Pusher, r *http.Request) error {
|
||||
if got, want := p.Push("https://"+r.Host+"/pushed", nil), ErrPushLimitReached; got != want {
|
||||
return fmt.Errorf("Push()=%v, want %v", got, want)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Setting{SettingMaxConcurrentStreams, 0})
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectWrongScheme(t *testing.T) {
|
||||
testServer_Push_RejectSingleRequest(t,
|
||||
func(p http.Pusher, r *http.Request) error {
|
||||
if err := p.Push("http://"+r.Host+"/pushed", nil); err == nil {
|
||||
return errors.New("Push() should have failed (push target URL is http)")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectMissingHost(t *testing.T) {
|
||||
testServer_Push_RejectSingleRequest(t,
|
||||
func(p http.Pusher, r *http.Request) error {
|
||||
if err := p.Push("https:pushed", nil); err == nil {
|
||||
return errors.New("Push() should have failed (push target URL missing host)")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectRelativePath(t *testing.T) {
|
||||
testServer_Push_RejectSingleRequest(t,
|
||||
func(p http.Pusher, r *http.Request) error {
|
||||
if err := p.Push("../test", nil); err == nil {
|
||||
return errors.New("Push() should have failed (push target is a relative path)")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectForbiddenMethod(t *testing.T) {
|
||||
testServer_Push_RejectSingleRequest(t,
|
||||
func(p http.Pusher, r *http.Request) error {
|
||||
if err := p.Push("https://"+r.Host+"/pushed", &http.PushOptions{Method: "POST"}); err == nil {
|
||||
return errors.New("Push() should have failed (cannot promise a POST)")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectForbiddenHeader(t *testing.T) {
|
||||
testServer_Push_RejectSingleRequest(t,
|
||||
func(p http.Pusher, r *http.Request) error {
|
||||
header := http.Header{
|
||||
"Content-Length": {"10"},
|
||||
"Content-Encoding": {"gzip"},
|
||||
"Trailer": {"Foo"},
|
||||
"Te": {"trailers"},
|
||||
"Host": {"test.com"},
|
||||
":authority": {"test.com"},
|
||||
}
|
||||
if err := p.Push("https://"+r.Host+"/pushed", &http.PushOptions{Header: header}); err == nil {
|
||||
return errors.New("Push() should have failed (forbidden headers)")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Push_StateTransitions(t *testing.T) {
|
||||
synctestTest(t, testServer_Push_StateTransitions)
|
||||
}
|
||||
func testServer_Push_StateTransitions(t testing.TB) {
|
||||
const body = "foo"
|
||||
|
||||
gotPromise := make(chan bool)
|
||||
finishedPush := make(chan bool)
|
||||
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.RequestURI() {
|
||||
case "/":
|
||||
if err := w.(http.Pusher).Push("/pushed", nil); err != nil {
|
||||
t.Errorf("Push error: %v", err)
|
||||
}
|
||||
// Don't finish this request until the push finishes so we don't
|
||||
// nondeterministically interleave output frames with the push.
|
||||
<-finishedPush
|
||||
case "/pushed":
|
||||
<-gotPromise
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
|
||||
w.WriteHeader(200)
|
||||
io.WriteString(w, body)
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
st.greet()
|
||||
if st.streamExists(2) {
|
||||
t.Fatal("stream 2 should be empty")
|
||||
}
|
||||
if got, want := st.streamState(2), StateIdle; got != want {
|
||||
t.Fatalf("streamState(2)=%v, want %v", got, want)
|
||||
}
|
||||
getSlash(st)
|
||||
// After the PUSH_PROMISE is sent, the stream should be stateHalfClosedRemote.
|
||||
_ = readFrame[*PushPromiseFrame](t, st)
|
||||
if got, want := st.streamState(2), StateHalfClosedRemote; got != want {
|
||||
t.Fatalf("streamState(2)=%v, want %v", got, want)
|
||||
}
|
||||
// We stall the HTTP handler for "/pushed" until the above check. If we don't
|
||||
// stall the handler, then the handler might write HEADERS and DATA and finish
|
||||
// the stream before we check st.streamState(2) -- should that happen, we'll
|
||||
// see stateClosed and fail the above check.
|
||||
close(gotPromise)
|
||||
st.wantHeaders(wantHeader{
|
||||
streamID: 2,
|
||||
endStream: false,
|
||||
})
|
||||
if got, want := st.streamState(2), StateClosed; got != want {
|
||||
t.Fatalf("streamState(2)=%v, want %v", got, want)
|
||||
}
|
||||
close(finishedPush)
|
||||
}
|
||||
|
||||
func TestServer_Push_RejectAfterGoAway(t *testing.T) {
|
||||
synctestTest(t, testServer_Push_RejectAfterGoAway)
|
||||
}
|
||||
func testServer_Push_RejectAfterGoAway(t testing.TB) {
|
||||
ready := make(chan struct{})
|
||||
errc := make(chan error, 2)
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
<-ready
|
||||
if got, want := w.(http.Pusher).Push("https://"+r.Host+"/pushed", nil), http.ErrNotSupported; got != want {
|
||||
errc <- fmt.Errorf("Push()=%v, want %v", got, want)
|
||||
}
|
||||
errc <- nil
|
||||
})
|
||||
defer st.Close()
|
||||
st.greet()
|
||||
getSlash(st)
|
||||
|
||||
// Send GOAWAY and wait for it to be processed.
|
||||
st.fr.WriteGoAway(1, ErrCodeNo, nil)
|
||||
synctest.Wait()
|
||||
close(ready)
|
||||
if err := <-errc; err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_Push_Underflow(t *testing.T) { synctestTest(t, testServer_Push_Underflow) }
|
||||
func testServer_Push_Underflow(t testing.TB) {
|
||||
// Test for #63511: Send several requests which generate PUSH_PROMISE responses,
|
||||
// verify they all complete successfully.
|
||||
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.RequestURI() {
|
||||
case "/":
|
||||
opt := &http.PushOptions{
|
||||
Header: http.Header{"User-Agent": {"testagent"}},
|
||||
}
|
||||
if err := w.(http.Pusher).Push("/pushed", opt); err != nil {
|
||||
t.Errorf("error pushing: %v", err)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
case "/pushed":
|
||||
r.Header.Set("User-Agent", "newagent")
|
||||
r.Header.Set("Cookie", "cookie")
|
||||
w.WriteHeader(200)
|
||||
default:
|
||||
t.Errorf("unknown RequestURL %q", r.URL.RequestURI())
|
||||
}
|
||||
})
|
||||
// Send several requests.
|
||||
st.greet()
|
||||
const numRequests = 4
|
||||
for i := 0; i < numRequests; i++ {
|
||||
st.writeHeaders(HeadersFrameParam{
|
||||
StreamID: uint32(1 + i*2), // clients send odd numbers
|
||||
BlockFragment: st.encodeHeader(),
|
||||
EndStream: true,
|
||||
EndHeaders: true,
|
||||
})
|
||||
}
|
||||
// Each request should result in one PUSH_PROMISE and two responses.
|
||||
numPushPromises := 0
|
||||
numHeaders := 0
|
||||
for numHeaders < numRequests*2 || numPushPromises < numRequests {
|
||||
f := st.readFrame()
|
||||
if f == nil {
|
||||
st.t.Fatal("conn is idle, want frame")
|
||||
}
|
||||
switch f := f.(type) {
|
||||
case *HeadersFrame:
|
||||
if !f.Flags.Has(FlagHeadersEndStream) {
|
||||
t.Fatalf("got HEADERS frame with no END_STREAM, expected END_STREAM: %v", f)
|
||||
}
|
||||
numHeaders++
|
||||
case *PushPromiseFrame:
|
||||
numPushPromises++
|
||||
}
|
||||
}
|
||||
}
|
||||
5413
src/net/http/internal/http2/server_test.go
Normal file
5413
src/net/http/internal/http2/server_test.go
Normal file
File diff suppressed because it is too large
Load Diff
28
src/net/http/internal/http2/synctest_test.go
Normal file
28
src/net/http/internal/http2/synctest_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
)
|
||||
|
||||
func synctestTest(t *testing.T, f func(t testing.TB)) {
|
||||
t.Helper()
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
t.Helper()
|
||||
f(t)
|
||||
})
|
||||
}
|
||||
|
||||
// synctestSubtest starts a subtest and runs f in a synctest bubble within it.
|
||||
func synctestSubtest(t *testing.T, name string, f func(testing.TB)) {
|
||||
t.Helper()
|
||||
t.Run(name, func(t *testing.T) {
|
||||
synctestTest(t, f)
|
||||
})
|
||||
}
|
||||
3438
src/net/http/internal/http2/transport.go
Normal file
3438
src/net/http/internal/http2/transport.go
Normal file
File diff suppressed because it is too large
Load Diff
295
src/net/http/internal/http2/transport_internal_test.go
Normal file
295
src/net/http/internal/http2/transport_internal_test.go
Normal file
@@ -0,0 +1,295 @@
|
||||
// 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type panicReader struct{}
|
||||
|
||||
func (panicReader) Read([]byte) (int, error) { panic("unexpected Read") }
|
||||
func (panicReader) Close() error { panic("unexpected Close") }
|
||||
|
||||
func TestActualContentLength(t *testing.T) {
|
||||
tests := []struct {
|
||||
req *http.Request
|
||||
want int64
|
||||
}{
|
||||
// Verify we don't read from Body:
|
||||
0: {
|
||||
req: &http.Request{Body: panicReader{}},
|
||||
want: -1,
|
||||
},
|
||||
// nil Body means 0, regardless of ContentLength:
|
||||
1: {
|
||||
req: &http.Request{Body: nil, ContentLength: 5},
|
||||
want: 0,
|
||||
},
|
||||
// ContentLength is used if set.
|
||||
2: {
|
||||
req: &http.Request{Body: panicReader{}, ContentLength: 5},
|
||||
want: 5,
|
||||
},
|
||||
// http.NoBody means 0, not -1.
|
||||
3: {
|
||||
req: &http.Request{Body: http.NoBody},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := actualContentLength(tt.req)
|
||||
if got != tt.want {
|
||||
t.Errorf("test[%d]: got %d; want %d", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that gzipReader doesn't crash on a second Read call following
|
||||
// the first Read call's gzip.NewReader returning an error.
|
||||
func TestGzipReader_DoubleReadCrash(t *testing.T) {
|
||||
gz := &gzipReader{
|
||||
body: io.NopCloser(strings.NewReader("0123456789")),
|
||||
}
|
||||
var buf [1]byte
|
||||
n, err1 := gz.Read(buf[:])
|
||||
if n != 0 || !strings.Contains(fmt.Sprint(err1), "invalid header") {
|
||||
t.Fatalf("Read = %v, %v; want 0, invalid header", n, err1)
|
||||
}
|
||||
n, err2 := gz.Read(buf[:])
|
||||
if n != 0 || err2 != err1 {
|
||||
t.Fatalf("second Read = %v, %v; want 0, %v", n, err2, err1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGzipReader_ReadAfterClose(t *testing.T) {
|
||||
body := bytes.Buffer{}
|
||||
w := gzip.NewWriter(&body)
|
||||
w.Write([]byte("012345679"))
|
||||
w.Close()
|
||||
gz := &gzipReader{
|
||||
body: io.NopCloser(&body),
|
||||
}
|
||||
var buf [1]byte
|
||||
n, err := gz.Read(buf[:])
|
||||
if n != 1 || err != nil {
|
||||
t.Fatalf("first Read = %v, %v; want 1, nil", n, err)
|
||||
}
|
||||
if err := gz.Close(); err != nil {
|
||||
t.Fatalf("gz Close error: %v", err)
|
||||
}
|
||||
n, err = gz.Read(buf[:])
|
||||
if n != 0 || err != fs.ErrClosed {
|
||||
t.Fatalf("Read after close = %v, %v; want 0, fs.ErrClosed", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportNewTLSConfig(t *testing.T) {
|
||||
tests := [...]struct {
|
||||
conf *tls.Config
|
||||
host string
|
||||
want *tls.Config
|
||||
}{
|
||||
// Normal case.
|
||||
0: {
|
||||
conf: nil,
|
||||
host: "foo.com",
|
||||
want: &tls.Config{
|
||||
ServerName: "foo.com",
|
||||
NextProtos: []string{NextProtoTLS},
|
||||
},
|
||||
},
|
||||
|
||||
// User-provided name (bar.com) takes precedence:
|
||||
1: {
|
||||
conf: &tls.Config{
|
||||
ServerName: "bar.com",
|
||||
},
|
||||
host: "foo.com",
|
||||
want: &tls.Config{
|
||||
ServerName: "bar.com",
|
||||
NextProtos: []string{NextProtoTLS},
|
||||
},
|
||||
},
|
||||
|
||||
// NextProto is prepended:
|
||||
2: {
|
||||
conf: &tls.Config{
|
||||
NextProtos: []string{"foo", "bar"},
|
||||
},
|
||||
host: "example.com",
|
||||
want: &tls.Config{
|
||||
ServerName: "example.com",
|
||||
NextProtos: []string{NextProtoTLS, "foo", "bar"},
|
||||
},
|
||||
},
|
||||
|
||||
// NextProto is not duplicated:
|
||||
3: {
|
||||
conf: &tls.Config{
|
||||
NextProtos: []string{"foo", "bar", NextProtoTLS},
|
||||
},
|
||||
host: "example.com",
|
||||
want: &tls.Config{
|
||||
ServerName: "example.com",
|
||||
NextProtos: []string{"foo", "bar", NextProtoTLS},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
// Ignore the session ticket keys part, which ends up populating
|
||||
// unexported fields in the Config:
|
||||
if tt.conf != nil {
|
||||
tt.conf.SessionTicketsDisabled = true
|
||||
}
|
||||
|
||||
tr := &Transport{TLSClientConfig: tt.conf}
|
||||
got := tr.newTLSConfig(tt.host)
|
||||
|
||||
got.SessionTicketsDisabled = false
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("%d. got %#v; want %#v", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorityAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
scheme, authority string
|
||||
want string
|
||||
}{
|
||||
{"http", "foo.com", "foo.com:80"},
|
||||
{"https", "foo.com", "foo.com:443"},
|
||||
{"https", "foo.com:", "foo.com:443"},
|
||||
{"https", "foo.com:1234", "foo.com:1234"},
|
||||
{"https", "1.2.3.4:1234", "1.2.3.4:1234"},
|
||||
{"https", "1.2.3.4", "1.2.3.4:443"},
|
||||
{"https", "1.2.3.4:", "1.2.3.4:443"},
|
||||
{"https", "[::1]:1234", "[::1]:1234"},
|
||||
{"https", "[::1]", "[::1]:443"},
|
||||
{"https", "[::1]:", "[::1]:443"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := authorityAddr(tt.scheme, tt.authority)
|
||||
if got != tt.want {
|
||||
t.Errorf("authorityAddr(%q, %q) = %q; want %q", tt.scheme, tt.authority, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 25009: use Request.GetBody if present, even if it seems like
|
||||
// we might not need it. Apparently something else can still read from
|
||||
// the original request body. Data race? In any case, rewinding
|
||||
// unconditionally on retry is a nicer model anyway and should
|
||||
// simplify code in the future (after the Go 1.11 freeze)
|
||||
func TestTransportUsesGetBodyWhenPresent(t *testing.T) {
|
||||
calls := 0
|
||||
someBody := func() io.ReadCloser {
|
||||
return struct{ io.ReadCloser }{io.NopCloser(bytes.NewReader(nil))}
|
||||
}
|
||||
req := &http.Request{
|
||||
Body: someBody(),
|
||||
GetBody: func() (io.ReadCloser, error) {
|
||||
calls++
|
||||
return someBody(), nil
|
||||
},
|
||||
}
|
||||
|
||||
req2, err := shouldRetryRequest(req, errClientConnUnusable)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Errorf("Calls = %d; want 1", calls)
|
||||
}
|
||||
if req2 == req {
|
||||
t.Error("req2 changed")
|
||||
}
|
||||
if req2 == nil {
|
||||
t.Fatal("req2 is nil")
|
||||
}
|
||||
if req2.Body == nil {
|
||||
t.Fatal("req2.Body is nil")
|
||||
}
|
||||
if req2.GetBody == nil {
|
||||
t.Fatal("req2.GetBody is nil")
|
||||
}
|
||||
if req2.Body == req.Body {
|
||||
t.Error("req2.Body unchanged")
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
func() *ClientConn {
|
||||
return &ClientConn{idleTimeout: 5 * time.Second, lastIdle: time.Now().Add(-10 * time.Second)}
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
func() *ClientConn {
|
||||
return &ClientConn{idleTimeout: 5 * time.Second, lastIdle: time.Time{}}
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
func() *ClientConn {
|
||||
return &ClientConn{idleTimeout: 60 * time.Second, lastIdle: time.Now().Add(-10 * time.Second)}
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
func() *ClientConn {
|
||||
return &ClientConn{idleTimeout: 0, lastIdle: time.Now().Add(-10 * time.Second)}
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := tt.cc().tooIdleLocked()
|
||||
if got != tt.want {
|
||||
t.Errorf("%d. got %v; want %v", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
5772
src/net/http/internal/http2/transport_test.go
Normal file
5772
src/net/http/internal/http2/transport_test.go
Normal file
File diff suppressed because it is too large
Load Diff
34
src/net/http/internal/http2/unencrypted.go
Normal file
34
src/net/http/internal/http2/unencrypted.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
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
|
||||
}
|
||||
383
src/net/http/internal/http2/write.go
Normal file
383
src/net/http/internal/http2/write.go
Normal file
@@ -0,0 +1,383 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/net/internal/httpcommon"
|
||||
)
|
||||
|
||||
// writeFramer is implemented by any type that is used to write frames.
|
||||
type writeFramer interface {
|
||||
writeFrame(writeContext) 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 writeContext interface {
|
||||
Framer() *Framer
|
||||
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 writeEndsStream(w writeFramer) bool {
|
||||
switch v := w.(type) {
|
||||
case *writeData:
|
||||
return v.endStream
|
||||
case *writeResHeaders:
|
||||
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 flushFrameWriter struct{}
|
||||
|
||||
func (flushFrameWriter) writeFrame(ctx writeContext) error {
|
||||
return ctx.Flush()
|
||||
}
|
||||
|
||||
func (flushFrameWriter) staysWithinBuffer(max int) bool { return false }
|
||||
|
||||
type writeSettings []Setting
|
||||
|
||||
func (s writeSettings) staysWithinBuffer(max int) bool {
|
||||
const settingSize = 6 // uint16 + uint32
|
||||
return frameHeaderLen+settingSize*len(s) <= max
|
||||
|
||||
}
|
||||
|
||||
func (s writeSettings) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WriteSettings([]Setting(s)...)
|
||||
}
|
||||
|
||||
type writeGoAway struct {
|
||||
maxStreamID uint32
|
||||
code ErrCode
|
||||
}
|
||||
|
||||
func (p *writeGoAway) writeFrame(ctx writeContext) error {
|
||||
err := ctx.Framer().WriteGoAway(p.maxStreamID, p.code, nil)
|
||||
ctx.Flush() // ignore error: we're hanging up on them anyway
|
||||
return err
|
||||
}
|
||||
|
||||
func (*writeGoAway) staysWithinBuffer(max int) bool { return false } // flushes
|
||||
|
||||
type writeData struct {
|
||||
streamID uint32
|
||||
p []byte
|
||||
endStream bool
|
||||
}
|
||||
|
||||
func (w *writeData) String() string {
|
||||
return fmt.Sprintf("writeData(stream=%d, p=%d, endStream=%v)", w.streamID, len(w.p), w.endStream)
|
||||
}
|
||||
|
||||
func (w *writeData) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WriteData(w.streamID, w.endStream, w.p)
|
||||
}
|
||||
|
||||
func (w *writeData) staysWithinBuffer(max int) bool {
|
||||
return frameHeaderLen+len(w.p) <= max
|
||||
}
|
||||
|
||||
// handlerPanicRST is the message sent from handler goroutines when
|
||||
// the handler panics.
|
||||
type handlerPanicRST struct {
|
||||
StreamID uint32
|
||||
}
|
||||
|
||||
func (hp handlerPanicRST) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WriteRSTStream(hp.StreamID, ErrCodeInternal)
|
||||
}
|
||||
|
||||
func (hp handlerPanicRST) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
|
||||
|
||||
func (se StreamError) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WriteRSTStream(se.StreamID, se.Code)
|
||||
}
|
||||
|
||||
func (se StreamError) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
|
||||
|
||||
type writePing struct {
|
||||
data [8]byte
|
||||
}
|
||||
|
||||
func (w writePing) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WritePing(false, w.data)
|
||||
}
|
||||
|
||||
func (w writePing) staysWithinBuffer(max int) bool { return frameHeaderLen+len(w.data) <= max }
|
||||
|
||||
type writePingAck struct{ pf *PingFrame }
|
||||
|
||||
func (w writePingAck) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WritePing(true, w.pf.Data)
|
||||
}
|
||||
|
||||
func (w writePingAck) staysWithinBuffer(max int) bool { return frameHeaderLen+len(w.pf.Data) <= max }
|
||||
|
||||
type writeSettingsAck struct{}
|
||||
|
||||
func (writeSettingsAck) writeFrame(ctx writeContext) error {
|
||||
return ctx.Framer().WriteSettingsAck()
|
||||
}
|
||||
|
||||
func (writeSettingsAck) staysWithinBuffer(max int) bool { return frameHeaderLen <= 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 splitHeaderBlock(ctx writeContext, headerBlock []byte, fn func(ctx writeContext, 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 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.
|
||||
endStream bool
|
||||
|
||||
date string
|
||||
contentType string
|
||||
contentLength string
|
||||
}
|
||||
|
||||
func encKV(enc *hpack.Encoder, k, v string) {
|
||||
if VerboseLogs {
|
||||
log.Printf("http2: server encoding header %q = %q", k, v)
|
||||
}
|
||||
enc.WriteField(hpack.HeaderField{Name: k, Value: v})
|
||||
}
|
||||
|
||||
func (w *writeResHeaders) 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 *writeResHeaders) writeFrame(ctx writeContext) error {
|
||||
enc, buf := ctx.HeaderEncoder()
|
||||
buf.Reset()
|
||||
|
||||
if w.httpResCode != 0 {
|
||||
encKV(enc, ":status", httpCodeString(w.httpResCode))
|
||||
}
|
||||
|
||||
encodeHeaders(enc, w.h, w.trailers)
|
||||
|
||||
if w.contentType != "" {
|
||||
encKV(enc, "content-type", w.contentType)
|
||||
}
|
||||
if w.contentLength != "" {
|
||||
encKV(enc, "content-length", w.contentLength)
|
||||
}
|
||||
if w.date != "" {
|
||||
encKV(enc, "date", w.date)
|
||||
}
|
||||
|
||||
headerBlock := buf.Bytes()
|
||||
if len(headerBlock) == 0 && w.trailers == nil {
|
||||
panic("unexpected empty hpack")
|
||||
}
|
||||
|
||||
return splitHeaderBlock(ctx, headerBlock, w.writeHeaderBlock)
|
||||
}
|
||||
|
||||
func (w *writeResHeaders) writeHeaderBlock(ctx writeContext, frag []byte, firstFrag, lastFrag bool) error {
|
||||
if firstFrag {
|
||||
return ctx.Framer().WriteHeaders(HeadersFrameParam{
|
||||
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 writePushPromise struct {
|
||||
streamID uint32 // pusher stream
|
||||
method string // for :method
|
||||
url *url.URL // for :scheme, :authority, :path
|
||||
h http.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 *writePushPromise) staysWithinBuffer(max int) bool {
|
||||
// TODO: see writeResHeaders.staysWithinBuffer
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *writePushPromise) writeFrame(ctx writeContext) error {
|
||||
enc, buf := ctx.HeaderEncoder()
|
||||
buf.Reset()
|
||||
|
||||
encKV(enc, ":method", w.method)
|
||||
encKV(enc, ":scheme", w.url.Scheme)
|
||||
encKV(enc, ":authority", w.url.Host)
|
||||
encKV(enc, ":path", w.url.RequestURI())
|
||||
encodeHeaders(enc, w.h, nil)
|
||||
|
||||
headerBlock := buf.Bytes()
|
||||
if len(headerBlock) == 0 {
|
||||
panic("unexpected empty hpack")
|
||||
}
|
||||
|
||||
return splitHeaderBlock(ctx, headerBlock, w.writeHeaderBlock)
|
||||
}
|
||||
|
||||
func (w *writePushPromise) writeHeaderBlock(ctx writeContext, frag []byte, firstFrag, lastFrag bool) error {
|
||||
if firstFrag {
|
||||
return ctx.Framer().WritePushPromise(PushPromiseParam{
|
||||
StreamID: w.streamID,
|
||||
PromiseID: w.promisedID,
|
||||
BlockFragment: frag,
|
||||
EndHeaders: lastFrag,
|
||||
})
|
||||
} else {
|
||||
return ctx.Framer().WriteContinuation(w.streamID, lastFrag, frag)
|
||||
}
|
||||
}
|
||||
|
||||
type write100ContinueHeadersFrame struct {
|
||||
streamID uint32
|
||||
}
|
||||
|
||||
func (w write100ContinueHeadersFrame) writeFrame(ctx writeContext) error {
|
||||
enc, buf := ctx.HeaderEncoder()
|
||||
buf.Reset()
|
||||
encKV(enc, ":status", "100")
|
||||
return ctx.Framer().WriteHeaders(HeadersFrameParam{
|
||||
StreamID: w.streamID,
|
||||
BlockFragment: buf.Bytes(),
|
||||
EndStream: false,
|
||||
EndHeaders: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (w write100ContinueHeadersFrame) staysWithinBuffer(max int) bool {
|
||||
// Sloppy but conservative:
|
||||
return 9+2*(len(":status")+len("100")) <= max
|
||||
}
|
||||
|
||||
type writeWindowUpdate struct {
|
||||
streamID uint32 // or 0 for conn-level
|
||||
n uint32
|
||||
}
|
||||
|
||||
func (wu writeWindowUpdate) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
|
||||
|
||||
func (wu writeWindowUpdate) writeFrame(ctx writeContext) 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 encodeHeaders(enc *hpack.Encoder, h http.Header, keys []string) {
|
||||
if keys == nil {
|
||||
sorter := sorterPool.Get().(*sorter)
|
||||
// Using defer here, since the returned keys from the
|
||||
// sorter.Keys method is only valid until the sorter
|
||||
// is returned:
|
||||
defer sorterPool.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 !validWireHeaderFieldName(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
|
||||
}
|
||||
encKV(enc, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
290
src/net/http/internal/http2/writesched.go
Normal file
290
src/net/http/internal/http2/writesched.go
Normal file
@@ -0,0 +1,290 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WriteScheduler is the interface implemented by HTTP/2 write schedulers.
|
||||
// Methods are never called concurrently.
|
||||
type WriteScheduler 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 OpenStreamOptions)
|
||||
|
||||
// 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 PriorityParam)
|
||||
|
||||
// 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 FrameWriteRequest)
|
||||
|
||||
// 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 FrameWriteRequest, ok bool)
|
||||
}
|
||||
|
||||
// OpenStreamOptions specifies extra options for WriteScheduler.OpenStream.
|
||||
type OpenStreamOptions 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 PriorityParam
|
||||
}
|
||||
|
||||
// FrameWriteRequest is a request to write a frame.
|
||||
type FrameWriteRequest 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 writeFramer
|
||||
|
||||
// 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 *stream
|
||||
|
||||
// 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 FrameWriteRequest) StreamID() uint32 {
|
||||
if wr.stream == nil {
|
||||
if se, ok := wr.write.(StreamError); 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 FrameWriteRequest) 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 FrameWriteRequest) DataSize() int {
|
||||
if wd, ok := wr.write.(*writeData); 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 FrameWriteRequest) Consume(n int32) (FrameWriteRequest, FrameWriteRequest, int) {
|
||||
var empty FrameWriteRequest
|
||||
|
||||
// Non-DATA frames are always consumed whole.
|
||||
wd, ok := wr.write.(*writeData)
|
||||
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 := FrameWriteRequest{
|
||||
stream: wr.stream,
|
||||
write: &writeData{
|
||||
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 := FrameWriteRequest{
|
||||
stream: wr.stream,
|
||||
write: &writeData{
|
||||
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 FrameWriteRequest) 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 *FrameWriteRequest) 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 writeQueue struct {
|
||||
currQueue []FrameWriteRequest
|
||||
nextQueue []FrameWriteRequest
|
||||
currPos int
|
||||
|
||||
prev, next *writeQueue
|
||||
}
|
||||
|
||||
func (q *writeQueue) empty() bool {
|
||||
return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0
|
||||
}
|
||||
|
||||
func (q *writeQueue) push(wr FrameWriteRequest) {
|
||||
q.nextQueue = append(q.nextQueue, wr)
|
||||
}
|
||||
|
||||
func (q *writeQueue) shift() FrameWriteRequest {
|
||||
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] = FrameWriteRequest{}
|
||||
q.currPos++
|
||||
return wr
|
||||
}
|
||||
|
||||
func (q *writeQueue) peek() *FrameWriteRequest {
|
||||
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 *writeQueue) consume(n int32) (FrameWriteRequest, bool) {
|
||||
if q.empty() {
|
||||
return FrameWriteRequest{}, false
|
||||
}
|
||||
consumed, rest, numresult := q.peek().Consume(n)
|
||||
switch numresult {
|
||||
case 0:
|
||||
return FrameWriteRequest{}, false
|
||||
case 1:
|
||||
q.shift()
|
||||
case 2:
|
||||
*q.peek() = rest
|
||||
}
|
||||
return consumed, true
|
||||
}
|
||||
|
||||
type writeQueuePool []*writeQueue
|
||||
|
||||
// put inserts an unused writeQueue into the pool.
|
||||
func (p *writeQueuePool) put(q *writeQueue) {
|
||||
for i := range q.currQueue {
|
||||
q.currQueue[i] = FrameWriteRequest{}
|
||||
}
|
||||
for i := range q.nextQueue {
|
||||
q.nextQueue[i] = FrameWriteRequest{}
|
||||
}
|
||||
q.currQueue = q.currQueue[:0]
|
||||
q.nextQueue = q.nextQueue[:0]
|
||||
q.currPos = 0
|
||||
*p = append(*p, q)
|
||||
}
|
||||
|
||||
// get returns an empty writeQueue.
|
||||
func (p *writeQueuePool) get() *writeQueue {
|
||||
ln := len(*p)
|
||||
if ln == 0 {
|
||||
return new(writeQueue)
|
||||
}
|
||||
x := ln - 1
|
||||
q := (*p)[x]
|
||||
(*p)[x] = nil
|
||||
*p = (*p)[:x]
|
||||
return q
|
||||
}
|
||||
195
src/net/http/internal/http2/writesched_benchmarks_test.go
Normal file
195
src/net/http/internal/http2/writesched_benchmarks_test.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func benchmarkThroughput(b *testing.B, wsFunc func() WriteScheduler, priority PriorityParam) {
|
||||
const maxFrameSize = 16
|
||||
const streamCount = 100
|
||||
|
||||
ws := wsFunc()
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
streams := make([]*stream, streamCount)
|
||||
// Possible stream payloads. We vary the payload size of different streams
|
||||
// to simulate real traffic somewhat.
|
||||
streamsFrame := [][]byte{
|
||||
make([]byte, maxFrameSize*5),
|
||||
make([]byte, maxFrameSize*10),
|
||||
make([]byte, maxFrameSize*15),
|
||||
make([]byte, maxFrameSize*20),
|
||||
make([]byte, maxFrameSize*25),
|
||||
}
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
streams[i] = &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
streams[i].flow.add(1 << 30) // arbitrary large value
|
||||
|
||||
ws.OpenStream(streamID, OpenStreamOptions{
|
||||
priority: priority,
|
||||
})
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
ws.Push(FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: streamsFrame[i%len(streamsFrame)],
|
||||
endStream: false,
|
||||
},
|
||||
stream: streams[i],
|
||||
})
|
||||
}
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
b.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
ws.CloseStream(streamID)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkStreamLifetime(b *testing.B, wsFunc func() WriteScheduler, priority PriorityParam) {
|
||||
const maxFrameSize = 16
|
||||
const streamCount = 100
|
||||
|
||||
ws := wsFunc()
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
streams := make([]*stream, streamCount)
|
||||
// Possible stream payloads. We vary the payload size of different streams
|
||||
// to simulate real traffic somewhat.
|
||||
streamsFrame := [][]byte{
|
||||
make([]byte, maxFrameSize*5),
|
||||
make([]byte, maxFrameSize*10),
|
||||
make([]byte, maxFrameSize*15),
|
||||
make([]byte, maxFrameSize*20),
|
||||
make([]byte, maxFrameSize*25),
|
||||
}
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
streams[i] = &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
streams[i].flow.add(1 << 30) // arbitrary large value
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
ws.OpenStream(streamID, OpenStreamOptions{
|
||||
priority: priority,
|
||||
})
|
||||
ws.Push(FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: streamsFrame[i%len(streamsFrame)],
|
||||
endStream: false,
|
||||
},
|
||||
stream: streams[i],
|
||||
})
|
||||
}
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
b.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
}
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
ws.CloseStream(streamID)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerThroughputRoundRobin(b *testing.B) {
|
||||
benchmarkThroughput(b, newRoundRobinWriteScheduler, PriorityParam{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerLifetimeRoundRobin(b *testing.B) {
|
||||
benchmarkStreamLifetime(b, newRoundRobinWriteScheduler, PriorityParam{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerThroughputRandom(b *testing.B) {
|
||||
benchmarkThroughput(b, NewRandomWriteScheduler, PriorityParam{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerLifetimeRandom(b *testing.B) {
|
||||
benchmarkStreamLifetime(b, NewRandomWriteScheduler, PriorityParam{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerThroughputPriorityRFC7540(b *testing.B) {
|
||||
benchmarkThroughput(b, func() WriteScheduler { return NewPriorityWriteScheduler(nil) }, PriorityParam{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerLifetimePriorityRFC7540(b *testing.B) {
|
||||
// RFC7540 priority scheduler does not always succeed in closing the
|
||||
// stream, causing this benchmark to panic due to opening an already open
|
||||
// stream.
|
||||
b.SkipNow()
|
||||
benchmarkStreamLifetime(b, func() WriteScheduler { return NewPriorityWriteScheduler(nil) }, PriorityParam{})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerThroughputPriorityRFC9218Incremental(b *testing.B) {
|
||||
benchmarkThroughput(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
|
||||
incremental: 1,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerLifetimePriorityRFC9218Incremental(b *testing.B) {
|
||||
benchmarkStreamLifetime(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
|
||||
incremental: 1,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerThroughputPriorityRFC9218NonIncremental(b *testing.B) {
|
||||
benchmarkThroughput(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
|
||||
incremental: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkWriteSchedulerLifetimePriorityRFC9218NonIncremental(b *testing.B) {
|
||||
benchmarkStreamLifetime(b, newPriorityWriteSchedulerRFC9218, PriorityParam{
|
||||
incremental: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkWriteQueue(b *testing.B) {
|
||||
var qp writeQueuePool
|
||||
frameCount := 25
|
||||
for b.Loop() {
|
||||
q := qp.get()
|
||||
for range frameCount {
|
||||
q.push(FrameWriteRequest{})
|
||||
}
|
||||
for !q.empty() {
|
||||
// Since we pushed empty frames, consuming 1 byte is enough to
|
||||
// consume the entire frame.
|
||||
q.consume(1)
|
||||
}
|
||||
qp.put(q)
|
||||
}
|
||||
}
|
||||
456
src/net/http/internal/http2/writesched_priority_rfc7540.go
Normal file
456
src/net/http/internal/http2/writesched_priority_rfc7540.go
Normal file
@@ -0,0 +1,456 @@
|
||||
// Copyright 2016 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// RFC 7540, Section 5.3.5: the default weight is 16.
|
||||
const priorityDefaultWeightRFC7540 = 15 // 16 = 15 + 1
|
||||
|
||||
// PriorityWriteSchedulerConfig configures a priorityWriteScheduler.
|
||||
type PriorityWriteSchedulerConfig 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 NewPriorityWriteScheduler(cfg *PriorityWriteSchedulerConfig) WriteScheduler {
|
||||
return newPriorityWriteSchedulerRFC7540(cfg)
|
||||
}
|
||||
|
||||
func newPriorityWriteSchedulerRFC7540(cfg *PriorityWriteSchedulerConfig) WriteScheduler {
|
||||
if cfg == nil {
|
||||
// For justification of these defaults, see:
|
||||
// https://docs.google.com/document/d/1oLhNg1skaWD4_DtaoCxdSRN5erEXrH-KnLrMwEpOtFY
|
||||
cfg = &PriorityWriteSchedulerConfig{
|
||||
MaxClosedNodesInTree: 10,
|
||||
MaxIdleNodesInTree: 10,
|
||||
ThrottleOutOfOrderWrites: false,
|
||||
}
|
||||
}
|
||||
|
||||
ws := &priorityWriteSchedulerRFC7540{
|
||||
nodes: make(map[uint32]*priorityNodeRFC7540),
|
||||
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 priorityNodeStateRFC7540 int
|
||||
|
||||
const (
|
||||
priorityNodeOpenRFC7540 priorityNodeStateRFC7540 = iota
|
||||
priorityNodeClosedRFC7540
|
||||
priorityNodeIdleRFC7540
|
||||
)
|
||||
|
||||
// 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 priorityNodeRFC7540 struct {
|
||||
q writeQueue // 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 priorityNodeStateRFC7540 // 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 *priorityNodeRFC7540
|
||||
kids *priorityNodeRFC7540 // start of the kids list
|
||||
prev, next *priorityNodeRFC7540 // doubly-linked list of siblings
|
||||
}
|
||||
|
||||
func (n *priorityNodeRFC7540) setParent(parent *priorityNodeRFC7540) {
|
||||
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 *priorityNodeRFC7540) 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 *priorityNodeRFC7540) walkReadyInOrder(openParent bool, tmp *[]*priorityNodeRFC7540, f func(*priorityNodeRFC7540, 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 == priorityNodeOpenRFC7540)
|
||||
}
|
||||
|
||||
// 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(sortPriorityNodeSiblingsRFC7540(*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 sortPriorityNodeSiblingsRFC7540 []*priorityNodeRFC7540
|
||||
|
||||
func (z sortPriorityNodeSiblingsRFC7540) Len() int { return len(z) }
|
||||
func (z sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k], z[i] }
|
||||
func (z sortPriorityNodeSiblingsRFC7540) 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 priorityWriteSchedulerRFC7540 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 priorityNodeRFC7540
|
||||
|
||||
// nodes maps stream ids to priority tree nodes.
|
||||
nodes map[uint32]*priorityNodeRFC7540
|
||||
|
||||
// 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 []*priorityNodeRFC7540
|
||||
|
||||
// From the config.
|
||||
maxClosedNodesInTree int
|
||||
maxIdleNodesInTree int
|
||||
writeThrottleLimit int32
|
||||
enableWriteThrottle bool
|
||||
|
||||
// tmp is scratch space for priorityNode.walkReadyInOrder to reduce allocations.
|
||||
tmp []*priorityNodeRFC7540
|
||||
|
||||
// pool of empty queues for reuse.
|
||||
queuePool writeQueuePool
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC7540) OpenStream(streamID uint32, options OpenStreamOptions) {
|
||||
// The stream may be currently idle but cannot be opened or closed.
|
||||
if curr := ws.nodes[streamID]; curr != nil {
|
||||
if curr.state != priorityNodeIdleRFC7540 {
|
||||
panic(fmt.Sprintf("stream %d already opened", streamID))
|
||||
}
|
||||
curr.state = priorityNodeOpenRFC7540
|
||||
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 := &priorityNodeRFC7540{
|
||||
q: *ws.queuePool.get(),
|
||||
id: streamID,
|
||||
weight: priorityDefaultWeightRFC7540,
|
||||
state: priorityNodeOpenRFC7540,
|
||||
}
|
||||
n.setParent(parent)
|
||||
ws.nodes[streamID] = n
|
||||
if streamID > ws.maxID {
|
||||
ws.maxID = streamID
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC7540) 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 != priorityNodeOpenRFC7540 {
|
||||
panic(fmt.Sprintf("violation of WriteScheduler interface: stream %d already closed", streamID))
|
||||
}
|
||||
|
||||
n := ws.nodes[streamID]
|
||||
n.state = priorityNodeClosedRFC7540
|
||||
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 *priorityWriteSchedulerRFC7540) AdjustStream(streamID uint32, priority PriorityParam) {
|
||||
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 = &priorityNodeRFC7540{
|
||||
q: *ws.queuePool.get(),
|
||||
id: streamID,
|
||||
weight: priorityDefaultWeightRFC7540,
|
||||
state: priorityNodeIdleRFC7540,
|
||||
}
|
||||
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 = priorityDefaultWeightRFC7540
|
||||
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 *priorityWriteSchedulerRFC7540) Push(wr FrameWriteRequest) {
|
||||
var n *priorityNodeRFC7540
|
||||
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 *priorityWriteSchedulerRFC7540) Pop() (wr FrameWriteRequest, ok bool) {
|
||||
ws.root.walkReadyInOrder(false, &ws.tmp, func(n *priorityNodeRFC7540, 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 *priorityWriteSchedulerRFC7540) addClosedOrIdleNode(list *[]*priorityNodeRFC7540, maxSize int, n *priorityNodeRFC7540) {
|
||||
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 *priorityWriteSchedulerRFC7540) removeNode(n *priorityNodeRFC7540) {
|
||||
for n.kids != nil {
|
||||
n.kids.setParent(n.parent)
|
||||
}
|
||||
n.setParent(nil)
|
||||
delete(ws.nodes, n.id)
|
||||
}
|
||||
633
src/net/http/internal/http2/writesched_priority_rfc7540_test.go
Normal file
633
src/net/http/internal/http2/writesched_priority_rfc7540_test.go
Normal file
@@ -0,0 +1,633 @@
|
||||
// Copyright 2016 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func defaultPriorityWriteScheduler() *priorityWriteSchedulerRFC7540 {
|
||||
return NewPriorityWriteScheduler(nil).(*priorityWriteSchedulerRFC7540)
|
||||
}
|
||||
|
||||
func checkPriorityWellFormed(ws *priorityWriteSchedulerRFC7540) error {
|
||||
for id, n := range ws.nodes {
|
||||
if id != n.id {
|
||||
return fmt.Errorf("bad ws.nodes: ws.nodes[%d] = %d", id, n.id)
|
||||
}
|
||||
if n.parent == nil {
|
||||
if n.next != nil || n.prev != nil {
|
||||
return fmt.Errorf("bad node %d: nil parent but prev/next not nil", id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for k := n.parent.kids; k != nil; k = k.next {
|
||||
if k.id == id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("bad node %d: not found in parent %d kids list", id, n.parent.id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fmtTree(ws *priorityWriteSchedulerRFC7540, fmtNode func(*priorityNodeRFC7540) string) string {
|
||||
var ids []int
|
||||
for _, n := range ws.nodes {
|
||||
ids = append(ids, int(n.id))
|
||||
}
|
||||
sort.Ints(ids)
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, id := range ids {
|
||||
if buf.Len() != 0 {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
if id == 0 {
|
||||
buf.WriteString(fmtNode(&ws.root))
|
||||
} else {
|
||||
buf.WriteString(fmtNode(ws.nodes[uint32(id)]))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func fmtNodeParentSkipRoot(n *priorityNodeRFC7540) string {
|
||||
switch {
|
||||
case n.id == 0:
|
||||
return ""
|
||||
case n.parent == nil:
|
||||
return fmt.Sprintf("%d{parent:nil}", n.id)
|
||||
default:
|
||||
return fmt.Sprintf("%d{parent:%d}", n.id, n.parent.id)
|
||||
}
|
||||
}
|
||||
|
||||
func fmtNodeWeightParentSkipRoot(n *priorityNodeRFC7540) string {
|
||||
switch {
|
||||
case n.id == 0:
|
||||
return ""
|
||||
case n.parent == nil:
|
||||
return fmt.Sprintf("%d{weight:%d,parent:nil}", n.id, n.weight)
|
||||
default:
|
||||
return fmt.Sprintf("%d{weight:%d,parent:%d}", n.id, n.weight, n.parent.id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityTwoStreams(t *testing.T) {
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{})
|
||||
|
||||
want := "1{weight:15,parent:0} 2{weight:15,parent:0}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After open\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
|
||||
// Move 1's parent to 2.
|
||||
ws.AdjustStream(1, PriorityParam{
|
||||
StreamDep: 2,
|
||||
Weight: 32,
|
||||
Exclusive: false,
|
||||
})
|
||||
want = "1{weight:32,parent:2} 2{weight:15,parent:0}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityAdjustExclusiveZero(t *testing.T) {
|
||||
// 1, 2, and 3 are all children of the 0 stream.
|
||||
// Exclusive reprioritization to any of the streams should bring
|
||||
// the rest of the streams under the reprioritized stream.
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{})
|
||||
ws.OpenStream(3, OpenStreamOptions{})
|
||||
|
||||
want := "1{weight:15,parent:0} 2{weight:15,parent:0} 3{weight:15,parent:0}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After open\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
|
||||
ws.AdjustStream(2, PriorityParam{
|
||||
StreamDep: 0,
|
||||
Weight: 20,
|
||||
Exclusive: true,
|
||||
})
|
||||
want = "1{weight:15,parent:2} 2{weight:20,parent:0} 3{weight:15,parent:2}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityAdjustOwnParent(t *testing.T) {
|
||||
// Assigning a node as its own parent should have no effect.
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{})
|
||||
ws.AdjustStream(2, PriorityParam{
|
||||
StreamDep: 2,
|
||||
Weight: 20,
|
||||
Exclusive: true,
|
||||
})
|
||||
want := "1{weight:15,parent:0} 2{weight:15,parent:0}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityClosedStreams(t *testing.T) {
|
||||
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{MaxClosedNodesInTree: 2}).(*priorityWriteSchedulerRFC7540)
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
|
||||
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
|
||||
|
||||
// Close the first three streams. We lose 1, but keep 2 and 3.
|
||||
ws.CloseStream(1)
|
||||
ws.CloseStream(2)
|
||||
ws.CloseStream(3)
|
||||
|
||||
want := "2{weight:15,parent:0} 3{weight:15,parent:2} 4{weight:15,parent:3}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After close\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Adding a stream as an exclusive child of 1 gives it default
|
||||
// priorities, since 1 is gone.
|
||||
ws.OpenStream(5, OpenStreamOptions{})
|
||||
ws.AdjustStream(5, PriorityParam{StreamDep: 1, Weight: 15, Exclusive: true})
|
||||
|
||||
// Adding a stream as an exclusive child of 2 should work, since 2 is not gone.
|
||||
ws.OpenStream(6, OpenStreamOptions{})
|
||||
ws.AdjustStream(6, PriorityParam{StreamDep: 2, Weight: 15, Exclusive: true})
|
||||
|
||||
want = "2{weight:15,parent:0} 3{weight:15,parent:6} 4{weight:15,parent:3} 5{weight:15,parent:0} 6{weight:15,parent:2}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After add streams\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityClosedStreamsDisabled(t *testing.T) {
|
||||
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{}).(*priorityWriteSchedulerRFC7540)
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
|
||||
|
||||
// Close the first two streams. We keep only 3.
|
||||
ws.CloseStream(1)
|
||||
ws.CloseStream(2)
|
||||
|
||||
want := "3{weight:15,parent:0}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After close\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityIdleStreams(t *testing.T) {
|
||||
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{MaxIdleNodesInTree: 2}).(*priorityWriteSchedulerRFC7540)
|
||||
ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 15}) // idle
|
||||
ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 15}) // idle
|
||||
ws.AdjustStream(3, PriorityParam{StreamDep: 2, Weight: 20}) // idle
|
||||
ws.OpenStream(4, OpenStreamOptions{})
|
||||
ws.OpenStream(5, OpenStreamOptions{})
|
||||
ws.OpenStream(6, OpenStreamOptions{})
|
||||
ws.AdjustStream(4, PriorityParam{StreamDep: 1, Weight: 15})
|
||||
ws.AdjustStream(5, PriorityParam{StreamDep: 2, Weight: 15})
|
||||
ws.AdjustStream(6, PriorityParam{StreamDep: 3, Weight: 15})
|
||||
|
||||
want := "2{weight:15,parent:0} 3{weight:20,parent:2} 4{weight:15,parent:0} 5{weight:15,parent:2} 6{weight:15,parent:3}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After open\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityIdleStreamsDisabled(t *testing.T) {
|
||||
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{}).(*priorityWriteSchedulerRFC7540)
|
||||
ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 15}) // idle
|
||||
ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 15}) // idle
|
||||
ws.AdjustStream(3, PriorityParam{StreamDep: 2, Weight: 20}) // idle
|
||||
ws.OpenStream(4, OpenStreamOptions{})
|
||||
|
||||
want := "4{weight:15,parent:0}"
|
||||
if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
|
||||
t.Errorf("After open\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioritySection531NonExclusive(t *testing.T) {
|
||||
// Example from RFC 7540 Section 5.3.1.
|
||||
// A,B,C,D = 1,2,3,4
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(4, OpenStreamOptions{})
|
||||
ws.AdjustStream(4, PriorityParam{
|
||||
StreamDep: 1,
|
||||
Weight: 15,
|
||||
Exclusive: false,
|
||||
})
|
||||
want := "1{parent:0} 2{parent:1} 3{parent:1} 4{parent:1}"
|
||||
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
|
||||
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioritySection531Exclusive(t *testing.T) {
|
||||
// Example from RFC 7540 Section 5.3.1.
|
||||
// A,B,C,D = 1,2,3,4
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(4, OpenStreamOptions{})
|
||||
ws.AdjustStream(4, PriorityParam{
|
||||
StreamDep: 1,
|
||||
Weight: 15,
|
||||
Exclusive: true,
|
||||
})
|
||||
want := "1{parent:0} 2{parent:4} 3{parent:4} 4{parent:1}"
|
||||
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
|
||||
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeSection533Tree() *priorityWriteSchedulerRFC7540 {
|
||||
// Initial tree from RFC 7540 Section 5.3.3.
|
||||
// A,B,C,D,E,F = 1,2,3,4,5,6
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
|
||||
ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
|
||||
ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
|
||||
return ws
|
||||
}
|
||||
|
||||
func TestPrioritySection533NonExclusive(t *testing.T) {
|
||||
// Example from RFC 7540 Section 5.3.3.
|
||||
// A,B,C,D,E,F = 1,2,3,4,5,6
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
|
||||
ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
|
||||
ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
|
||||
ws.AdjustStream(1, PriorityParam{
|
||||
StreamDep: 4,
|
||||
Weight: 15,
|
||||
Exclusive: false,
|
||||
})
|
||||
want := "1{parent:4} 2{parent:1} 3{parent:1} 4{parent:0} 5{parent:3} 6{parent:4}"
|
||||
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
|
||||
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioritySection533Exclusive(t *testing.T) {
|
||||
// Example from RFC 7540 Section 5.3.3.
|
||||
// A,B,C,D,E,F = 1,2,3,4,5,6
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
|
||||
ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
|
||||
ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
|
||||
ws.AdjustStream(1, PriorityParam{
|
||||
StreamDep: 4,
|
||||
Weight: 15,
|
||||
Exclusive: true,
|
||||
})
|
||||
want := "1{parent:4} 2{parent:1} 3{parent:1} 4{parent:0} 5{parent:3} 6{parent:1}"
|
||||
if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
|
||||
t.Errorf("After adjust\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
if err := checkPriorityWellFormed(ws); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkPopAll(ws WriteScheduler, order []uint32) error {
|
||||
for k, id := range order {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
return fmt.Errorf("Pop[%d]: got ok=false, want %d (order=%v)", k, id, order)
|
||||
}
|
||||
if got := wr.StreamID(); got != id {
|
||||
return fmt.Errorf("Pop[%d]: got %v, want %d (order=%v)", k, got, id, order)
|
||||
}
|
||||
}
|
||||
wr, ok := ws.Pop()
|
||||
if ok {
|
||||
return fmt.Errorf("Pop[%d]: got %v, want ok=false (order=%v)", len(order), wr.StreamID(), order)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPriorityPopFrom533Tree(t *testing.T) {
|
||||
ws := makeSection533Tree()
|
||||
|
||||
ws.Push(makeWriteHeadersRequest(3 /*C*/))
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
ws.Push(makeWriteHeadersRequest(5 /*E*/))
|
||||
ws.Push(makeWriteHeadersRequest(1 /*A*/))
|
||||
t.Log("tree:", fmtTree(ws, fmtNodeParentSkipRoot))
|
||||
|
||||
if err := checkPopAll(ws, []uint32{0 /*NonStream*/, 1, 3, 5}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// #49741 RST_STREAM and Control frames should have more priority than data
|
||||
// frames to avoid blocking streams caused by clients not able to drain the
|
||||
// queue.
|
||||
func TestPriorityRSTFrames(t *testing.T) {
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
|
||||
sc := &serverConn{maxFrameSize: 16}
|
||||
st1 := &stream{id: 1, sc: sc}
|
||||
|
||||
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 16), false}, st1, nil})
|
||||
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 16), false}, st1, nil})
|
||||
ws.Push(makeWriteRSTStream(1))
|
||||
// No flow-control bytes available.
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
t.Fatalf("Pop should work for control frames and not be limited by flow control")
|
||||
}
|
||||
if _, ok := wr.write.(StreamError); !ok {
|
||||
t.Fatal("expected RST stream frames first", wr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityPopFromLinearTree(t *testing.T) {
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
|
||||
ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
|
||||
|
||||
ws.Push(makeWriteHeadersRequest(3))
|
||||
ws.Push(makeWriteHeadersRequest(4))
|
||||
ws.Push(makeWriteHeadersRequest(1))
|
||||
ws.Push(makeWriteHeadersRequest(2))
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
t.Log("tree:", fmtTree(ws, fmtNodeParentSkipRoot))
|
||||
|
||||
if err := checkPopAll(ws, []uint32{0, 0 /*NonStreams*/, 1, 2, 3, 4}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityFlowControl(t *testing.T) {
|
||||
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{ThrottleOutOfOrderWrites: false})
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
|
||||
sc := &serverConn{maxFrameSize: 16}
|
||||
st1 := &stream{id: 1, sc: sc}
|
||||
st2 := &stream{id: 2, sc: sc}
|
||||
|
||||
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 16), false}, st1, nil})
|
||||
ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 16), false}, st2, nil})
|
||||
ws.AdjustStream(2, PriorityParam{StreamDep: 1})
|
||||
|
||||
// No flow-control bytes available.
|
||||
if wr, ok := ws.Pop(); ok {
|
||||
t.Fatalf("Pop(limited by flow control)=%v,true, want false", wr)
|
||||
}
|
||||
|
||||
// Add enough flow-control bytes to write st2 in two Pop calls.
|
||||
// Should write data from st2 even though it's lower priority than st1.
|
||||
for i := 1; i <= 2; i++ {
|
||||
st2.flow.add(8)
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
t.Fatalf("Pop(%d)=false, want true", i)
|
||||
}
|
||||
if got, want := wr.DataSize(), 8; got != want {
|
||||
t.Fatalf("Pop(%d)=%d bytes, want %d bytes", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityThrottleOutOfOrderWrites(t *testing.T) {
|
||||
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{ThrottleOutOfOrderWrites: true})
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
|
||||
|
||||
sc := &serverConn{maxFrameSize: 4096}
|
||||
st1 := &stream{id: 1, sc: sc}
|
||||
st2 := &stream{id: 2, sc: sc}
|
||||
st1.flow.add(4096)
|
||||
st2.flow.add(4096)
|
||||
ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 4096), false}, st2, nil})
|
||||
ws.AdjustStream(2, PriorityParam{StreamDep: 1})
|
||||
|
||||
// We have enough flow-control bytes to write st2 in a single Pop call.
|
||||
// However, due to out-of-order write throttling, the first call should
|
||||
// only write 1KB.
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
t.Fatalf("Pop(st2.first)=false, want true")
|
||||
}
|
||||
if got, want := wr.StreamID(), uint32(2); got != want {
|
||||
t.Fatalf("Pop(st2.first)=stream %d, want stream %d", got, want)
|
||||
}
|
||||
if got, want := wr.DataSize(), 1024; got != want {
|
||||
t.Fatalf("Pop(st2.first)=%d bytes, want %d bytes", got, want)
|
||||
}
|
||||
|
||||
// Now add data on st1. This should take precedence.
|
||||
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 4096), false}, st1, nil})
|
||||
wr, ok = ws.Pop()
|
||||
if !ok {
|
||||
t.Fatalf("Pop(st1)=false, want true")
|
||||
}
|
||||
if got, want := wr.StreamID(), uint32(1); got != want {
|
||||
t.Fatalf("Pop(st1)=stream %d, want stream %d", got, want)
|
||||
}
|
||||
if got, want := wr.DataSize(), 4096; got != want {
|
||||
t.Fatalf("Pop(st1)=%d bytes, want %d bytes", got, want)
|
||||
}
|
||||
|
||||
// Should go back to writing 1KB from st2.
|
||||
wr, ok = ws.Pop()
|
||||
if !ok {
|
||||
t.Fatalf("Pop(st2.last)=false, want true")
|
||||
}
|
||||
if got, want := wr.StreamID(), uint32(2); got != want {
|
||||
t.Fatalf("Pop(st2.last)=stream %d, want stream %d", got, want)
|
||||
}
|
||||
if got, want := wr.DataSize(), 1024; got != want {
|
||||
t.Fatalf("Pop(st2.last)=%d bytes, want %d bytes", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityWeights(t *testing.T) {
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{})
|
||||
|
||||
sc := &serverConn{maxFrameSize: 8}
|
||||
st1 := &stream{id: 1, sc: sc}
|
||||
st2 := &stream{id: 2, sc: sc}
|
||||
st1.flow.add(40)
|
||||
st2.flow.add(40)
|
||||
|
||||
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 40), false}, st1, nil})
|
||||
ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 40), false}, st2, nil})
|
||||
ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 34})
|
||||
ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 9})
|
||||
|
||||
// st1 gets 3.5x the bandwidth of st2 (3.5 = (34+1)/(9+1)).
|
||||
// The maximum frame size is 8 bytes. The write sequence should be:
|
||||
// st1, total bytes so far is (st1=8, st=0)
|
||||
// st2, total bytes so far is (st1=8, st=8)
|
||||
// st1, total bytes so far is (st1=16, st=8)
|
||||
// st1, total bytes so far is (st1=24, st=8) // 3x bandwidth
|
||||
// st1, total bytes so far is (st1=32, st=8) // 4x bandwidth
|
||||
// st2, total bytes so far is (st1=32, st=16) // 2x bandwidth
|
||||
// st1, total bytes so far is (st1=40, st=16)
|
||||
// st2, total bytes so far is (st1=40, st=24)
|
||||
// st2, total bytes so far is (st1=40, st=32)
|
||||
// st2, total bytes so far is (st1=40, st=40)
|
||||
if err := checkPopAll(ws, []uint32{1, 2, 1, 1, 1, 2, 1, 2, 2, 2}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityWeightsMinMax(t *testing.T) {
|
||||
ws := defaultPriorityWriteScheduler()
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.OpenStream(2, OpenStreamOptions{})
|
||||
|
||||
sc := &serverConn{maxFrameSize: 8}
|
||||
st1 := &stream{id: 1, sc: sc}
|
||||
st2 := &stream{id: 2, sc: sc}
|
||||
st1.flow.add(40)
|
||||
st2.flow.add(40)
|
||||
|
||||
// st2 gets 256x the bandwidth of st1 (256 = (255+1)/(0+1)).
|
||||
// The maximum frame size is 8 bytes. The write sequence should be:
|
||||
// st2, total bytes so far is (st1=0, st=8)
|
||||
// st1, total bytes so far is (st1=8, st=8)
|
||||
// st2, total bytes so far is (st1=8, st=16)
|
||||
// st2, total bytes so far is (st1=8, st=24)
|
||||
// st2, total bytes so far is (st1=8, st=32)
|
||||
// st2, total bytes so far is (st1=8, st=40) // 5x bandwidth
|
||||
// st1, total bytes so far is (st1=16, st=40)
|
||||
// st1, total bytes so far is (st1=24, st=40)
|
||||
// st1, total bytes so far is (st1=32, st=40)
|
||||
// st1, total bytes so far is (st1=40, st=40)
|
||||
ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 40), false}, st1, nil})
|
||||
ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 40), false}, st2, nil})
|
||||
ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 0})
|
||||
ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 255})
|
||||
|
||||
if err := checkPopAll(ws, []uint32{2, 1, 2, 2, 2, 2, 1, 1, 1, 1}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityRstStreamOnNonOpenStreams(t *testing.T) {
|
||||
ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{
|
||||
MaxClosedNodesInTree: 0,
|
||||
MaxIdleNodesInTree: 0,
|
||||
})
|
||||
ws.OpenStream(1, OpenStreamOptions{})
|
||||
ws.CloseStream(1)
|
||||
ws.Push(FrameWriteRequest{write: streamError(1, ErrCodeProtocol)})
|
||||
ws.Push(FrameWriteRequest{write: streamError(2, ErrCodeProtocol)})
|
||||
|
||||
if err := checkPopAll(ws, []uint32{1, 2}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// https://go.dev/issue/66514
|
||||
func TestPriorityIssue66514(t *testing.T) {
|
||||
addDep := func(ws *priorityWriteSchedulerRFC7540, child uint32, parent uint32) {
|
||||
ws.AdjustStream(child, PriorityParam{
|
||||
StreamDep: parent,
|
||||
Exclusive: false,
|
||||
Weight: 16,
|
||||
})
|
||||
}
|
||||
|
||||
validateDepTree := func(ws *priorityWriteSchedulerRFC7540, id uint32, t *testing.T) {
|
||||
for n := ws.nodes[id]; n != nil; n = n.parent {
|
||||
if n.parent == nil {
|
||||
if n.id != uint32(0) {
|
||||
t.Errorf("detected nodes not parented to 0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ws := NewPriorityWriteScheduler(nil).(*priorityWriteSchedulerRFC7540)
|
||||
|
||||
// Root entry
|
||||
addDep(ws, uint32(1), uint32(0))
|
||||
addDep(ws, uint32(3), uint32(1))
|
||||
addDep(ws, uint32(5), uint32(1))
|
||||
|
||||
for id := uint32(7); id < uint32(100); id += uint32(4) {
|
||||
addDep(ws, id, id-uint32(4))
|
||||
addDep(ws, id+uint32(2), id-uint32(4))
|
||||
validateDepTree(ws, id, t)
|
||||
}
|
||||
}
|
||||
226
src/net/http/internal/http2/writesched_priority_rfc9218.go
Normal file
226
src/net/http/internal/http2/writesched_priority_rfc9218.go
Normal file
@@ -0,0 +1,226 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type streamMetadata struct {
|
||||
location *writeQueue
|
||||
priority PriorityParam
|
||||
}
|
||||
|
||||
type priorityWriteSchedulerRFC9218 struct {
|
||||
// control contains control frames (SETTINGS, PING, etc.).
|
||||
control writeQueue
|
||||
|
||||
// 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]*writeQueue
|
||||
|
||||
// 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]streamMetadata
|
||||
|
||||
// queuePool are empty queues for reuse.
|
||||
queuePool writeQueuePool
|
||||
|
||||
// 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 PriorityParam
|
||||
}
|
||||
}
|
||||
|
||||
func newPriorityWriteSchedulerRFC9218() WriteScheduler {
|
||||
ws := &priorityWriteSchedulerRFC9218{
|
||||
streams: make(map[uint32]streamMetadata),
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC9218) OpenStream(streamID uint32, opt OpenStreamOptions) {
|
||||
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] = streamMetadata{
|
||||
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 *priorityWriteSchedulerRFC9218) 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 *priorityWriteSchedulerRFC9218) AdjustStream(streamID uint32, priority PriorityParam) {
|
||||
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] = streamMetadata{
|
||||
location: q,
|
||||
priority: priority,
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *priorityWriteSchedulerRFC9218) Push(wr FrameWriteRequest) {
|
||||
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 *priorityWriteSchedulerRFC9218) Pop() (FrameWriteRequest, 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 FrameWriteRequest{}, false
|
||||
}
|
||||
391
src/net/http/internal/http2/writesched_priority_rfc9218_test.go
Normal file
391
src/net/http/internal/http2/writesched_priority_rfc9218_test.go
Normal file
@@ -0,0 +1,391 @@
|
||||
// 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.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrioritySchedulerUrgency(t *testing.T) {
|
||||
const maxFrameSize = 16
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
ws := newPriorityWriteSchedulerRFC9218()
|
||||
streams := make([]*stream, 5)
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
streams[i] = &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
streams[i].flow.add(1 << 20) // arbitrary large value
|
||||
ws.OpenStream(streamID, OpenStreamOptions{
|
||||
priority: PriorityParam{
|
||||
urgency: 7,
|
||||
incremental: 0,
|
||||
},
|
||||
})
|
||||
wr := FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: make([]byte, maxFrameSize*(i+1)),
|
||||
endStream: false,
|
||||
},
|
||||
stream: streams[i],
|
||||
}
|
||||
ws.Push(wr)
|
||||
}
|
||||
// Raise the urgency of all even-numbered streams.
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
if streamID%2 == 1 {
|
||||
continue
|
||||
}
|
||||
ws.AdjustStream(streamID, PriorityParam{
|
||||
urgency: 0,
|
||||
incremental: 0,
|
||||
})
|
||||
}
|
||||
const controlFrames = 2
|
||||
for range controlFrames {
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
}
|
||||
|
||||
// We should get the control frames first.
|
||||
for range controlFrames {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok || wr.StreamID() != 0 {
|
||||
t.Fatalf("wr.Pop() = stream %v, %v; want 0, true", wr.StreamID(), ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Each stream should write maxFrameSize bytes until it runs out of data.
|
||||
// Higher-urgency even-numbered streams should come first.
|
||||
want := []uint32{2, 2, 4, 4, 4, 4, 1, 3, 3, 3, 5, 5, 5, 5, 5}
|
||||
var got []uint32
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
t.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
got = append(got, wr.StreamID())
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("popped streams %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioritySchedulerIncremental(t *testing.T) {
|
||||
const maxFrameSize = 16
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
ws := newPriorityWriteSchedulerRFC9218()
|
||||
streams := make([]*stream, 5)
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
streams[i] = &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
streams[i].flow.add(1 << 20) // arbitrary large value
|
||||
ws.OpenStream(streamID, OpenStreamOptions{
|
||||
priority: PriorityParam{
|
||||
urgency: 7,
|
||||
incremental: 0,
|
||||
},
|
||||
})
|
||||
wr := FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: make([]byte, maxFrameSize*(i+1)),
|
||||
endStream: false,
|
||||
},
|
||||
stream: streams[i],
|
||||
}
|
||||
ws.Push(wr)
|
||||
}
|
||||
// Make even-numbered streams incremental.
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
if streamID%2 == 1 {
|
||||
continue
|
||||
}
|
||||
ws.AdjustStream(streamID, PriorityParam{
|
||||
urgency: 7,
|
||||
incremental: 1,
|
||||
})
|
||||
}
|
||||
const controlFrames = 2
|
||||
for range controlFrames {
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
}
|
||||
|
||||
// We should get the control frames first.
|
||||
for range controlFrames {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok || wr.StreamID() != 0 {
|
||||
t.Fatalf("wr.Pop() = stream %v, %v; want 0, true", wr.StreamID(), ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Each stream should write maxFrameSize bytes until it runs out of data.
|
||||
// We should:
|
||||
// - Round-robin between even and odd-numbered streams as they have
|
||||
// different i but the same u.
|
||||
// - Amongst even-numbered streams, round-robin writes as they are
|
||||
// incremental.
|
||||
// - Among odd-numbered streams, do not round-robin as they are
|
||||
// non-incremental.
|
||||
want := []uint32{2, 1, 4, 3, 2, 3, 4, 3, 4, 5, 4, 5, 5, 5, 5}
|
||||
var got []uint32
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
t.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
got = append(got, wr.StreamID())
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("popped streams %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioritySchedulerUrgencyAndIncremental(t *testing.T) {
|
||||
const maxFrameSize = 16
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
ws := newPriorityWriteSchedulerRFC9218()
|
||||
streams := make([]*stream, 6)
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
streams[i] = &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
streams[i].flow.add(1 << 20) // arbitrary large value
|
||||
ws.OpenStream(streamID, OpenStreamOptions{
|
||||
priority: PriorityParam{
|
||||
urgency: 7,
|
||||
incremental: 0,
|
||||
},
|
||||
})
|
||||
wr := FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: make([]byte, maxFrameSize*(i+1)),
|
||||
endStream: false,
|
||||
},
|
||||
stream: streams[i],
|
||||
}
|
||||
ws.Push(wr)
|
||||
}
|
||||
// Make even-numbered streams incremental and of higher urgency.
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
if streamID%2 == 1 {
|
||||
continue
|
||||
}
|
||||
ws.AdjustStream(streamID, PriorityParam{
|
||||
urgency: 0,
|
||||
incremental: 1,
|
||||
})
|
||||
}
|
||||
// Close stream 1 and 4
|
||||
ws.CloseStream(1)
|
||||
ws.CloseStream(4)
|
||||
const controlFrames = 2
|
||||
for range controlFrames {
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
}
|
||||
|
||||
// We should get the control frames first.
|
||||
for range controlFrames {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok || wr.StreamID() != 0 {
|
||||
t.Fatalf("wr.Pop() = stream %v, %v; want 0, true", wr.StreamID(), ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Each stream should write maxFrameSize bytes until it runs out of data.
|
||||
// We should:
|
||||
// - Get even-numbered streams first that are written in a round-robin
|
||||
// manner as they have higher urgency and are incremental.
|
||||
// - Get odd-numbered streams after that are written one-by-one to
|
||||
// completion as they are of lower urgency and are not incremental.
|
||||
// - Skip stream 1 and 4 that have been closed.
|
||||
want := []uint32{2, 6, 2, 6, 6, 6, 6, 6, 3, 3, 3, 5, 5, 5, 5, 5}
|
||||
var got []uint32
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
t.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
got = append(got, wr.StreamID())
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("popped streams %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioritySchedulerIdempotentUpdate(t *testing.T) {
|
||||
const maxFrameSize = 16
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
ws := newPriorityWriteSchedulerRFC9218()
|
||||
streams := make([]*stream, 6)
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
streams[i] = &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
streams[i].flow.add(1 << 20) // arbitrary large value
|
||||
ws.OpenStream(streamID, OpenStreamOptions{
|
||||
priority: PriorityParam{
|
||||
urgency: 7,
|
||||
incremental: 0,
|
||||
},
|
||||
})
|
||||
wr := FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: make([]byte, maxFrameSize*(i+1)),
|
||||
endStream: false,
|
||||
},
|
||||
stream: streams[i],
|
||||
}
|
||||
ws.Push(wr)
|
||||
}
|
||||
// Make even-numbered streams incremental and of higher urgency.
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
if streamID%2 == 1 {
|
||||
continue
|
||||
}
|
||||
ws.AdjustStream(streamID, PriorityParam{
|
||||
urgency: 0,
|
||||
incremental: 1,
|
||||
})
|
||||
}
|
||||
ws.CloseStream(1)
|
||||
// Repeat the same priority update to ensure idempotency.
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
if streamID%2 == 1 {
|
||||
continue
|
||||
}
|
||||
ws.AdjustStream(streamID, PriorityParam{
|
||||
urgency: 0,
|
||||
incremental: 1,
|
||||
})
|
||||
}
|
||||
ws.CloseStream(2)
|
||||
const controlFrames = 2
|
||||
for range controlFrames {
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
}
|
||||
|
||||
// We should get the control frames first.
|
||||
for range controlFrames {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok || wr.StreamID() != 0 {
|
||||
t.Fatalf("wr.Pop() = stream %v, %v; want 0, true", wr.StreamID(), ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Each stream should write maxFrameSize bytes until it runs out of data.
|
||||
// We should:
|
||||
// - Get even-numbered streams first that are written in a round-robin
|
||||
// manner as they have higher urgency and are incremental.
|
||||
// - Get odd-numbered streams after that are written one-by-one to
|
||||
// completion as they are of lower urgency and are not incremental.
|
||||
// - Skip stream 1 and 4 that have been closed.
|
||||
want := []uint32{4, 6, 4, 6, 4, 6, 4, 6, 6, 6, 3, 3, 3, 5, 5, 5, 5, 5}
|
||||
var got []uint32
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
t.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
got = append(got, wr.StreamID())
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("popped streams %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrioritySchedulerBuffersPriorityUpdate(t *testing.T) {
|
||||
const maxFrameSize = 16
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
ws := newPriorityWriteSchedulerRFC9218()
|
||||
|
||||
// Priorities are adjusted for streams that are not open yet.
|
||||
ws.AdjustStream(1, PriorityParam{urgency: 0})
|
||||
ws.AdjustStream(5, PriorityParam{urgency: 0})
|
||||
for _, streamID := range []uint32{1, 3, 5} {
|
||||
stream := &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
stream.flow.add(1 << 20) // arbitrary large value
|
||||
ws.OpenStream(streamID, OpenStreamOptions{
|
||||
priority: PriorityParam{
|
||||
urgency: 7,
|
||||
incremental: 1,
|
||||
},
|
||||
})
|
||||
wr := FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: make([]byte, maxFrameSize*(3)),
|
||||
endStream: false,
|
||||
},
|
||||
stream: stream,
|
||||
}
|
||||
ws.Push(wr)
|
||||
}
|
||||
|
||||
const controlFrames = 2
|
||||
for range controlFrames {
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
}
|
||||
|
||||
// We should get the control frames first.
|
||||
for range controlFrames {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok || wr.StreamID() != 0 {
|
||||
t.Fatalf("wr.Pop() = stream %v, %v; want 0, true", wr.StreamID(), ok)
|
||||
}
|
||||
}
|
||||
|
||||
// The most recent priority adjustment is buffered and applied. Older ones
|
||||
// are ignored.
|
||||
want := []uint32{5, 5, 5, 1, 3, 1, 3, 1, 3}
|
||||
var got []uint32
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
t.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
got = append(got, wr.StreamID())
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("popped streams %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
79
src/net/http/internal/http2/writesched_random.go
Normal file
79
src/net/http/internal/http2/writesched_random.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "math"
|
||||
|
||||
// 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 NewRandomWriteScheduler() WriteScheduler {
|
||||
return &randomWriteScheduler{sq: make(map[uint32]*writeQueue)}
|
||||
}
|
||||
|
||||
type randomWriteScheduler struct {
|
||||
// zero are frames not associated with a specific stream.
|
||||
zero writeQueue
|
||||
|
||||
// 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]*writeQueue
|
||||
|
||||
// pool of empty queues for reuse.
|
||||
queuePool writeQueuePool
|
||||
}
|
||||
|
||||
func (ws *randomWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {
|
||||
// no-op: idle streams are not tracked
|
||||
}
|
||||
|
||||
func (ws *randomWriteScheduler) CloseStream(streamID uint32) {
|
||||
q, ok := ws.sq[streamID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(ws.sq, streamID)
|
||||
ws.queuePool.put(q)
|
||||
}
|
||||
|
||||
func (ws *randomWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {
|
||||
// no-op: priorities are ignored
|
||||
}
|
||||
|
||||
func (ws *randomWriteScheduler) Push(wr FrameWriteRequest) {
|
||||
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 *randomWriteScheduler) Pop() (FrameWriteRequest, 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 FrameWriteRequest{}, false
|
||||
}
|
||||
66
src/net/http/internal/http2/writesched_random_test.go
Normal file
66
src/net/http/internal/http2/writesched_random_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2016 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRandomScheduler(t *testing.T) {
|
||||
ws := NewRandomWriteScheduler()
|
||||
ws.Push(makeWriteHeadersRequest(3))
|
||||
ws.Push(makeWriteHeadersRequest(4))
|
||||
ws.Push(makeWriteHeadersRequest(1))
|
||||
ws.Push(makeWriteHeadersRequest(2))
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
ws.Push(makeWriteRSTStream(1))
|
||||
|
||||
// Pop all frames. Should get the non-stream and RST stream requests first,
|
||||
// followed by the stream requests in any order.
|
||||
var order []FrameWriteRequest
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
order = append(order, wr)
|
||||
}
|
||||
t.Logf("got frames: %v", order)
|
||||
if len(order) != 7 {
|
||||
t.Fatalf("got %d frames, expected 6", len(order))
|
||||
}
|
||||
if order[0].StreamID() != 0 || order[1].StreamID() != 0 {
|
||||
t.Fatal("expected non-stream frames first", order[0], order[1])
|
||||
}
|
||||
if _, ok := order[2].write.(StreamError); !ok {
|
||||
t.Fatal("expected RST stream frames first", order[2])
|
||||
}
|
||||
got := make(map[uint32]bool)
|
||||
for _, wr := range order[2:] {
|
||||
got[wr.StreamID()] = true
|
||||
}
|
||||
for id := uint32(1); id <= 4; id++ {
|
||||
if !got[id] {
|
||||
t.Errorf("frame not found for stream %d", id)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we clean up maps for empty queues in all cases (golang.org/issue/33812)
|
||||
const arbitraryStreamID = 123
|
||||
ws.Push(makeHandlerPanicRST(arbitraryStreamID))
|
||||
rws := ws.(*randomWriteScheduler)
|
||||
if got, want := len(rws.sq), 1; got != want {
|
||||
t.Fatalf("len of 123 stream = %v; want %v", got, want)
|
||||
}
|
||||
_, ok := ws.Pop()
|
||||
if !ok {
|
||||
t.Fatal("expected to be able to Pop")
|
||||
}
|
||||
if got, want := len(rws.sq), 0; got != want {
|
||||
t.Fatalf("len of 123 stream = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
}
|
||||
121
src/net/http/internal/http2/writesched_roundrobin.go
Normal file
121
src/net/http/internal/http2/writesched_roundrobin.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2023 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type roundRobinWriteScheduler struct {
|
||||
// control contains control frames (SETTINGS, PING, etc.).
|
||||
control writeQueue
|
||||
|
||||
// streams maps stream ID to a queue.
|
||||
streams map[uint32]*writeQueue
|
||||
|
||||
// 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 *writeQueue
|
||||
|
||||
// pool of empty queues for reuse.
|
||||
queuePool writeQueuePool
|
||||
}
|
||||
|
||||
// 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 newRoundRobinWriteScheduler() WriteScheduler {
|
||||
ws := &roundRobinWriteScheduler{
|
||||
streams: make(map[uint32]*writeQueue),
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
func (ws *roundRobinWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) {
|
||||
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 *roundRobinWriteScheduler) 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 *roundRobinWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {}
|
||||
|
||||
func (ws *roundRobinWriteScheduler) Push(wr FrameWriteRequest) {
|
||||
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 *roundRobinWriteScheduler) Pop() (FrameWriteRequest, bool) {
|
||||
// Control and RST_STREAM frames first.
|
||||
if !ws.control.empty() {
|
||||
return ws.control.shift(), true
|
||||
}
|
||||
if ws.head == nil {
|
||||
return FrameWriteRequest{}, 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 FrameWriteRequest{}, false
|
||||
}
|
||||
67
src/net/http/internal/http2/writesched_roundrobin_test.go
Normal file
67
src/net/http/internal/http2/writesched_roundrobin_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2023 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRoundRobinScheduler(t *testing.T) {
|
||||
const maxFrameSize = 16
|
||||
sc := &serverConn{maxFrameSize: maxFrameSize}
|
||||
ws := newRoundRobinWriteScheduler()
|
||||
streams := make([]*stream, 4)
|
||||
for i := range streams {
|
||||
streamID := uint32(i) + 1
|
||||
streams[i] = &stream{
|
||||
id: streamID,
|
||||
sc: sc,
|
||||
}
|
||||
streams[i].flow.add(1 << 20) // arbitrary large value
|
||||
ws.OpenStream(streamID, OpenStreamOptions{})
|
||||
wr := FrameWriteRequest{
|
||||
write: &writeData{
|
||||
streamID: streamID,
|
||||
p: make([]byte, maxFrameSize*(i+1)),
|
||||
endStream: false,
|
||||
},
|
||||
stream: streams[i],
|
||||
}
|
||||
ws.Push(wr)
|
||||
}
|
||||
const controlFrames = 2
|
||||
for i := 0; i < controlFrames; i++ {
|
||||
ws.Push(makeWriteNonStreamRequest())
|
||||
}
|
||||
|
||||
// We should get the control frames first.
|
||||
for i := 0; i < controlFrames; i++ {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok || wr.StreamID() != 0 {
|
||||
t.Fatalf("wr.Pop() = stream %v, %v; want 0, true", wr.StreamID(), ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Each stream should write maxFrameSize bytes until it runs out of data.
|
||||
// Stream 1 has one frame of data, 2 has two frames, etc.
|
||||
want := []uint32{1, 2, 3, 4, 2, 3, 4, 3, 4, 4}
|
||||
var got []uint32
|
||||
for {
|
||||
wr, ok := ws.Pop()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if wr.DataSize() != maxFrameSize {
|
||||
t.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize)
|
||||
}
|
||||
got = append(got, wr.StreamID())
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("popped streams %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
186
src/net/http/internal/http2/writesched_test.go
Normal file
186
src/net/http/internal/http2/writesched_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright 2016 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 ignore
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func makeWriteNonStreamRequest() FrameWriteRequest {
|
||||
return FrameWriteRequest{writeSettingsAck{}, nil, nil}
|
||||
}
|
||||
|
||||
func makeWriteHeadersRequest(streamID uint32) FrameWriteRequest {
|
||||
st := &stream{id: streamID}
|
||||
return FrameWriteRequest{&writeResHeaders{streamID: streamID, httpResCode: 200}, st, nil}
|
||||
}
|
||||
|
||||
func makeHandlerPanicRST(streamID uint32) FrameWriteRequest {
|
||||
st := &stream{id: streamID}
|
||||
return FrameWriteRequest{&handlerPanicRST{StreamID: streamID}, st, nil}
|
||||
}
|
||||
|
||||
func makeWriteRSTStream(streamID uint32) FrameWriteRequest {
|
||||
return FrameWriteRequest{write: streamError(streamID, ErrCodeInternal)}
|
||||
}
|
||||
|
||||
func checkConsume(wr FrameWriteRequest, nbytes int32, want []FrameWriteRequest) error {
|
||||
consumed, rest, n := wr.Consume(nbytes)
|
||||
var wantConsumed, wantRest FrameWriteRequest
|
||||
switch len(want) {
|
||||
case 0:
|
||||
case 1:
|
||||
wantConsumed = want[0]
|
||||
case 2:
|
||||
wantConsumed = want[0]
|
||||
wantRest = want[1]
|
||||
}
|
||||
if !reflect.DeepEqual(consumed, wantConsumed) || !reflect.DeepEqual(rest, wantRest) || n != len(want) {
|
||||
return fmt.Errorf("got %v, %v, %v\nwant %v, %v, %v", consumed, rest, n, wantConsumed, wantRest, len(want))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFrameWriteRequestNonData(t *testing.T) {
|
||||
wr := makeWriteNonStreamRequest()
|
||||
if got, want := wr.DataSize(), 0; got != want {
|
||||
t.Errorf("DataSize: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Non-DATA frames are always consumed whole.
|
||||
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
|
||||
t.Errorf("Consume:\n%v", err)
|
||||
}
|
||||
|
||||
wr = makeWriteRSTStream(123)
|
||||
if got, want := wr.DataSize(), 0; got != want {
|
||||
t.Errorf("DataSize: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// RST_STREAM frames are always consumed whole.
|
||||
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
|
||||
t.Errorf("Consume:\n%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// #49741 RST_STREAM and Control frames should have more priority than data
|
||||
// frames to avoid blocking streams caused by clients not able to drain the
|
||||
// queue.
|
||||
func TestFrameWriteRequestWithData(t *testing.T) {
|
||||
st := &stream{
|
||||
id: 1,
|
||||
sc: &serverConn{maxFrameSize: 16},
|
||||
}
|
||||
const size = 32
|
||||
wr := FrameWriteRequest{&writeData{st.id, make([]byte, size), true}, st, make(chan error)}
|
||||
if got, want := wr.DataSize(), size; got != want {
|
||||
t.Errorf("DataSize: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// No flow-control bytes available: cannot consume anything.
|
||||
if err := checkConsume(wr, math.MaxInt32, []FrameWriteRequest{}); err != nil {
|
||||
t.Errorf("Consume(limited by flow control):\n%v", err)
|
||||
}
|
||||
|
||||
wr = makeWriteNonStreamRequest()
|
||||
if got, want := wr.DataSize(), 0; got != want {
|
||||
t.Errorf("DataSize: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Non-DATA frames are always consumed whole.
|
||||
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
|
||||
t.Errorf("Consume:\n%v", err)
|
||||
}
|
||||
|
||||
wr = makeWriteRSTStream(1)
|
||||
if got, want := wr.DataSize(), 0; got != want {
|
||||
t.Errorf("DataSize: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// RST_STREAM frames are always consumed whole.
|
||||
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
|
||||
t.Errorf("Consume:\n%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFrameWriteRequestData(t *testing.T) {
|
||||
st := &stream{
|
||||
id: 1,
|
||||
sc: &serverConn{maxFrameSize: 16},
|
||||
}
|
||||
const size = 32
|
||||
wr := FrameWriteRequest{&writeData{st.id, make([]byte, size), true}, st, make(chan error)}
|
||||
if got, want := wr.DataSize(), size; got != want {
|
||||
t.Errorf("DataSize: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// No flow-control bytes available: cannot consume anything.
|
||||
if err := checkConsume(wr, math.MaxInt32, []FrameWriteRequest{}); err != nil {
|
||||
t.Errorf("Consume(limited by flow control):\n%v", err)
|
||||
}
|
||||
|
||||
// Add enough flow-control bytes to consume the entire frame,
|
||||
// but we're now restricted by st.sc.maxFrameSize.
|
||||
st.flow.add(size)
|
||||
want := []FrameWriteRequest{
|
||||
{
|
||||
write: &writeData{st.id, make([]byte, st.sc.maxFrameSize), false},
|
||||
stream: st,
|
||||
done: nil,
|
||||
},
|
||||
{
|
||||
write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize), true},
|
||||
stream: st,
|
||||
done: wr.done,
|
||||
},
|
||||
}
|
||||
if err := checkConsume(wr, math.MaxInt32, want); err != nil {
|
||||
t.Errorf("Consume(limited by maxFrameSize):\n%v", err)
|
||||
}
|
||||
rest := want[1]
|
||||
|
||||
// Consume 8 bytes from the remaining frame.
|
||||
want = []FrameWriteRequest{
|
||||
{
|
||||
write: &writeData{st.id, make([]byte, 8), false},
|
||||
stream: st,
|
||||
done: nil,
|
||||
},
|
||||
{
|
||||
write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize-8), true},
|
||||
stream: st,
|
||||
done: wr.done,
|
||||
},
|
||||
}
|
||||
if err := checkConsume(rest, 8, want); err != nil {
|
||||
t.Errorf("Consume(8):\n%v", err)
|
||||
}
|
||||
rest = want[1]
|
||||
|
||||
// Consume all remaining bytes.
|
||||
want = []FrameWriteRequest{
|
||||
{
|
||||
write: &writeData{st.id, make([]byte, size-st.sc.maxFrameSize-8), true},
|
||||
stream: st,
|
||||
done: wr.done,
|
||||
},
|
||||
}
|
||||
if err := checkConsume(rest, math.MaxInt32, want); err != nil {
|
||||
t.Errorf("Consume(remainder):\n%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFrameWriteRequest_StreamID(t *testing.T) {
|
||||
const streamID = 123
|
||||
wr := FrameWriteRequest{write: streamError(streamID, ErrCodeNo)}
|
||||
if got := wr.StreamID(); got != streamID {
|
||||
t.Errorf("FrameWriteRequest(StreamError) = %v; want %v", got, streamID)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user