You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by xu...@apache.org on 2026/09/01 02:13:21 UTC

(dubbo-go) branch develop updated: feat(triple): add HTTP/3 receive window config (#3704)

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 <33...@qq.com>
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 <33...@qq.com>
    
    * fix(triple): validate HTTP/3 receive window config
    
    Signed-off-by: CAICAIIs <33...@qq.com>
    
    * fix(triple): format HTTP/3 receive window test imports
    
    ---------
    
    Signed-off-by: CAICAIIs <33...@qq.com>
---
 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) {