From ccf02e1a2a4c2ddfd3047ce103ffbd3f9039b601 Mon Sep 17 00:00:00 2001 From: zhangzherui Date: Wed, 22 Jul 2026 23:14:13 +0800 Subject: [PATCH] fix(embedding): reuse SSRF-safe HTTP transport --- internal/models/embedding/transport.go | 12 ++++++++++- internal/models/embedding/transport_test.go | 24 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/internal/models/embedding/transport.go b/internal/models/embedding/transport.go index 1c3ae791f..bfe598aa6 100644 --- a/internal/models/embedding/transport.go +++ b/internal/models/embedding/transport.go @@ -8,6 +8,14 @@ import ( secutils "github.com/Tencent/WeKnora/internal/utils" ) +// sharedEmbeddingHTTPTransport keeps a single SSRF-safe connection pool for +// all embedding clients. Embedders are recreated as model configuration changes, +// but their outbound connections can be safely reused across client instances. +var sharedEmbeddingHTTPTransport = func() http.RoundTripper { + cfg := secutils.DefaultSSRFSafeHTTPClientConfig() + return secutils.NewSSRFSafeHTTPClient(cfg).Transport +}() + // validateEmbeddingBaseURL checks that a resolved embedding API base URL is safe // for outbound requests. Empty URLs are allowed (callers apply provider defaults). func validateEmbeddingBaseURL(baseURL string) error { @@ -25,5 +33,7 @@ func validateEmbeddingBaseURL(baseURL string) error { func newEmbeddingHTTPClient(timeout time.Duration) *http.Client { cfg := secutils.DefaultSSRFSafeHTTPClientConfig() cfg.Timeout = timeout - return secutils.NewSSRFSafeHTTPClient(cfg) + client := secutils.NewSSRFSafeHTTPClient(cfg) + client.Transport = sharedEmbeddingHTTPTransport + return client } diff --git a/internal/models/embedding/transport_test.go b/internal/models/embedding/transport_test.go index af2db66be..551406a63 100644 --- a/internal/models/embedding/transport_test.go +++ b/internal/models/embedding/transport_test.go @@ -3,8 +3,32 @@ package embedding import ( "strings" "testing" + "time" ) +func TestNewEmbeddingHTTPClient_ReusesTransport(t *testing.T) { + firstTimeout := 15 * time.Second + secondTimeout := 45 * time.Second + first := newEmbeddingHTTPClient(firstTimeout) + second := newEmbeddingHTTPClient(secondTimeout) + + if first == second { + t.Fatal("expected distinct HTTP clients") + } + if first.Transport != second.Transport { + t.Fatal("expected embedding HTTP clients to share a transport") + } + if first.Transport != sharedEmbeddingHTTPTransport { + t.Fatal("expected embedding HTTP client to use the shared transport") + } + if first.Timeout != firstTimeout { + t.Fatalf("unexpected first client timeout: got %v, want %v", first.Timeout, firstTimeout) + } + if second.Timeout != secondTimeout { + t.Fatalf("unexpected second client timeout: got %v, want %v", second.Timeout, secondTimeout) + } +} + func TestValidateEmbeddingBaseURL_RejectsLoopback(t *testing.T) { err := validateEmbeddingBaseURL("http://169.254.169.254/latest/meta-data") if err == nil {