// Package backend — shared HTTP transport pool. // // SharedTransport returns a per-host *http.Transport that all backend instances // targeting the same base URL share. This enables HTTP/2 multiplexing: multiple // concurrent ChatStream calls share a single TLS connection to the same host. package backend import ( "crypto/tls" "net" "net/http" "sync" "time" ) var ( transportsMu sync.Mutex transports = make(map[string]*http.Transport) ) // SharedTransport returns a shared *http.Transport for the given host key. // The key should be "backend:baseURL" (e.g. "anthropic:https://api.anthropic.com"). // All callers sharing the same key share the same underlying TCP+TLS connections. func SharedTransport(key string) *http.Transport { transportsMu.Lock() defer transportsMu.Unlock() if t, ok := transports[key]; ok { return t } t := &http.Transport{ ForceAttemptHTTP2: true, MaxIdleConnsPerHost: 4, IdleConnTimeout: 5 * time.Minute, TLSHandshakeTimeout: 10 * time.Second, // No ResponseHeaderTimeout — streaming responses have no header deadline. DialContext: (&net.Dialer{ Timeout: 10 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, TLSClientConfig: &tls.Config{ MinVersion: tls.VersionTLS12, }, } transports[key] = t return t } // SharedClient returns an *http.Client backed by the shared transport for key. func SharedClient(key string) *http.Client { return &http.Client{Transport: SharedTransport(key)} } // PoolStats returns the number of active transport entries. func PoolStats() int { transportsMu.Lock() defer transportsMu.Unlock() return len(transports) } // ClosePool closes all idle connections in all transports and clears the pool. func ClosePool() { transportsMu.Lock() defer transportsMu.Unlock() for _, t := range transports { t.CloseIdleConnections() } transports = make(map[string]*http.Transport) }