perf: add HTTP connection pooling across providers and disable proxy buffering on SSE streams
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

This commit is contained in:
2026-07-21 17:40:18 +00:00
parent 0decc63e8c
commit 654f6ab6d1
9 changed files with 42 additions and 8 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ type DeepSeekProvider struct {
func NewDeepSeekProvider(cfg config.DeepSeekConfig, apiKey string) *DeepSeekProvider {
return &DeepSeekProvider{
client: resty.New().SetTimeout(10 * time.Minute),
client: NewOptimizedRestyClient(10 * time.Minute),
config: cfg,
apiKey: apiKey,
}
+1 -1
View File
@@ -21,7 +21,7 @@ type GeminiProvider struct {
func NewGeminiProvider(cfg config.GeminiConfig, apiKey string) *GeminiProvider {
return &GeminiProvider{
client: resty.New().SetTimeout(10 * time.Minute),
client: NewOptimizedRestyClient(10 * time.Minute),
config: cfg,
apiKey: apiKey,
}
+1 -1
View File
@@ -20,7 +20,7 @@ type GrokProvider struct {
func NewGrokProvider(cfg config.GrokConfig, apiKey string) *GrokProvider {
return &GrokProvider{
client: resty.New().SetTimeout(10 * time.Minute),
client: NewOptimizedRestyClient(10 * time.Minute),
config: cfg,
apiKey: apiKey,
}
+33
View File
@@ -5,10 +5,15 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"regexp"
"strings"
"time"
"gophergate/internal/models"
"github.com/go-resty/resty/v2"
)
var keySanitizeRegex = regexp.MustCompile(`(?i)(key|api_key|secret)=[^&]+`)
@@ -18,6 +23,34 @@ func SanitizeURL(rawURL string) string {
return keySanitizeRegex.ReplaceAllString(rawURL, "$1=REDACTED")
}
// Shared HTTP transport configured with high connection pooling, TCP keep-alive,
// and HTTP/2 multiplexing to minimize latency when connecting to upstream LLM providers.
var sharedHTTPTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 200,
MaxIdleConnsPerHost: 50,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
// NewOptimizedRestyClient creates a resty client equipped with HTTP connection pooling.
func NewOptimizedRestyClient(timeout time.Duration) *resty.Client {
httpClient := &http.Client{
Transport: sharedHTTPTransport,
}
client := resty.NewWithClient(httpClient)
if timeout > 0 {
client.SetTimeout(timeout)
}
return client
}
func sanitizeFunctionName(name string) string {
var sb strings.Builder
+1 -1
View File
@@ -21,7 +21,7 @@ type MoonshotProvider struct {
func NewMoonshotProvider(cfg config.MoonshotConfig, apiKey string) *MoonshotProvider {
return &MoonshotProvider{
client: resty.New().SetTimeout(10 * time.Minute),
client: NewOptimizedRestyClient(10 * time.Minute),
config: cfg,
apiKey: strings.TrimSpace(apiKey),
}
+1 -2
View File
@@ -20,10 +20,9 @@ type OllamaProvider struct {
}
func NewOllamaProvider(cfg config.OllamaConfig) *OllamaProvider {
client := resty.New()
client := NewOptimizedRestyClient(15 * time.Minute)
// Set reasonable timeouts for local Ollama server (longer for larger models)
// For streaming, we want a very long timeout or none at all to handle generation time
client.SetTimeout(15 * time.Minute)
client.SetRetryCount(2)
client.SetRetryWaitTime(1 * time.Second)
+1 -1
View File
@@ -22,7 +22,7 @@ type OpenAIProvider struct {
func NewOpenAIProvider(cfg config.OpenAIConfig, apiKey string) *OpenAIProvider {
return &OpenAIProvider{
client: resty.New().SetTimeout(10 * time.Minute),
client: NewOptimizedRestyClient(10 * time.Minute),
config: cfg,
apiKey: apiKey,
}
+1 -1
View File
@@ -21,7 +21,7 @@ type XiaomiProvider struct {
func NewXiaomiProvider(cfg config.XiaomiConfig, apiKey string) *XiaomiProvider {
return &XiaomiProvider{
client: resty.New().SetTimeout(10 * time.Minute),
client: NewOptimizedRestyClient(10 * time.Minute),
config: cfg,
apiKey: strings.TrimSpace(apiKey),
}
+2
View File
@@ -403,6 +403,7 @@ func (s *Server) handleResponses(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("X-Accel-Buffering", "no")
var lastUsage *models.ResponsesUsage
c.Stream(func(w io.Writer) bool {
@@ -765,6 +766,7 @@ func (s *Server) handleChatCompletions(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("X-Accel-Buffering", "no")
var lastUsage *models.Usage
c.Stream(func(w io.Writer) bool {