Allow HTTP/2 max concurrent stream configuration

This commit is contained in:
Tom Moulard 2022-04-04 11:46:07 +02:00 committed by GitHub
parent 0d7d5a0318
commit 8c56d1a338
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 4 deletions

View file

@ -147,8 +147,11 @@ Subject alternative names.
`--entrypoints.<name>.http.tls.options`: `--entrypoints.<name>.http.tls.options`:
Default TLS options for the routers linked to the entry point. Default TLS options for the routers linked to the entry point.
`--entrypoints.<name>.http2.maxconcurrentstreams`:
Specifies the number of concurrent streams per connection that each client is allowed to initiate. (Default: ```250```)
`--entrypoints.<name>.http3`: `--entrypoints.<name>.http3`:
HTTP3 configuration. (Default: ```false```) HTTP/3 configuration. (Default: ```false```)
`--entrypoints.<name>.http3.advertisedport`: `--entrypoints.<name>.http3.advertisedport`:
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)

View file

@ -114,8 +114,11 @@ Trust only forwarded headers from selected IPs.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP`: `TRAEFIK_ENTRYPOINTS_<NAME>_HTTP`:
HTTP configuration. HTTP configuration.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP2_MAXCONCURRENTSTREAMS`:
Specifies the number of concurrent streams per connection that each client is allowed to initiate. (Default: ```250```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3`: `TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3`:
HTTP3 configuration. (Default: ```false```) HTTP/3 configuration. (Default: ```false```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3_ADVERTISEDPORT`: `TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3_ADVERTISEDPORT`:
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)

View file

@ -30,6 +30,8 @@
trustedIPs = ["foobar", "foobar"] trustedIPs = ["foobar", "foobar"]
[entryPoints.EntryPoint0.udp] [entryPoints.EntryPoint0.udp]
timeout = 42 timeout = 42
[entryPoints.EntryPoint0.http2]
maxConcurrentStreams = 42
[entryPoints.EntryPoint0.http3] [entryPoints.EntryPoint0.http3]
advertisedPort = 42 advertisedPort = 42
[entryPoints.EntryPoint0.http] [entryPoints.EntryPoint0.http]

View file

@ -32,6 +32,8 @@ entryPoints:
trustedIPs: trustedIPs:
- foobar - foobar
- foobar - foobar
http2:
maxConcurrentStreams: 42
http3: http3:
advertisedPort: 42 advertisedPort: 42
udp: udp:

View file

@ -100,6 +100,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
entryPoints: entryPoints:
name: name:
address: ":8888" # same as ":8888/tcp" address: ":8888" # same as ":8888/tcp"
http2:
maxConcurrentStreams: 42
http3: http3:
advertisedPort: 8888 advertisedPort: 8888
transport: transport:
@ -127,6 +129,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
[entryPoints] [entryPoints]
[entryPoints.name] [entryPoints.name]
address = ":8888" # same as ":8888/tcp" address = ":8888" # same as ":8888/tcp"
[entryPoints.name.http2]
maxConcurrentStreams = 42
[entryPoints.name.http3] [entryPoints.name.http3]
advertisedPort = 8888 advertisedPort = 8888
[entryPoints.name.transport] [entryPoints.name.transport]
@ -148,6 +152,7 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entryPoints.name.address=:8888 # same as :8888/tcp --entryPoints.name.address=:8888 # same as :8888/tcp
--entryPoints.name.http2.maxConcurrentStreams=42
--entryPoints.name.http3.advertisedport=8888 --entryPoints.name.http3.advertisedport=8888
--entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42 --entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42
--entryPoints.name.transport.lifeCycle.graceTimeOut=42 --entryPoints.name.transport.lifeCycle.graceTimeOut=42
@ -223,6 +228,32 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go. Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
### HTTP/2
#### `maxConcurrentStreams`
_Optional, Default=250_
`maxConcurrentStreams` specifies the number of concurrent streams per connection that each client is allowed to initiate.
The `maxConcurrentStreams` value must be greater than zero.
```yaml tab="File (YAML)"
entryPoints:
foo:
http2:
maxConcurrentStreams: 250
```
```toml tab="File (TOML)"
[entryPoints.foo]
[entryPoints.foo.http2]
maxConcurrentStreams = 250
```
```bash tab="CLI"
--entryPoints.name.http2.maxConcurrentStreams=250
```
### HTTP/3 ### HTTP/3
#### `http3` #### `http3`

