This is an automated email from the ASF dual-hosted git repository.

alexstocks 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 806bd6634 fix(protocol/triple): use context-aware dialer in HTTP/2 
transport (#3165)
806bd6634 is described below

commit 806bd66349cd6fbfbc07a23378371590f3e12da9
Author: 陈钢阳 <[email protected]>
AuthorDate: Tue Jan 27 16:18:13 2026 +0800

    fix(protocol/triple): use context-aware dialer in HTTP/2 transport (#3165)
    
    This fixes a bug where the Triple client's HTTP/2 transport wasn't
    respecting context cancellation and timeouts. The non-TLS path was
    using net.Dial instead of DialContext, and the TLS path wasn't
    explicitly using a context-aware dialer.
    
    Changes:
    - Use (&net.Dialer{}).DialContext for non-TLS HTTP/2 connections
    - Use (&tls.Dialer{Config: tlsConfig}).DialContext for TLS connections
    - Add test cases to verify timeout behavior with both TLS and non-TLS
    
    The test uses a black-hole address (RFC 5737) to ensure connections
    time out as expected without relying on external network resources.
---
 protocol/triple/client.go               |   7 +-
 protocol/triple/client_external_test.go | 214 ++++++++++++++++++++++++++++++++
 2 files changed, 219 insertions(+), 2 deletions(-)

diff --git a/protocol/triple/client.go b/protocol/triple/client.go
index a0e7c124a..b235ee4c0 100644
--- a/protocol/triple/client.go
+++ b/protocol/triple/client.go
@@ -203,11 +203,14 @@ func newClientManager(url *common.URL) (*clientManager, 
error) {
                                TLSClientConfig: cfg,
                                ReadIdleTimeout: keepAliveInterval,
                                PingTimeout:     keepAliveTimeout,
+                               DialTLSContext: func(ctx context.Context, 
network, addr string, tlsConfig *tls.Config) (net.Conn, error) {
+                                       return (&tls.Dialer{Config: 
tlsConfig}).DialContext(ctx, network, addr)
+                               },
                        }
                } else {
                        transport = &http2.Transport{
-                               DialTLSContext: func(_ context.Context, 
network, addr string, _ *tls.Config) (net.Conn, error) {
-                                       return net.Dial(network, addr)
+                               DialTLSContext: func(ctx context.Context, 
network, addr string, _ *tls.Config) (net.Conn, error) {
+                                       return (&net.Dialer{}).DialContext(ctx, 
network, addr)
                                },
                                AllowHTTP:       true,
                                ReadIdleTimeout: keepAliveInterval,
diff --git a/protocol/triple/client_external_test.go 
b/protocol/triple/client_external_test.go
new file mode 100644
index 000000000..049b3b041
--- /dev/null
+++ b/protocol/triple/client_external_test.go
@@ -0,0 +1,214 @@
+/*
+ * 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_test
+
+import (
+       "context"
+       "crypto/rand"
+       "crypto/rsa"
+       "crypto/x509"
+       "crypto/x509/pkix"
+       "encoding/pem"
+       "math/big"
+       "os"
+       "path/filepath"
+       "testing"
+       "time"
+)
+
+import (
+       "github.com/pkg/errors"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/global"
+       "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+       "dubbo.apache.org/dubbo-go/v3/protocol/triple"
+)
+
+// blackHoleAddress is an address that is reserved for documentation and 
should not be routable.
+// See RFC 5737. Connections to it should time out.
+const blackHoleAddress = "192.0.2.1:8080"
+
+// generateTestCerts generates self-signed CA and server certificates for 
testing.
+// Returns paths to the generated certificate files and a cleanup function.
+func generateTestCerts(t *testing.T) (caCertFile, serverCertFile, 
serverKeyFile string, cleanup func()) {
+       tempDir, err := os.MkdirTemp("", "triple_test")
+       require.NoError(t, err)
+
+       cleanup = func() {
+               os.RemoveAll(tempDir)
+       }
+
+       // Generate CA key pair
+       caKey, err := rsa.GenerateKey(rand.Reader, 2048)
+       require.NoError(t, err)
+
+       caTemplate := &x509.Certificate{
+               SerialNumber: big.NewInt(1),
+               Subject: pkix.Name{
+                       Organization: []string{"Test CA"},
+                       CommonName:   "Test CA",
+               },
+               NotBefore:             time.Now(),
+               NotAfter:              time.Now().Add(24 * time.Hour),
+               KeyUsage:              x509.KeyUsageCertSign | 
x509.KeyUsageCRLSign,
+               BasicConstraintsValid: true,
+               IsCA:                  true,
+       }
+
+       caCertDER, err := x509.CreateCertificate(rand.Reader, caTemplate, 
caTemplate, &caKey.PublicKey, caKey)
+       require.NoError(t, err)
+
+       caCertFile = filepath.Join(tempDir, "ca.crt")
+       err = writePEMFile(caCertFile, "CERTIFICATE", caCertDER)
+       require.NoError(t, err)
+
+       caCert, err := x509.ParseCertificate(caCertDER)
+       require.NoError(t, err)
+
+       // Generate server key pair
+       serverKey, err := rsa.GenerateKey(rand.Reader, 2048)
+       require.NoError(t, err)
+
+       serverTemplate := &x509.Certificate{
+               SerialNumber: big.NewInt(2),
+               Subject: pkix.Name{
+                       Organization: []string{"Test Server"},
+                       CommonName:   "localhost",
+               },
+               NotBefore:   time.Now(),
+               NotAfter:    time.Now().Add(24 * time.Hour),
+               KeyUsage:    x509.KeyUsageDigitalSignature | 
x509.KeyUsageKeyEncipherment,
+               ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+               DNSNames:    []string{"localhost"},
+       }
+
+       serverCertDER, err := x509.CreateCertificate(rand.Reader, 
serverTemplate, caCert, &serverKey.PublicKey, caKey)
+       require.NoError(t, err)
+
+       serverCertFile = filepath.Join(tempDir, "server.crt")
+       err = writePEMFile(serverCertFile, "CERTIFICATE", serverCertDER)
+       require.NoError(t, err)
+
+       serverKeyFile = filepath.Join(tempDir, "server.key")
+       err = writePEMFile(serverKeyFile, "RSA PRIVATE KEY", 
x509.MarshalPKCS1PrivateKey(serverKey))
+       require.NoError(t, err)
+
+       return caCertFile, serverCertFile, serverKeyFile, cleanup
+}
+
+// writePEMFile writes data to a PEM file
+func writePEMFile(filename, blockType string, data []byte) error {
+       file, err := os.Create(filename)
+       if err != nil {
+               return err
+       }
+       defer file.Close()
+
+       return pem.Encode(file, &pem.Block{
+               Type:  blockType,
+               Bytes: data,
+       })
+}
+
+// testClientInvokeWithTimeout tests that a Triple client invocation correctly 
times out.
+func testClientInvokeWithTimeout(t *testing.T, tlsConfig *global.TLSConfig) {
+
+       // 1. Get an instance of TripleProtocol
+       proto := triple.GetProtocol()
+
+       // 2. Create a URL that will time out
+       url, err := common.NewURL(
+               "tri://"+blackHoleAddress,
+               common.WithMethods([]string{"test"}),
+               common.WithProtocol(triple.TRIPLE),
+               common.WithParamsValue(constant.IDLMode, constant.NONIDL),
+               common.WithParamsValue(constant.SerializationKey, 
constant.MsgpackSerialization),
+       )
+       require.NoError(t, err)
+
+       if tlsConfig != nil {
+               url.SetAttribute(constant.TLSConfigKey, tlsConfig)
+               url.SetParam(constant.SslEnabledKey, "true")
+       }
+
+       // 3. Get an invoker, which will be a *TripleClient
+       invoker := proto.Refer(url)
+       require.NotNil(t, invoker)
+
+       // 4. Create a dummy invocation with raw parameters
+       inv := invocation.NewRPCInvocationWithOptions(
+               invocation.WithMethodName("test"),
+               invocation.WithParameterRawValues([]any{"param1", "param2"}),
+       )
+       inv.SetAttribute(constant.CallTypeKey, constant.CallUnary)
+
+       // 5. Create a context with a short timeout
+       timeout := 2 * time.Second
+       ctx, cancel := context.WithTimeout(context.Background(), timeout)
+       defer cancel()
+
+       // 6. Record start time and invoke. This should trigger a dial attempt.
+       startTime := time.Now()
+       result := invoker.Invoke(ctx, inv)
+       duration := time.Since(startTime)
+
+       err = result.Error()
+
+       t.Logf("invoke result err: %+v, duration: %v", err, duration)
+
+       require.Error(t, err)
+       assert.Truef(t, errors.Is(err, context.DeadlineExceeded), "context 
deadline exceeded")
+
+       // 7. Ensure that the duration is at least the timeout duration
+       assert.GreaterOrEqual(t, duration, timeout)
+       assert.Less(t, duration, timeout+time.Second, "Invocation took too 
long, likely not timing out correctly")
+}
+
+// TestClientInvokeWithTimeout tests that a Triple client invocation correctly 
times out.
+// Test certificates are dynamically generated at runtime to avoid committing 
private keys to the repository.
+func TestClientInvokeWithTimeout(t *testing.T) {
+       t.Run(
+               "without TLS", func(t *testing.T) {
+                       testClientInvokeWithTimeout(t, nil)
+               },
+       )
+
+       t.Run(
+               "with TLS", func(t *testing.T) {
+                       // Generate test certificates dynamically
+                       caCertFile, serverCertFile, serverKeyFile, cleanup := 
generateTestCerts(t)
+                       defer cleanup()
+
+                       // Set TLS configuration with generated cert files
+                       tlsConfig := &global.TLSConfig{
+                               CACertFile:  caCertFile,
+                               TLSCertFile: serverCertFile,
+                               TLSKeyFile:  serverKeyFile,
+                       }
+
+                       testClientInvokeWithTimeout(t, tlsConfig)
+               },
+       )
+}

Reply via email to