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 c7452e174 feat(triple): add server http3 quic config helper (#3299)
c7452e174 is described below

commit c7452e1749f9f558ccf9665db0a32bfd83113ce0
Author: CAICAII <[email protected]>
AuthorDate: Wed Aug 5 07:50:20 2026 +0800

    feat(triple): add server http3 quic config helper (#3299)
    
    * feat(triple): add server http3 quic config helper
    
    * refactor(triple): drop http3 config compat mirror
    
    * test(triple): satisfy lint for http3 server tests
    
    * fix(global): align http3 json tags and decode aliases
    
    * style(global): format http3 config file
    
    * refactor(global): remove http3 json compat decoder
    
    Signed-off-by: CAICAIIs <[email protected]>
    
    ---------
    
    Signed-off-by: CAICAIIs <[email protected]>
---
 global/config_test.go                           | 57 +++++++++++++++++-
 global/http3_config.go                          | 28 +++++++--
 protocol/triple/triple_protocol/http3_config.go | 64 ++++++++++++++++++++
 protocol/triple/triple_protocol/server.go       | 28 +++++++--
 protocol/triple/triple_protocol/server_test.go  | 78 ++++++++++++++++++++++++-
 5 files changed, 242 insertions(+), 13 deletions(-)

diff --git a/global/config_test.go b/global/config_test.go
index 8efe71b7d..e5e948f32 100644
--- a/global/config_test.go
+++ b/global/config_test.go
@@ -18,6 +18,7 @@
 package global
 
 import (
+       "encoding/json"
        "reflect"
        "testing"
 )
@@ -1165,13 +1166,22 @@ 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,
+                       Enable:                true,
+                       Negotiation:           false,
+                       KeepAlivePeriod:       "15s",
+                       MaxIdleTimeout:        "30s",
+                       MaxIncomingStreams:    128,
+                       MaxIncomingUniStreams: 64,
                }
                cloned := http3.Clone()
                assert.NotNil(t, cloned)
+               assert.NotSame(t, http3, cloned)
                assert.Equal(t, http3.Enable, cloned.Enable)
                assert.Equal(t, http3.Negotiation, cloned.Negotiation)
+               assert.Equal(t, http3.KeepAlivePeriod, cloned.KeepAlivePeriod)
+               assert.Equal(t, http3.MaxIdleTimeout, cloned.MaxIdleTimeout)
+               assert.Equal(t, http3.MaxIncomingStreams, 
cloned.MaxIncomingStreams)
+               assert.Equal(t, http3.MaxIncomingUniStreams, 
cloned.MaxIncomingUniStreams)
        })
 
        t.Run("clone_nil_http3_config", func(t *testing.T) {
@@ -1184,8 +1194,13 @@ func TestHttp3ConfigClone(t *testing.T) {
                http3 := DefaultHttp3Config()
                cloned := http3.Clone()
                assert.NotNil(t, cloned)
+               assert.NotSame(t, http3, cloned)
                assert.Equal(t, http3.Enable, cloned.Enable)
                assert.Equal(t, http3.Negotiation, cloned.Negotiation)
+               assert.Equal(t, http3.KeepAlivePeriod, cloned.KeepAlivePeriod)
+               assert.Equal(t, http3.MaxIdleTimeout, cloned.MaxIdleTimeout)
+               assert.Equal(t, http3.MaxIncomingStreams, 
cloned.MaxIncomingStreams)
+               assert.Equal(t, http3.MaxIncomingUniStreams, 
cloned.MaxIncomingUniStreams)
        })
 }
 
@@ -1194,8 +1209,46 @@ func TestDefaultHttp3Config(t *testing.T) {
        t.Run("default_http3_config", func(t *testing.T) {
                http3 := DefaultHttp3Config()
                assert.NotNil(t, http3)
+               assert.False(t, http3.Enable)
+               assert.True(t, http3.Negotiation)
+               assert.Empty(t, http3.KeepAlivePeriod)
+               assert.Empty(t, http3.MaxIdleTimeout)
+               assert.Zero(t, http3.MaxIncomingStreams)
+               assert.Zero(t, http3.MaxIncomingUniStreams)
        })
 }