View file

@ -16,7 +16,8 @@ type EntryPoint struct {
ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"` ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"`
HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
HTTP3 *HTTP3Config `description:"HTTP3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` HTTP2 *HTTP2Config `description:"HTTP/2 configuration." json:"http2,omitempty" toml:"http2,omitempty" yaml:"http2,omitempty" export:"true"`
HTTP3 *HTTP3Config `description:"HTTP/3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"` UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"`
} }
@ -50,6 +51,8 @@ func (ep *EntryPoint) SetDefaults() {
ep.ForwardedHeaders = &ForwardedHeaders{} ep.ForwardedHeaders = &ForwardedHeaders{}
ep.UDP = &UDPConfig{} ep.UDP = &UDPConfig{}
ep.UDP.SetDefaults() ep.UDP.SetDefaults()
ep.HTTP2 = &HTTP2Config{}
ep.HTTP2.SetDefaults()
} }
// HTTPConfig is the HTTP configuration of an entry point. // HTTPConfig is the HTTP configuration of an entry point.
@ -59,6 +62,16 @@ type HTTPConfig struct {
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
} }
// HTTP2Config is the HTTP2 configuration of an entry point.
type HTTP2Config struct {
MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (c *HTTP2Config) SetDefaults() {
c.MaxConcurrentStreams = 250 // https://cs.opensource.google/go/x/net/+/cd36cc07:http2/server.go;l=58
}
// HTTP3Config is the HTTP3 configuration of an entry point. // HTTP3Config is the HTTP3 configuration of an entry point.
type HTTP3Config struct { type HTTP3Config struct {
AdvertisedPort int `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"` AdvertisedPort int `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"`

View file

@ -7,6 +7,8 @@ import (
stdlog "log" stdlog "log"
"net" "net"
"net/http" "net/http"
"os"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@ -507,6 +509,10 @@ type httpServer struct {
} }
func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) { func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) {
if configuration.HTTP2.MaxConcurrentStreams < 0 {
return nil, errors.New("max concurrent streams value must be greater than or equal to zero")
}
httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter()) httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter())
next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher) next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher)
@ -524,7 +530,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
} }
if withH2c { if withH2c {
handler = h2c.NewHandler(handler, &http2.Server{}) handler = h2c.NewHandler(handler, &http2.Server{
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
})
} }
serverHTTP := &http.Server{ serverHTTP := &http.Server{
@ -535,6 +543,20 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
} }
// ConfigureServer configures HTTP/2 with the MaxConcurrentStreams option for the given server.
// Also keeping behavior the same as
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/http/server.go;l=3262
if !strings.Contains(os.Getenv("GODEBUG"), "http2server=0") {
err = http2.ConfigureServer(serverHTTP, &http2.Server{
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
NewWriteScheduler: func() http2.WriteScheduler { return http2.NewPriorityWriteScheduler(nil) },
})
if err != nil {
return nil, fmt.Errorf("configure HTTP/2 server: %w", err)
}
}
listener := newHTTPForwarder(ln) listener := newHTTPForwarder(ln)
go func() { go func() {
err := serverHTTP.Serve(listener) err := serverHTTP.Serve(listener)

View file

@ -88,6 +88,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) {
Address: "127.0.0.1:8090", Address: "127.0.0.1:8090",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
HTTP3: &static.HTTP3Config{ HTTP3: &static.HTTP3Config{
AdvertisedPort: 8080, AdvertisedPort: 8080,
}, },

View file

@ -83,6 +83,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
Address: "127.0.0.1:0", Address: "127.0.0.1:0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)
@ -166,6 +167,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
Address: ":0", Address: ":0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)
@ -202,6 +204,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
Address: ":0", Address: ":0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)