1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2025-08-06 17:40:57 +00:00
forgejo/modules/httplib/client_pool.go
2025-06-25 22:33:30 +02:00

150 lines
4.2 KiB
Go

// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package httplib
import (
"crypto/tls"
"net"
"net/http"
"sync"
"time"
"forgejo.org/modules/proxy"
"forgejo.org/modules/setting"
)
// ClientPool manages HTTP clients with connection pooling
type ClientPool struct {
clients map[string]*http.Client
mutex sync.RWMutex
}
var (
globalClientPool *ClientPool
once sync.Once
)
// GetGlobalClientPool returns the global HTTP client pool
func GetGlobalClientPool() *ClientPool {
once.Do(func() {
globalClientPool = &ClientPool{
clients: make(map[string]*http.Client),
}
})
return globalClientPool
}
// GetClient returns an HTTP client for the given configuration key
func (cp *ClientPool) GetClient(key string) *http.Client {
cp.mutex.RLock()
if client, exists := cp.clients[key]; exists {
cp.mutex.RUnlock()
return client
}
cp.mutex.RUnlock()
cp.mutex.Lock()
defer cp.mutex.Unlock()
// Double-check after acquiring write lock
if client, exists := cp.clients[key]; exists {
return client
}
client := cp.createClient(key)
cp.clients[key] = client
return client
}
// createClient creates a new HTTP client with optimized connection pooling
func (cp *ClientPool) createClient(key string) *http.Client {
transport := &http.Transport{
Proxy: proxy.Proxy(),
DialContext: (&net.Dialer{
Timeout: setting.HTTPClient.DialTimeout,
KeepAlive: setting.HTTPClient.KeepAlive,
}).DialContext,
ForceAttemptHTTP2: setting.HTTPClient.ForceHTTP2,
MaxIdleConns: setting.HTTPClient.MaxIdleConns,
MaxIdleConnsPerHost: setting.HTTPClient.MaxIdleConnsPerHost,
IdleConnTimeout: setting.HTTPClient.IdleConnTimeout,
TLSHandshakeTimeout: setting.HTTPClient.TLSHandshakeTimeout,
ExpectContinueTimeout: setting.HTTPClient.ExpectContinueTimeout,
// Enable connection pooling
DisableKeepAlives: false,
}
return &http.Client{
Transport: transport,
Timeout: setting.HTTPClient.DefaultTimeout,
}
}
// GetClientWithTimeout returns an HTTP client with custom timeout
func (cp *ClientPool) GetClientWithTimeout(key string, timeout time.Duration) *http.Client {
client := cp.GetClient(key)
// Create a copy with custom timeout
return &http.Client{
Transport: client.Transport,
Timeout: timeout,
}
}
// GetClientWithTLS returns an HTTP client with custom TLS configuration
func (cp *ClientPool) GetClientWithTLS(key string, tlsConfig *tls.Config) *http.Client {
baseClient := cp.GetClient(key)
baseTransport := baseClient.Transport.(*http.Transport)
// Create a new transport with custom TLS config
transport := &http.Transport{
Proxy: baseTransport.Proxy,
DialContext: baseTransport.DialContext,
ForceAttemptHTTP2: baseTransport.ForceAttemptHTTP2,
MaxIdleConns: baseTransport.MaxIdleConns,
MaxIdleConnsPerHost: baseTransport.MaxIdleConnsPerHost,
IdleConnTimeout: baseTransport.IdleConnTimeout,
TLSHandshakeTimeout: baseTransport.TLSHandshakeTimeout,
ExpectContinueTimeout: baseTransport.ExpectContinueTimeout,
DisableKeepAlives: baseTransport.DisableKeepAlives,
TLSClientConfig: tlsConfig,
}
return &http.Client{
Transport: transport,
Timeout: baseClient.Timeout,
}
}
// Close closes all clients in the pool
func (cp *ClientPool) Close() {
cp.mutex.Lock()
defer cp.mutex.Unlock()
for _, client := range cp.clients {
client.CloseIdleConnections()
}
cp.clients = make(map[string]*http.Client)
}
// GetDefaultClient returns the default HTTP client
func GetDefaultClient() *http.Client {
return GetGlobalClientPool().GetClient("default")
}
// GetWebhookClient returns an HTTP client optimized for webhook delivery
func GetWebhookClient() *http.Client {
pool := GetGlobalClientPool()
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
return pool.GetClientWithTimeout("webhook", timeout)
}
// GetLFSClient returns an HTTP client optimized for LFS operations
func GetLFSClient() *http.Client {
return GetGlobalClientPool().GetClient("lfs")
}
// GetMigrationClient returns an HTTP client for repository migrations
func GetMigrationClient() *http.Client {
return GetGlobalClientPool().GetClient("migration")
}