+
+func TestHttp3ConfigJSONTags(t *testing.T) {
+       http3 := &Http3Config{
+               Enable:                true,
+               Negotiation:           true,
+               KeepAlivePeriod:       "15s",
+               MaxIdleTimeout:        "30s",
+               MaxIncomingStreams:    128,
+               MaxIncomingUniStreams: 64,
+       }
+
+       data, err := json.Marshal(http3)
+       require.NoError(t, err)
+       assert.Contains(t, string(data), "\"keep-alive-period\":\"15s\"")
+       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")
+
+       var decoded Http3Config
+       err = json.Unmarshal([]byte(`{
+               "enable": true,
+               "negotiation": true,
+               "keep-alive-period": "15s",
+               "max-idle-timeout": "30s",
+               "max-incoming-streams": 128,
+               "max-incoming-uni-streams": 64
+       }`), &decoded)
+       require.NoError(t, err)
+       assert.Equal(t, http3, &decoded)
+
+}
+
 func TestConsumerConfigClone(t *testing.T) {
        t.Run("clone_full_consumer_config", func(t *testing.T) {
                consumer := &ConsumerConfig{
diff --git a/global/http3_config.go b/global/http3_config.go
index c079afa52..0bd4d210d 100644
--- a/global/http3_config.go
+++ b/global/http3_config.go
@@ -34,14 +34,28 @@ type Http3Config struct {
        // ref: 
https://quic-go.net/docs/http3/server/#advertising-http3-via-alt-svc
        Negotiation bool `yaml:"negotiation" json:"negotiation,omitempty"`
 
-       // TODO: add more params about http3
+       // KeepAlivePeriod defines how often to send keep-alive packets.
+       KeepAlivePeriod string `yaml:"keep-alive-period" 
json:"keep-alive-period,omitempty"`
+
+       // MaxIdleTimeout defines the maximum idle timeout for QUIC connections.
+       MaxIdleTimeout string `yaml:"max-idle-timeout" 
json:"max-idle-timeout,omitempty"`
+
+       // MaxIncomingStreams defines the maximum number of concurrent 
bidirectional streams.
+       MaxIncomingStreams int64 `yaml:"max-incoming-streams" 
json:"max-incoming-streams,omitempty"`
+
+       // MaxIncomingUniStreams defines the maximum number of concurrent 
unidirectional streams.
+       MaxIncomingUniStreams int64 `yaml:"max-incoming-uni-streams" 
json:"max-incoming-uni-streams,omitempty"`
 }
 
 // DefaultHttp3Config returns a default Http3Config instance.
 func DefaultHttp3Config() *Http3Config {
        return &Http3Config{
-               Enable:      false,
-               Negotiation: true,
+               Enable:                false,
+               Negotiation:           true,
+               KeepAlivePeriod:       "",
+               MaxIdleTimeout:        "",
+               MaxIncomingStreams:    0,
+               MaxIncomingUniStreams: 0,
        }
 }
 
@@ -52,7 +66,11 @@ func (t *Http3Config) Clone() *Http3Config {
        }
 
        return &Http3Config{
-               Enable:      t.Enable,
-               Negotiation: t.Negotiation,
+               Enable:                t.Enable,
+               Negotiation:           t.Negotiation,
+               KeepAlivePeriod:       t.KeepAlivePeriod,
+               MaxIdleTimeout:        t.MaxIdleTimeout,
+               MaxIncomingStreams:    t.MaxIncomingStreams,
+               MaxIncomingUniStreams: t.MaxIncomingUniStreams,
        }
 }
diff --git a/protocol/triple/triple_protocol/http3_config.go 
b/protocol/triple/triple_protocol/http3_config.go
new file mode 100644
index 000000000..93a520ad8
--- /dev/null
+++ b/protocol/triple/triple_protocol/http3_config.go
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "fmt"
+       "time"
+)
+
+import (
+       "github.com/quic-go/quic-go"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/global"
+)
+
+func newQUICConfig(http3Config *global.Http3Config) (*quic.Config, error) {
+       quicConfig := &quic.Config{}
+       if http3Config == nil {
+               return quicConfig, nil
+       }
+
+       if http3Config.KeepAlivePeriod != "" {
+               keepAlivePeriod, err := 
time.ParseDuration(http3Config.KeepAlivePeriod)
+               if err != nil {
+                       return nil, fmt.Errorf("invalid http3 keep-alive-period 
%q: %w", http3Config.KeepAlivePeriod, err)
+               }
+               quicConfig.KeepAlivePeriod = keepAlivePeriod
+       }
+
+       if http3Config.MaxIdleTimeout != "" {
+               maxIdleTimeout, err := 
time.ParseDuration(http3Config.MaxIdleTimeout)
+               if err != nil {
+                       return nil, fmt.Errorf("invalid http3 max-idle-timeout 
%q: %w", http3Config.MaxIdleTimeout, err)
+               }
+               quicConfig.MaxIdleTimeout = maxIdleTimeout
+       }
+
+       // Preserve quic-go defaults when these fields are left unset in config.
+       if http3Config.MaxIncomingStreams != 0 {
+               quicConfig.MaxIncomingStreams = http3Config.MaxIncomingStreams
+       }
+       if http3Config.MaxIncomingUniStreams != 0 {
+               quicConfig.MaxIncomingUniStreams = 
http3Config.MaxIncomingUniStreams
+       }
+
+       return quicConfig, nil
+}
diff --git a/protocol/triple/triple_protocol/server.go 
b/protocol/triple/triple_protocol/server.go
index 6402b9c7f..ea88764a4 100644
--- a/protocol/triple/triple_protocol/server.go
+++ b/protocol/triple/triple_protocol/server.go
@@ -29,7 +29,6 @@ import (
 
        "github.com/dubbogo/grpc-go"
 
-       "github.com/quic-go/quic-go"
        "github.com/quic-go/quic-go/http3"
 
        "golang.org/x/net/http2"
@@ -221,15 +220,24 @@ func (s *Server) startHttp3(tlsConf *tls.Config) error {
                return fmt.Errorf("TRIPLE HTTP/3 Server must have TLS config, 
but TLS config is nil")
        }
 
+       var http3Config *global.Http3Config
+       if s.tripleConfig != nil {
+               http3Config = s.tripleConfig.Http3
+       }
+
+       quicConfig, err := newQUICConfig(http3Config)
+       if err != nil {
+               return err
+       }
+
        s.http3Srv = &http3.Server{
                Addr:    s.addr,
                Handler: s.mux,
                // Adapt and enhance a generic tls.Config object into a 
configuration
                // specifically for HTTP/3 services.
                // ref: 
https://quic-go.net/docs/http3/server/#setting-up-a-http3server
-               TLSConfig: http3.ConfigureTLSConfig(tlsConf),
-               // TODO: Detailed QUIC configuration.
-               QUICConfig: &quic.Config{},
+               TLSConfig:  http3.ConfigureTLSConfig(tlsConf),
+               QUICConfig: quicConfig,
        }
 
        logger.Debugf("[Triple][Server] triple HTTP/3 Server starting on %v", 
s.addr)
@@ -243,12 +251,22 @@ func (s *Server) startHttp2AndHttp3(tlsConf *tls.Config) 
error {
                return fmt.Errorf("TRIPLE HTTP/2 and HTTP/3 Server must have 
TLS config, but TLS config is nil")
        }
 
+       var http3Config *global.Http3Config
+       if s.tripleConfig != nil {
+               http3Config = s.tripleConfig.Http3
+       }
+
+       quicConfig, err := newQUICConfig(http3Config)
+       if err != nil {
+               return err
+       }
+
        // Start HTTP/3 server first to get its configuration
        s.http3Srv = &http3.Server{
                Addr:       s.addr,
                Handler:    s.mux,
                TLSConfig:  http3.ConfigureTLSConfig(tlsConf),
-               QUICConfig: &quic.Config{},
+               QUICConfig: quicConfig,
        }
 
        // Create Alt-Svc handler wrapper for HTTP/2 server
diff --git a/protocol/triple/triple_protocol/server_test.go 
b/protocol/triple/triple_protocol/server_test.go
index d2693c98d..84d87ae45 100644
--- a/protocol/triple/triple_protocol/server_test.go
+++ b/protocol/triple/triple_protocol/server_test.go
@@ -18,10 +18,12 @@
 package triple_protocol
 
 import (
+       "crypto/tls"
        "net/http"
        "net/http/httptest"
        "net/url"
        "testing"
+       "time"
 )
 
 import (
@@ -90,7 +92,7 @@ func TestServer_RegisterMuxHandle(t *testing.T) {
                },
        })
        for _, test := range tests {
-               err := srv.RegisterUnaryHandler(test.path, nil, nil)
+               err := test.registerFunc(srv, test.path)
                require.NoError(t, err)
                _, pattern := srv.mux.Handler(&http.Request{
                        URL: &url.URL{
@@ -101,6 +103,80 @@ func TestServer_RegisterMuxHandle(t *testing.T) {
        }
 }
 
+func TestNewQUICConfig(t *testing.T) {
+       t.Run("defaults_preserved_when_unset", func(t *testing.T) {
+               quicConfig, err := newQUICConfig(&global.Http3Config{})
+               require.NoError(t, err)
+               require.NotNil(t, quicConfig)
+               assert.Zero(t, quicConfig.KeepAlivePeriod)
+               assert.Zero(t, quicConfig.MaxIdleTimeout)
+               assert.Zero(t, quicConfig.MaxIncomingStreams)
+               assert.Zero(t, quicConfig.MaxIncomingUniStreams)
+       })
+
+       t.Run("explicit_fields_are_mapped", func(t *testing.T) {
+               quicConfig, err := newQUICConfig(&global.Http3Config{
+                       KeepAlivePeriod:       "15s",
+                       MaxIdleTimeout:        "30s",
+                       MaxIncomingStreams:    128,
+                       MaxIncomingUniStreams: 64,
+               })
+               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, int64(128), quicConfig.MaxIncomingStreams)
+               assert.Equal(t, int64(64), quicConfig.MaxIncomingUniStreams)
+       })
+
+       t.Run("invalid_keep_alive_period_returns_error", func(t *testing.T) {
+               quicConfig, err := newQUICConfig(&global.Http3Config{
+                       KeepAlivePeriod: "invalid",
+               })
+               require.Error(t, err)
+               assert.Nil(t, quicConfig)
+               assert.ErrorContains(t, err, "keep-alive-period")
+       })
+
+       t.Run("invalid_max_idle_timeout_returns_error", func(t *testing.T) {
+               quicConfig, err := newQUICConfig(&global.Http3Config{
+                       MaxIdleTimeout: "invalid",
+               })
+               require.Error(t, err)
+               assert.Nil(t, quicConfig)
+               assert.ErrorContains(t, err, "max-idle-timeout")
+       })
+}
+
+func TestServer_HTTP3PathsUseQUICConfigHelper(t *testing.T) {
+       t.Run("start_http3_returns_parse_error", func(t *testing.T) {
+               srv := NewServer("127.0.0.1:0", &global.TripleConfig{
+                       Http3: &global.Http3Config{
+                               KeepAlivePeriod: "invalid",
+                       },
+               })
+
+               err := srv.startHttp3(&tls.Config{})
+               require.Error(t, err)
+               require.ErrorContains(t, err, "keep-alive-period")
+               assert.Nil(t, srv.http3Srv)
+       })
+
+       t.Run("start_http2_and_http3_returns_parse_error", func(t *testing.T) {
+               srv := NewServer("127.0.0.1:0", &global.TripleConfig{
+                       Http3: &global.Http3Config{
+                               MaxIdleTimeout: "invalid",
+                       },
+               })
+
+               err := srv.startHttp2AndHttp3(&tls.Config{})
+               require.Error(t, err)
+               require.ErrorContains(t, err, "max-idle-timeout")
+               assert.Nil(t, srv.http3Srv)
+               assert.Nil(t, srv.httpSrv)
+       })
+}
+
 func TestServerSetFallbackHTTPHandler(t *testing.T) {
        srv := NewServer("127.0.0.1:0", nil)
        srv.SetFallbackHTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, 
r *http.Request) {

Reply via email to