This is an automated email from the ASF dual-hosted git repository.
Alanxtl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new 4154395f7 feat(triple): add HTTP/3 receive window config (#3704)
4154395f7 is described below
commit 4154395f7eefd5810cf4af73d5d76847e866de76
Author: CAICAII <[email protected]>
AuthorDate: Tue Sep 1 10:13:16 2026 +0800
feat(triple): add HTTP/3 receive window config (#3704)
* feat(triple): add HTTP/3 receive window config
Signed-off-by: CAICAIIs <[email protected]>
* fix(triple): validate HTTP/3 receive window config
Signed-off-by: CAICAIIs <[email protected]>
* fix(triple): format HTTP/3 receive window test imports
---------
Signed-off-by: CAICAIIs <[email protected]>
---
global/config_test.go | 54 ++++--
global/http3_config.go | 44 +++--
.../triple/internal/http3config/http3_config.go | 72 ++++++++
.../internal/http3config/http3_config_test.go | 195 +++++++++++++++++++--
protocol/triple/options.go | 33 ++++
protocol/triple/options_test.go | 79 +++++++++
6 files changed, 440 insertions(+), 37 deletions(-)
diff --git a/global/config_test.go b/global/config_test.go
index e5e948f32..798562f3d 100644
--- a/global/config_test.go
+++ b/global/config_test.go
@@ -1166,12 +1166,16 @@ func TestDefaultTripleConfig(t *testing.T) {
func TestHttp3ConfigClone(t *testing.T) {
t.Run("clone_http3_config", func(t *testing.T) {
http3 := &Http3Config{
- Enable: true,
- Negotiation: false,
- KeepAlivePeriod: "15s",
- MaxIdleTimeout: "30s",
- MaxIncomingStreams: 128,
- MaxIncomingUniStreams: 64,
+ Enable: true,
+ Negotiation: false,
+ KeepAlivePeriod: "15s",
+ MaxIdleTimeout: "30s",
+ MaxIncomingStreams: 128,
+ MaxIncomingUniStreams: 64,
+ InitialStreamReceiveWindow: "524288",
+ MaxStreamReceiveWindow: "6291456",
+ InitialConnectionReceiveWindow: "1048576",
+ MaxConnectionReceiveWindow: "16777216",
}
cloned := http3.Clone()
assert.NotNil(t, cloned)
@@ -1182,6 +1186,10 @@ func TestHttp3ConfigClone(t *testing.T) {
assert.Equal(t, http3.MaxIdleTimeout, cloned.MaxIdleTimeout)
assert.Equal(t, http3.MaxIncomingStreams,
cloned.MaxIncomingStreams)
assert.Equal(t, http3.MaxIncomingUniStreams,
cloned.MaxIncomingUniStreams)
+ assert.Equal(t, http3.InitialStreamReceiveWindow,
cloned.InitialStreamReceiveWindow)
+ assert.Equal(t, http3.MaxStreamReceiveWindow,
cloned.MaxStreamReceiveWindow)
+ assert.Equal(t, http3.InitialConnectionReceiveWindow,
cloned.InitialConnectionReceiveWindow)
+ assert.Equal(t, http3.MaxConnectionReceiveWindow,
cloned.MaxConnectionReceiveWindow)
})
t.Run("clone_nil_http3_config", func(t *testing.T) {
@@ -1201,6 +1209,10 @@ func TestHttp3ConfigClone(t *testing.T) {
assert.Equal(t, http3.MaxIdleTimeout, cloned.MaxIdleTimeout)
assert.Equal(t, http3.MaxIncomingStreams,
cloned.MaxIncomingStreams)
assert.Equal(t, http3.MaxIncomingUniStreams,
cloned.MaxIncomingUniStreams)
+ assert.Equal(t, http3.InitialStreamReceiveWindow,
cloned.InitialStreamReceiveWindow)
+ assert.Equal(t, http3.MaxStreamReceiveWindow,
cloned.MaxStreamReceiveWindow)
+ assert.Equal(t, http3.InitialConnectionReceiveWindow,
cloned.InitialConnectionReceiveWindow)
+ assert.Equal(t, http3.MaxConnectionReceiveWindow,
cloned.MaxConnectionReceiveWindow)
})
}
@@ -1215,17 +1227,25 @@ func TestDefaultHttp3Config(t *testing.T) {
assert.Empty(t, http3.MaxIdleTimeout)
assert.Zero(t, http3.MaxIncomingStreams)
assert.Zero(t, http3.MaxIncomingUniStreams)
+ assert.Empty(t, http3.InitialStreamReceiveWindow)
+ assert.Empty(t, http3.MaxStreamReceiveWindow)
+ assert.Empty(t, http3.InitialConnectionReceiveWindow)
+ assert.Empty(t, http3.MaxConnectionReceiveWindow)
})
}
func TestHttp3ConfigJSONTags(t *testing.T) {
http3 := &Http3Config{
- Enable: true,
- Negotiation: true,
- KeepAlivePeriod: "15s",
- MaxIdleTimeout: "30s",
- MaxIncomingStreams: 128,
- MaxIncomingUniStreams: 64,
+ Enable: true,
+ Negotiation: true,
+ KeepAlivePeriod: "15s",
+ MaxIdleTimeout: "30s",
+ MaxIncomingStreams: 128,
+ MaxIncomingUniStreams: 64,
+ InitialStreamReceiveWindow: "524288",
+ MaxStreamReceiveWindow: "6291456",
+ InitialConnectionReceiveWindow: "1048576",
+ MaxConnectionReceiveWindow: "16777216",
}
data, err := json.Marshal(http3)
@@ -1234,6 +1254,10 @@ func TestHttp3ConfigJSONTags(t *testing.T) {
assert.Contains(t, string(data), "\"max-idle-timeout\":\"30s\"")
assert.Contains(t, string(data), "\"max-incoming-streams\":128")
assert.Contains(t, string(data), "\"max-incoming-uni-streams\":64")
+ assert.Contains(t, string(data),
"\"initial-stream-receive-window\":\"524288\"")
+ assert.Contains(t, string(data),
"\"max-stream-receive-window\":\"6291456\"")
+ assert.Contains(t, string(data),
"\"initial-connection-receive-window\":\"1048576\"")
+ assert.Contains(t, string(data),
"\"max-connection-receive-window\":\"16777216\"")
var decoded Http3Config
err = json.Unmarshal([]byte(`{
@@ -1242,7 +1266,11 @@ func TestHttp3ConfigJSONTags(t *testing.T) {
"keep-alive-period": "15s",
"max-idle-timeout": "30s",
"max-incoming-streams": 128,
- "max-incoming-uni-streams": 64
+ "max-incoming-uni-streams": 64,
+ "initial-stream-receive-window": "524288",
+ "max-stream-receive-window": "6291456",
+ "initial-connection-receive-window": "1048576",
+ "max-connection-receive-window": "16777216"
}`), &decoded)
require.NoError(t, err)
assert.Equal(t, http3, &decoded)
diff --git a/global/http3_config.go b/global/http3_config.go
index c77a8f030..e337d53ad 100644
--- a/global/http3_config.go
+++ b/global/http3_config.go
@@ -46,17 +46,33 @@ type Http3Config struct {
// MaxIncomingUniStreams defines the maximum number of concurrent
unidirectional streams accepted by server and client.
MaxIncomingUniStreams int64 `yaml:"max-incoming-uni-streams"
json:"max-incoming-uni-streams,omitempty"`
+ // InitialStreamReceiveWindow defines the initial stream-level flow
control receive window.
+ // Size strings such as "512KiB" and "6MiB" are supported.
+ InitialStreamReceiveWindow string `yaml:"initial-stream-receive-window"
json:"initial-stream-receive-window,omitempty"`
+ // MaxStreamReceiveWindow defines the maximum stream-level flow control
receive window.
+ // Size strings such as "512KiB" and "6MiB" are supported.
+ MaxStreamReceiveWindow string `yaml:"max-stream-receive-window"
json:"max-stream-receive-window,omitempty"`
+ // InitialConnectionReceiveWindow defines the initial connection-level
flow control receive window.
+ // Size strings such as "512KiB" and "15MiB" are supported.
+ InitialConnectionReceiveWindow string
`yaml:"initial-connection-receive-window"
json:"initial-connection-receive-window,omitempty"`
+ // MaxConnectionReceiveWindow defines the maximum connection-level flow
control receive window.
+ // Size strings such as "512KiB" and "15MiB" are supported.
+ MaxConnectionReceiveWindow string `yaml:"max-connection-receive-window"
json:"max-connection-receive-window,omitempty"`
}
// DefaultHttp3Config returns a default Http3Config instance.
func DefaultHttp3Config() *Http3Config {
return &Http3Config{
- Enable: false,
- Negotiation: true,
- KeepAlivePeriod: "",
- MaxIdleTimeout: "",
- MaxIncomingStreams: 0,
- MaxIncomingUniStreams: 0,
+ Enable: false,
+ Negotiation: true,
+ KeepAlivePeriod: "",
+ MaxIdleTimeout: "",
+ MaxIncomingStreams: 0,
+ MaxIncomingUniStreams: 0,
+ InitialStreamReceiveWindow: "",
+ MaxStreamReceiveWindow: "",
+ InitialConnectionReceiveWindow: "",
+ MaxConnectionReceiveWindow: "",
}
}
@@ -67,11 +83,15 @@ func (t *Http3Config) Clone() *Http3Config {
}
return &Http3Config{
- Enable: t.Enable,
- Negotiation: t.Negotiation,
- KeepAlivePeriod: t.KeepAlivePeriod,
- MaxIdleTimeout: t.MaxIdleTimeout,
- MaxIncomingStreams: t.MaxIncomingStreams,
- MaxIncomingUniStreams: t.MaxIncomingUniStreams,
+ Enable: t.Enable,
+ Negotiation: t.Negotiation,
+ KeepAlivePeriod: t.KeepAlivePeriod,
+ MaxIdleTimeout: t.MaxIdleTimeout,
+ MaxIncomingStreams: t.MaxIncomingStreams,
+ MaxIncomingUniStreams: t.MaxIncomingUniStreams,
+ InitialStreamReceiveWindow: t.InitialStreamReceiveWindow,
+ MaxStreamReceiveWindow: t.MaxStreamReceiveWindow,
+ InitialConnectionReceiveWindow:
t.InitialConnectionReceiveWindow,
+ MaxConnectionReceiveWindow: t.MaxConnectionReceiveWindow,
}
}
diff --git a/protocol/triple/internal/http3config/http3_config.go
b/protocol/triple/internal/http3config/http3_config.go
index 89bf86964..631e872fe 100644
--- a/protocol/triple/internal/http3config/http3_config.go
+++ b/protocol/triple/internal/http3config/http3_config.go
@@ -19,11 +19,16 @@ package http3config
import (
"fmt"
+ "strconv"
+ "strings"
"time"
)
import (
+ "github.com/dustin/go-humanize"
+
"github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/quicvarint"
)
import (
@@ -65,5 +70,72 @@ func NewQUICConfig(http3Config *global.Http3Config, defaults
*quic.Config) (*qui
quicConfig.MaxIncomingUniStreams =
http3Config.MaxIncomingUniStreams
}
+ parseReceiveWindow := func(name, value string) (uint64, error) {
+ // Parse option-generated decimal values exactly before
accepting humanized sizes.
+ window, err := strconv.ParseUint(strings.TrimSpace(value), 10,
64)
+ if err != nil {
+ window, err = humanize.ParseBytes(value)
+ }
+ if err != nil {
+ return 0, fmt.Errorf("invalid http3 %s %q: %w", name,
value, err)
+ }
+ return window, nil
+ }
+
+ initialStreamReceiveWindow := quicConfig.InitialStreamReceiveWindow
+ maxStreamReceiveWindow := quicConfig.MaxStreamReceiveWindow
+ initialConnectionReceiveWindow :=
quicConfig.InitialConnectionReceiveWindow
+ maxConnectionReceiveWindow := quicConfig.MaxConnectionReceiveWindow
+ var err error
+ if http3Config.InitialStreamReceiveWindow != "" {
+ initialStreamReceiveWindow, err =
parseReceiveWindow("initial-stream-receive-window",
http3Config.InitialStreamReceiveWindow)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if http3Config.MaxStreamReceiveWindow != "" {
+ maxStreamReceiveWindow, err =
parseReceiveWindow("max-stream-receive-window",
http3Config.MaxStreamReceiveWindow)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if http3Config.InitialConnectionReceiveWindow != "" {
+ initialConnectionReceiveWindow, err =
parseReceiveWindow("initial-connection-receive-window",
http3Config.InitialConnectionReceiveWindow)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if http3Config.MaxConnectionReceiveWindow != "" {
+ maxConnectionReceiveWindow, err =
parseReceiveWindow("max-connection-receive-window",
http3Config.MaxConnectionReceiveWindow)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ for _, receiveWindow := range []struct {
+ name string
+ value uint64
+ }{
+ {"initial-stream-receive-window", initialStreamReceiveWindow},
+ {"max-stream-receive-window", maxStreamReceiveWindow},
+ {"initial-connection-receive-window",
initialConnectionReceiveWindow},
+ {"max-connection-receive-window", maxConnectionReceiveWindow},
+ } {
+ if receiveWindow.value > quicvarint.Max {
+ return nil, fmt.Errorf("invalid http3 %s: value %d
exceeds QUIC varint maximum %d", receiveWindow.name, receiveWindow.value,
quicvarint.Max)
+ }
+ }
+ if initialStreamReceiveWindow != 0 && maxStreamReceiveWindow != 0 &&
initialStreamReceiveWindow > maxStreamReceiveWindow {
+ return nil, fmt.Errorf("invalid http3 receive windows:
initial-stream-receive-window %d exceeds max-stream-receive-window %d",
initialStreamReceiveWindow, maxStreamReceiveWindow)
+ }
+ if initialConnectionReceiveWindow != 0 && maxConnectionReceiveWindow !=
0 && initialConnectionReceiveWindow > maxConnectionReceiveWindow {
+ return nil, fmt.Errorf("invalid http3 receive windows:
initial-connection-receive-window %d exceeds max-connection-receive-window %d",
initialConnectionReceiveWindow, maxConnectionReceiveWindow)
+ }
+
+ quicConfig.InitialStreamReceiveWindow = initialStreamReceiveWindow
+ quicConfig.MaxStreamReceiveWindow = maxStreamReceiveWindow
+ quicConfig.InitialConnectionReceiveWindow =
initialConnectionReceiveWindow
+ quicConfig.MaxConnectionReceiveWindow = maxConnectionReceiveWindow
+
return quicConfig, nil
}
diff --git a/protocol/triple/internal/http3config/http3_config_test.go
b/protocol/triple/internal/http3config/http3_config_test.go
index 2d3982d8c..474c1bc58 100644
--- a/protocol/triple/internal/http3config/http3_config_test.go
+++ b/protocol/triple/internal/http3config/http3_config_test.go
@@ -24,9 +24,12 @@ import (
import (
"github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/quicvarint"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+
+ "go.yaml.in/yaml/v4"
)
import (
@@ -42,14 +45,22 @@ func TestNewQUICConfig(t *testing.T) {
assert.Zero(t, quicConfig.MaxIdleTimeout)
assert.Zero(t, quicConfig.MaxIncomingStreams)
assert.Zero(t, quicConfig.MaxIncomingUniStreams)
+ assert.Zero(t, quicConfig.InitialStreamReceiveWindow)
+ assert.Zero(t, quicConfig.MaxStreamReceiveWindow)
+ assert.Zero(t, quicConfig.InitialConnectionReceiveWindow)
+ assert.Zero(t, quicConfig.MaxConnectionReceiveWindow)
})
t.Run("explicit_fields_are_mapped", func(t *testing.T) {
quicConfig, err := NewQUICConfig(&global.Http3Config{
- KeepAlivePeriod: "15s",
- MaxIdleTimeout: "30s",
- MaxIncomingStreams: 128,
- MaxIncomingUniStreams: 64,
+ KeepAlivePeriod: "15s",
+ MaxIdleTimeout: "30s",
+ MaxIncomingStreams: 128,
+ MaxIncomingUniStreams: 64,
+ InitialStreamReceiveWindow: "512KiB",
+ MaxStreamReceiveWindow: "6MiB",
+ InitialConnectionReceiveWindow: "1MiB",
+ MaxConnectionReceiveWindow: "16MiB",
}, nil)
require.NoError(t, err)
require.NotNil(t, quicConfig)
@@ -57,6 +68,10 @@ func TestNewQUICConfig(t *testing.T) {
assert.Equal(t, 30*time.Second, quicConfig.MaxIdleTimeout)
assert.Equal(t, int64(128), quicConfig.MaxIncomingStreams)
assert.Equal(t, int64(64), quicConfig.MaxIncomingUniStreams)
+ assert.Equal(t, uint64(524288),
quicConfig.InitialStreamReceiveWindow)
+ assert.Equal(t, uint64(6291456),
quicConfig.MaxStreamReceiveWindow)
+ assert.Equal(t, uint64(1048576),
quicConfig.InitialConnectionReceiveWindow)
+ assert.Equal(t, uint64(16777216),
quicConfig.MaxConnectionReceiveWindow)
})
t.Run("invalid_keep_alive_period_returns_error", func(t *testing.T) {
@@ -76,10 +91,142 @@ func TestNewQUICConfig(t *testing.T) {
assert.Nil(t, quicConfig)
assert.ErrorContains(t, err, "max-idle-timeout")
})
+
+ t.Run("invalid_receive_window_returns_error", func(t *testing.T) {
+ quicConfig, err := NewQUICConfig(&global.Http3Config{
+ MaxConnectionReceiveWindow: "invalid",
+ }, nil)
+ require.Error(t, err)
+ assert.Nil(t, quicConfig)
+ assert.ErrorContains(t, err, "max-connection-receive-window")
+ })
+
+ t.Run("receive_windows_must_fit_quic_varint", func(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ field func(*global.Http3Config)
+ want string
+ }{
+ {
+ name: "initial_stream",
+ field: func(config *global.Http3Config) {
+ config.InitialStreamReceiveWindow =
"4611686018427387904"
+ },
+ want: "initial-stream-receive-window",
+ },
+ {
+ name: "max_stream",
+ field: func(config *global.Http3Config) {
+ config.MaxStreamReceiveWindow =
"4611686018427387904"
+ },
+ want: "max-stream-receive-window",
+ },
+ {
+ name: "initial_connection",
+ field: func(config *global.Http3Config) {
+ config.InitialConnectionReceiveWindow =
"4611686018427387904"
+ },
+ want: "initial-connection-receive-window",
+ },
+ {
+ name: "max_connection",
+ field: func(config *global.Http3Config) {
+ config.MaxConnectionReceiveWindow =
"4611686018427387904"
+ },
+ want: "max-connection-receive-window",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ config := &global.Http3Config{}
+ test.field(config)
+
+ quicConfig, err := NewQUICConfig(config, nil)
+ require.Error(t, err)
+ assert.Nil(t, quicConfig)
+ require.ErrorContains(t, err, test.want)
+ require.ErrorContains(t, err, "QUIC varint
maximum")
+ })
+ }
+ })
+
+ t.Run("initial_receive_window_must_not_exceed_maximum", func(t
*testing.T) {
+ for _, test := range []struct {
+ name string
+ configure func(*global.Http3Config)
+ want string
+ }{
+ {
+ name: "stream",
+ configure: func(config *global.Http3Config) {
+ config.InitialStreamReceiveWindow =
"16MiB"
+ config.MaxStreamReceiveWindow = "1MiB"
+ },
+ want: "initial-stream-receive-window",
+ },
+ {
+ name: "connection",
+ configure: func(config *global.Http3Config) {
+ config.InitialConnectionReceiveWindow =
"32MiB"
+ config.MaxConnectionReceiveWindow =
"2MiB"
+ },
+ want: "initial-connection-receive-window",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ config := &global.Http3Config{
+ InitialStreamReceiveWindow: "1MiB",
+ MaxStreamReceiveWindow: "2MiB",
+ InitialConnectionReceiveWindow: "1MiB",
+ MaxConnectionReceiveWindow: "2MiB",
+ }
+ test.configure(config)
+
+ quicConfig, err := NewQUICConfig(config, nil)
+ require.Error(t, err)
+ assert.Nil(t, quicConfig)
+ assert.ErrorContains(t, err, test.want)
+ })
+ }
+ })
+
+ t.Run("quic_varint_maximum_is_accepted", func(t *testing.T) {
+ max := "4611686018427387903"
+ quicConfig, err := NewQUICConfig(&global.Http3Config{
+ InitialStreamReceiveWindow: max,
+ MaxStreamReceiveWindow: max,
+ InitialConnectionReceiveWindow: max,
+ MaxConnectionReceiveWindow: max,
+ }, nil)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.InitialStreamReceiveWindow)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.MaxStreamReceiveWindow)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.InitialConnectionReceiveWindow)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.MaxConnectionReceiveWindow)
+ })
+
+ t.Run("yaml_receive_windows_are_validated", func(t *testing.T) {
+ var config global.TripleConfig
+ err := yaml.Unmarshal([]byte(`
+http3:
+ initial-stream-receive-window: "16MiB"
+ max-stream-receive-window: "1MiB"
+ initial-connection-receive-window: "32MiB"
+ max-connection-receive-window: "2MiB"
+`), &config)
+ require.NoError(t, err)
+ _, err = NewQUICConfig(config.Http3, nil)
+ require.Error(t, err)
+ assert.ErrorContains(t, err, "initial-stream-receive-window")
+ })
+
t.Run("nil_config_uses_defaults", func(t *testing.T) {
defaults := &quic.Config{
- KeepAlivePeriod: 10 * time.Second,
- MaxIdleTimeout: 20 * time.Second,
+ KeepAlivePeriod: 10 * time.Second,
+ MaxIdleTimeout: 20 * time.Second,
+ InitialStreamReceiveWindow: 512 * 1024,
+ MaxStreamReceiveWindow: 6 * 1024 * 1024,
+ InitialConnectionReceiveWindow: 512 * 1024,
+ MaxConnectionReceiveWindow: 15 * 1024 * 1024,
}
quicConfig, err := NewQUICConfig(nil, defaults)
@@ -88,12 +235,20 @@ func TestNewQUICConfig(t *testing.T) {
assert.NotSame(t, defaults, quicConfig)
assert.Equal(t, 10*time.Second, quicConfig.KeepAlivePeriod)
assert.Equal(t, 20*time.Second, quicConfig.MaxIdleTimeout)
+ assert.Equal(t, uint64(512*1024),
quicConfig.InitialStreamReceiveWindow)
+ assert.Equal(t, uint64(6*1024*1024),
quicConfig.MaxStreamReceiveWindow)
+ assert.Equal(t, uint64(512*1024),
quicConfig.InitialConnectionReceiveWindow)
+ assert.Equal(t, uint64(15*1024*1024),
quicConfig.MaxConnectionReceiveWindow)
})
t.Run("defaults_are_used_when_fields_unset", func(t *testing.T) {
defaults := &quic.Config{
- KeepAlivePeriod: 10 * time.Second,
- MaxIdleTimeout: 20 * time.Second,
+ KeepAlivePeriod: 10 * time.Second,
+ MaxIdleTimeout: 20 * time.Second,
+ InitialStreamReceiveWindow: 512 * 1024,
+ MaxStreamReceiveWindow: 6 * 1024 * 1024,
+ InitialConnectionReceiveWindow: 512 * 1024,
+ MaxConnectionReceiveWindow: 15 * 1024 * 1024,
}
quicConfig, err := NewQUICConfig(&global.Http3Config{},
defaults)
@@ -102,20 +257,36 @@ func TestNewQUICConfig(t *testing.T) {
assert.NotSame(t, defaults, quicConfig)
assert.Equal(t, 10*time.Second, quicConfig.KeepAlivePeriod)
assert.Equal(t, 20*time.Second, quicConfig.MaxIdleTimeout)
+ assert.Equal(t, uint64(512*1024),
quicConfig.InitialStreamReceiveWindow)
+ assert.Equal(t, uint64(6*1024*1024),
quicConfig.MaxStreamReceiveWindow)
+ assert.Equal(t, uint64(512*1024),
quicConfig.InitialConnectionReceiveWindow)
+ assert.Equal(t, uint64(15*1024*1024),
quicConfig.MaxConnectionReceiveWindow)
})
t.Run("explicit_fields_override_defaults", func(t *testing.T) {
quicConfig, err := NewQUICConfig(&global.Http3Config{
- KeepAlivePeriod: "15s",
- MaxIdleTimeout: "30s",
+ KeepAlivePeriod: "15s",
+ MaxIdleTimeout: "30s",
+ InitialStreamReceiveWindow: "262144",
+ MaxStreamReceiveWindow: "4194304",
+ InitialConnectionReceiveWindow: "524288",
+ MaxConnectionReceiveWindow: "8388608",
}, &quic.Config{
- KeepAlivePeriod: 10 * time.Second,
- MaxIdleTimeout: 20 * time.Second,
+ KeepAlivePeriod: 10 * time.Second,
+ MaxIdleTimeout: 20 * time.Second,
+ InitialStreamReceiveWindow: 512 * 1024,
+ MaxStreamReceiveWindow: 6 * 1024 * 1024,
+ InitialConnectionReceiveWindow: 512 * 1024,
+ MaxConnectionReceiveWindow: 15 * 1024 * 1024,
})
require.NoError(t, err)
require.NotNil(t, quicConfig)
assert.Equal(t, 15*time.Second, quicConfig.KeepAlivePeriod)
assert.Equal(t, 30*time.Second, quicConfig.MaxIdleTimeout)
+ assert.Equal(t, uint64(262144),
quicConfig.InitialStreamReceiveWindow)
+ assert.Equal(t, uint64(4194304),
quicConfig.MaxStreamReceiveWindow)
+ assert.Equal(t, uint64(524288),
quicConfig.InitialConnectionReceiveWindow)
+ assert.Equal(t, uint64(8388608),
quicConfig.MaxConnectionReceiveWindow)
})
t.Run("explicit_zero_duration_overrides_default", func(t *testing.T) {
diff --git a/protocol/triple/options.go b/protocol/triple/options.go
index 9e1aa9276..61141658e 100644
--- a/protocol/triple/options.go
+++ b/protocol/triple/options.go
@@ -22,6 +22,7 @@ import (
"maps"
"net/http"
"net/url"
+ "strconv"
"strings"
"time"
)
@@ -230,6 +231,38 @@ func WithHttp3MaxIncomingUniStreams(streams int64) Option {
}
}
+// WithHttp3InitialStreamReceiveWindow sets the initial stream-level flow
control receive window in bytes.
+// A zero window keeps the quic-go default.
+func WithHttp3InitialStreamReceiveWindow(window uint64) Option {
+ return func(opts *Options) {
+ opts.Triple.Http3.InitialStreamReceiveWindow =
strconv.FormatUint(window, 10)
+ }
+}
+
+// WithHttp3MaxStreamReceiveWindow sets the maximum stream-level flow control
receive window in bytes.
+// A zero window keeps the quic-go default.
+func WithHttp3MaxStreamReceiveWindow(window uint64) Option {
+ return func(opts *Options) {
+ opts.Triple.Http3.MaxStreamReceiveWindow =
strconv.FormatUint(window, 10)
+ }
+}
+
+// WithHttp3InitialConnectionReceiveWindow sets the initial connection-level
flow control receive window in bytes.
+// A zero window keeps the quic-go default.
+func WithHttp3InitialConnectionReceiveWindow(window uint64) Option {
+ return func(opts *Options) {
+ opts.Triple.Http3.InitialConnectionReceiveWindow =
strconv.FormatUint(window, 10)
+ }
+}
+
+// WithHttp3MaxConnectionReceiveWindow sets the maximum connection-level flow
control receive window in bytes.
+// A zero window keeps the quic-go default.
+func WithHttp3MaxConnectionReceiveWindow(window uint64) Option {
+ return func(opts *Options) {
+ opts.Triple.Http3.MaxConnectionReceiveWindow =
strconv.FormatUint(window, 10)
+ }
+}
+
// CORSOption configures a single aspect of CORS.
type CORSOption func(*global.CorsConfig)
diff --git a/protocol/triple/options_test.go b/protocol/triple/options_test.go
index 61c5f1712..60667f636 100644
--- a/protocol/triple/options_test.go
+++ b/protocol/triple/options_test.go
@@ -23,10 +23,16 @@ import (
)
import (
+ "github.com/quic-go/quic-go/quicvarint"
+
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+import (
+ "dubbo.apache.org/dubbo-go/v3/protocol/triple/internal/http3config"
+)
+
func TestHTTP3TransportOptions(t *testing.T) {
opts := NewOptions(
WithHttp3Enable(),
@@ -35,6 +41,10 @@ func TestHTTP3TransportOptions(t *testing.T) {
WithHttp3MaxIdleTimeout(30*time.Second),
WithHttp3MaxIncomingStreams(128),
WithHttp3MaxIncomingUniStreams(64),
+ WithHttp3InitialStreamReceiveWindow(512*1024),
+ WithHttp3MaxStreamReceiveWindow(6*1024*1024),
+ WithHttp3InitialConnectionReceiveWindow(1024*1024),
+ WithHttp3MaxConnectionReceiveWindow(16*1024*1024),
)
require.NotNil(t, opts)
@@ -46,6 +56,75 @@ func TestHTTP3TransportOptions(t *testing.T) {
assert.Equal(t, "30s", opts.Triple.Http3.MaxIdleTimeout)
assert.Equal(t, int64(128), opts.Triple.Http3.MaxIncomingStreams)
assert.Equal(t, int64(64), opts.Triple.Http3.MaxIncomingUniStreams)
+ assert.Equal(t, "524288", opts.Triple.Http3.InitialStreamReceiveWindow)
+ assert.Equal(t, "6291456", opts.Triple.Http3.MaxStreamReceiveWindow)
+ assert.Equal(t, "1048576",
opts.Triple.Http3.InitialConnectionReceiveWindow)
+ assert.Equal(t, "16777216",
opts.Triple.Http3.MaxConnectionReceiveWindow)
+}
+
+func TestHTTP3ReceiveWindowOptionsAreValidated(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ options []Option
+ field string
+ }{
+ {
+ name: "initial_stream_overflow",
+ options:
[]Option{WithHttp3InitialStreamReceiveWindow(quicvarint.Max + 1)},
+ field: "initial-stream-receive-window",
+ },
+ {
+ name: "max_stream_overflow",
+ options:
[]Option{WithHttp3MaxStreamReceiveWindow(quicvarint.Max + 1)},
+ field: "max-stream-receive-window",
+ },
+ {
+ name: "initial_connection_overflow",
+ options:
[]Option{WithHttp3InitialConnectionReceiveWindow(quicvarint.Max + 1)},
+ field: "initial-connection-receive-window",
+ },
+ {
+ name: "max_connection_overflow",
+ options:
[]Option{WithHttp3MaxConnectionReceiveWindow(quicvarint.Max + 1)},
+ field: "max-connection-receive-window",
+ },
+ {
+ name: "stream_initial_exceeds_maximum",
+ options: []Option{
+ WithHttp3InitialStreamReceiveWindow(16 * 1024 *
1024),
+ WithHttp3MaxStreamReceiveWindow(1 * 1024 *
1024),
+ },
+ field: "initial-stream-receive-window",
+ },
+ {
+ name: "connection_initial_exceeds_maximum",
+ options: []Option{
+ WithHttp3InitialConnectionReceiveWindow(32 *
1024 * 1024),
+ WithHttp3MaxConnectionReceiveWindow(2 * 1024 *
1024),
+ },
+ field: "initial-connection-receive-window",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ opts := NewOptions(test.options...)
+ _, err := http3config.NewQUICConfig(opts.Triple.Http3,
nil)
+ require.Error(t, err)
+ assert.ErrorContains(t, err, test.field)
+ })
+ }
+
+ opts := NewOptions(
+ WithHttp3InitialStreamReceiveWindow(quicvarint.Max),
+ WithHttp3MaxStreamReceiveWindow(quicvarint.Max),
+ WithHttp3InitialConnectionReceiveWindow(quicvarint.Max),
+ WithHttp3MaxConnectionReceiveWindow(quicvarint.Max),
+ )
+ quicConfig, err := http3config.NewQUICConfig(opts.Triple.Http3, nil)
+ require.NoError(t, err)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.InitialStreamReceiveWindow)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.MaxStreamReceiveWindow)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.InitialConnectionReceiveWindow)
+ assert.Equal(t, uint64(quicvarint.Max),
quicConfig.MaxConnectionReceiveWindow)
}
func TestHTTP3DeprecatedAliases(t *testing.T) {