From 0b7f0b40427ec7888208919416f8cf749c9cee2a Mon Sep 17 00:00:00 2001 From: GCHQDeveloper548 <176427199+GCHQDeveloper548@users.noreply.github.com> Date: Fri, 3 Oct 2025 13:24:04 +0100 Subject: [PATCH] Implement HTTP2 HPACK table size options --- .../configuration-options.md | 2 + .../install-configuration/entrypoints.md | 4 +- docs/content/routing/entrypoints.md | 52 +++++++++++++++++++ pkg/config/static/entrypoints.go | 8 ++- pkg/config/static/static_config_test.go | 16 ++++-- pkg/server/server_entrypoint_tcp.go | 10 +++- pkg/server/server_entrypoint_tcp_test.go | 31 +++++++++++ 7 files changed, 115 insertions(+), 8 deletions(-) diff --git a/docs/content/reference/install-configuration/configuration-options.md b/docs/content/reference/install-configuration/configuration-options.md index 2a0c4a4dc..7f79fe0ff 100644 --- a/docs/content/reference/install-configuration/configuration-options.md +++ b/docs/content/reference/install-configuration/configuration-options.md @@ -100,6 +100,8 @@ THIS FILE MUST NOT BE EDITED BY HAND | entrypoints._name_.http.tls.domains[0].sans | Subject alternative names. | | | entrypoints._name_.http.tls.options | 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. | 250 | +| entrypoints._name_.http2.maxdecoderheadertablesize | Specifies the maximum size of the HTTP2 HPACK header table on the decoding (receiving from client) side. | 4096 | +| entrypoints._name_.http2.maxencoderheadertablesize | Specifies the maximum size of the HTTP2 HPACK header table on the encoding (sending to client) side. | 4096 | | entrypoints._name_.http3 | HTTP/3 configuration. | false | | entrypoints._name_.http3.advertisedport | UDP port to advertise, on which HTTP/3 is available. | 0 | | entrypoints._name_.observability.accesslogs | Enables access-logs for this entryPoint. | true | diff --git a/docs/content/reference/install-configuration/entrypoints.md b/docs/content/reference/install-configuration/entrypoints.md index e7badffbf..8915f2575 100644 --- a/docs/content/reference/install-configuration/entrypoints.md +++ b/docs/content/reference/install-configuration/entrypoints.md @@ -101,7 +101,9 @@ additionalArguments: | `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../../routing/providers/kubernetes-crd.md#kind-tlsoption). | - | No | | `http.tls.certResolver` | Apply a certificate resolver on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../install-configuration/tls/certificate-resolvers/overview.md). | - | No | | `http2.maxConcurrentStreams` | Set the number of concurrent streams per connection that each client is allowed to initiate.
The value must be greater than zero. | 250 | No | -| `http3` | Enable HTTP/3 protocol on the `entryPoint`.
HTTP/3 requires a TCP `entryPoint`. as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. In most scenarios, this `entryPoint` is the same as the one used for TLS traffic.
More information [here](#http3). | - | No | +| `http2.maxDecoderHeaderTableSize` | Set the maximum size of the decoder header compression table. This controls the maximum size of the header cache that the server is willing to maintain so the client does not need to repeatedly send the same header across requests in the same http2 connection.
This value is only a maximum, the other end of the connection can use a lower size. | 4096 | No | +| `http2.maxEncoderHeaderTableSize` | Set the maximum size of the encoder header compression table. This controls the maximum size of the header cache that the server is willing to maintain when sending headers to the client, allowing the server to reduce the amount of duplicate headers it is sending in responses.
This value is only a maximum, the other end of the connection can use a lower size. | 4096 | No | +| `http3` | Enable HTTP/3 protocol on the `entryPoint`.
HTTP/3 requires a TCP `entryPoint`. as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. In most scenarios, this `entryPoint` is the same as the one used for TLS traffic.
More information [here](#http3). | - | No | | `http3.advertisedPort` | Set the UDP port to advertise as the HTTP/3 authority.
It defaults to the entryPoint's address port.
It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. | - | No | | `observability.accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | | `observability.metrics` | Defines whether a router attached to this EntryPoint produces metrics by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 6911f43b9..8f1b7a10d 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -107,6 +107,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. address: ":8888" # same as ":8888/tcp" http2: maxConcurrentStreams: 42 + maxDecoderHeaderTableSize: 42 + maxEncoderHeaderTableSize: 42 http3: advertisedPort: 8888 transport: @@ -136,6 +138,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. address = ":8888" # same as ":8888/tcp" [entryPoints.name.http2] maxConcurrentStreams = 42 + maxDecoderHeaderTableSize = 42 + maxEncoderHeaderTableSize = 42 [entryPoints.name.http3] advertisedPort = 8888 [entryPoints.name.transport] @@ -158,6 +162,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments. ## Static configuration --entryPoints.name.address=:8888 # same as :8888/tcp --entryPoints.name.http2.maxConcurrentStreams=42 + --entryPoints.name.http2.maxDecoderHeaderTableSize=42 + --entryPoints.name.http2.maxEncoderHeaderTableSize=42 --entryPoints.name.http3.advertisedport=8888 --entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42 --entryPoints.name.transport.lifeCycle.graceTimeOut=42 @@ -408,6 +414,52 @@ entryPoints: --entryPoints.name.http2.maxConcurrentStreams=250 ``` +#### `maxDecoderHeaderTableSize` + +_Optional, Default=4096_ + +`maxDecoderHeaderTableSize` specifies the maximum size of the HTTP2 HPACK header table on the decoding (receiving from client) side. + +```yaml tab="File (YAML)" +entryPoints: + foo: + http2: + maxDecoderHeaderTableSize: 4096 +``` + +```toml tab="File (TOML)" +[entryPoints.foo] + [entryPoints.foo.http2] + maxDecoderHeaderTableSize = 4096 +``` + +```bash tab="CLI" +--entryPoints.name.http2.maxDecoderHeaderTableSize=4096 +``` + +#### `maxEncoderHeaderTableSize` + +_Optional, Default=4096_ + +`maxEncoderHeaderTableSize` specifies the maximum size of the HTTP2 HPACK header table on the encoding (sending to client) side. + +```yaml tab="File (YAML)" +entryPoints: + foo: + http2: + maxEncoderHeaderTableSize: 4096 +``` + +```toml tab="File (TOML)" +[entryPoints.foo] + [entryPoints.foo.http2] + maxEncoderHeaderTableSize = 4096 +``` + +```bash tab="CLI" +--entryPoints.name.http2.maxEncoderHeaderTableSize=4096 +``` + ### HTTP/3 #### `http3` diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 3ac50e048..3153ad717 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -81,12 +81,16 @@ func (c *HTTPConfig) SetDefaults() { // 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"` + 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"` + MaxDecoderHeaderTableSize int32 `description:"Specifies the maximum size of the HTTP2 HPACK header table on the decoding (receiving from client) side." json:"maxDecoderHeaderTableSize,omitempty" toml:"maxDecoderHeaderTableSize,omitempty" yaml:"maxDecoderHeaderTableSize,omitempty" export:"true"` + MaxEncoderHeaderTableSize int32 `description:"Specifies the maximum size of the HTTP2 HPACK header table on the encoding (sending to client) side." json:"maxEncoderHeaderTableSize,omitempty" toml:"maxEncoderHeaderTableSize,omitempty" yaml:"maxEncoderHeaderTableSize,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 + c.MaxConcurrentStreams = 250 // https://cs.opensource.google/go/x/net/+/cd36cc07:http2/server.go;l=58 + c.MaxDecoderHeaderTableSize = 4096 // https://cs.opensource.google/go/x/net/+/0e478a2a:http2/server.go;l=105 + c.MaxEncoderHeaderTableSize = 4096 // https://cs.opensource.google/go/x/net/+/0e478a2a:http2/server.go;l=111 } // HTTP3Config is the HTTP3 configuration of an entry point. diff --git a/pkg/config/static/static_config_test.go b/pkg/config/static/static_config_test.go index a18c7c35e..b7ba5b729 100644 --- a/pkg/config/static/static_config_test.go +++ b/pkg/config/static/static_config_test.go @@ -74,7 +74,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ @@ -120,7 +122,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ @@ -177,7 +181,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ @@ -238,7 +244,9 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { MaxHeaderBytes: 1048576, }, HTTP2: &HTTP2Config{ - MaxConcurrentStreams: 250, + MaxConcurrentStreams: 250, + MaxDecoderHeaderTableSize: 4096, + MaxEncoderHeaderTableSize: 4096, }, HTTP3: nil, UDP: &UDPConfig{ diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index eb68714d2..c6e5e5857 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -631,6 +631,12 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E if configuration.HTTP2.MaxConcurrentStreams < 0 { return nil, errors.New("max concurrent streams value must be greater than or equal to zero") } + if configuration.HTTP2.MaxDecoderHeaderTableSize < 0 { + return nil, errors.New("max decoder header table size value must be greater than or equal to zero") + } + if configuration.HTTP2.MaxEncoderHeaderTableSize < 0 { + return nil, errors.New("max encoder header table size value must be greater than or equal to zero") + } httpSwitcher := middlewares.NewHandlerSwitcher(http.NotFoundHandler()) @@ -688,7 +694,9 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), MaxHeaderBytes: configuration.HTTP.MaxHeaderBytes, HTTP2: &http.HTTP2Config{ - MaxConcurrentStreams: int(configuration.HTTP2.MaxConcurrentStreams), + MaxConcurrentStreams: int(configuration.HTTP2.MaxConcurrentStreams), + MaxDecoderHeaderTableSize: int(configuration.HTTP2.MaxDecoderHeaderTableSize), + MaxEncoderHeaderTableSize: int(configuration.HTTP2.MaxEncoderHeaderTableSize), }, } if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index 8170234a7..acb3de6de 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -648,3 +648,34 @@ func TestPathOperations(t *testing.T) { }) } } + +func TestHTTP2Config(t *testing.T) { + expectedMaxConcurrentStreams := 42 + expectedEncoderTableSize := 128 + expectedDecoderTableSize := 256 + + // Create a listener for the server. + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { + _ = ln.Close() + }) + + // Define the server configuration. + configuration := &static.EntryPoint{} + configuration.SetDefaults() + configuration.HTTP2.MaxConcurrentStreams = int32(expectedMaxConcurrentStreams) + configuration.HTTP2.MaxEncoderHeaderTableSize = int32(expectedEncoderTableSize) + configuration.HTTP2.MaxDecoderHeaderTableSize = int32(expectedDecoderTableSize) + + // Create the HTTP server using newHTTPServer. + server, err := newHTTPServer(t.Context(), ln, configuration, false, requestdecorator.New(nil)) + require.NoError(t, err) + + // Get the underlying HTTP Server. + httpServer := server.Server.(*http.Server) + + assert.Equal(t, expectedMaxConcurrentStreams, httpServer.HTTP2.MaxConcurrentStreams) + assert.Equal(t, expectedEncoderTableSize, httpServer.HTTP2.MaxEncoderHeaderTableSize) + assert.Equal(t, expectedDecoderTableSize, httpServer.HTTP2.MaxDecoderHeaderTableSize) +}