Files
Archive/sing-box/common/httpclient/manager.go
T
2026-04-17 21:15:10 +02:00

180 lines
4.8 KiB
Go

package httpclient
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
var (
_ adapter.HTTPClientManager = (*Manager)(nil)
_ adapter.LifecycleService = (*Manager)(nil)
)
type Manager struct {
ctx context.Context
logger log.ContextLogger
access sync.Mutex
defines map[string]option.HTTPClient
sharedTransports map[string]*sharedManagedTransport
managedTransports []*ManagedTransport
defaultTag string
defaultTransport *sharedManagedTransport
defaultTransportFallback func() (*ManagedTransport, error)
}
type sharedManagedTransport struct {
managed *ManagedTransport
shared *sharedState
}
func NewManager(ctx context.Context, logger log.ContextLogger, clients []option.HTTPClient, defaultHTTPClient string) *Manager {
defines := make(map[string]option.HTTPClient, len(clients))
for _, client := range clients {
defines[client.Tag] = client
}
defaultTag := defaultHTTPClient
if defaultTag == "" && len(clients) > 0 {
defaultTag = clients[0].Tag
}
return &Manager{
ctx: ctx,
logger: logger,
defines: defines,
sharedTransports: make(map[string]*sharedManagedTransport),
defaultTag: defaultTag,
}
}
func (m *Manager) Initialize(defaultTransportFallback func() (*ManagedTransport, error)) {
m.defaultTransportFallback = defaultTransportFallback
}
func (m *Manager) Name() string {
return "http-client"
}
func (m *Manager) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
if m.defaultTag != "" {
sharedTransport, err := m.resolveShared(m.defaultTag)
if err != nil {
return E.Cause(err, "resolve default http client")
}
m.defaultTransport = sharedTransport
}
return nil
}
func (m *Manager) DefaultTransport() adapter.HTTPTransport {
m.access.Lock()
defer m.access.Unlock()
if m.defaultTransport == nil && m.defaultTransportFallback != nil {
transport, err := m.defaultTransportFallback()
if err != nil {
m.logger.Error(E.Cause(err, "create default http client"))
return nil
}
m.managedTransports = append(m.managedTransports, transport)
m.defaultTransport = &sharedManagedTransport{
managed: transport,
shared: &sharedState{},
}
}
if m.defaultTransport == nil {
return nil
}
return newSharedRef(m.defaultTransport.managed, m.defaultTransport.shared)
}
func (m *Manager) ResolveTransport(ctx context.Context, logger logger.ContextLogger, options option.HTTPClientOptions) (adapter.HTTPTransport, error) {
if options.Tag != "" {
if options.ResolveOnDetour {
define, loaded := m.defines[options.Tag]
if !loaded {
return nil, E.New("http_client not found: ", options.Tag)
}
resolvedOptions := define.Options()
resolvedOptions.ResolveOnDetour = true
transport, err := NewTransport(ctx, logger, options.Tag, resolvedOptions)
if err != nil {
return nil, err
}
m.trackTransport(transport)
return transport, nil
}
sharedTransport, err := m.resolveShared(options.Tag)
if err != nil {
return nil, err
}
return newSharedRef(sharedTransport.managed, sharedTransport.shared), nil
}
transport, err := NewTransport(ctx, logger, "", options)
if err != nil {
return nil, err
}
m.trackTransport(transport)
return transport, nil
}
func (m *Manager) trackTransport(transport *ManagedTransport) {
m.access.Lock()
defer m.access.Unlock()
m.managedTransports = append(m.managedTransports, transport)
}
func (m *Manager) resolveShared(tag string) (*sharedManagedTransport, error) {
m.access.Lock()
defer m.access.Unlock()
if sharedTransport, loaded := m.sharedTransports[tag]; loaded {
return sharedTransport, nil
}
define, loaded := m.defines[tag]
if !loaded {
return nil, E.New("http_client not found: ", tag)
}
transport, err := NewTransport(m.ctx, m.logger, tag, define.Options())
if err != nil {
return nil, E.Cause(err, "create shared http_client[", tag, "]")
}
sharedTransport := &sharedManagedTransport{
managed: transport,
shared: &sharedState{},
}
m.sharedTransports[tag] = sharedTransport
m.managedTransports = append(m.managedTransports, transport)
return sharedTransport, nil
}
func (m *Manager) ResetNetwork() {
m.access.Lock()
defer m.access.Unlock()
for _, transport := range m.managedTransports {
transport.Reset()
}
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if m.managedTransports == nil {
return nil
}
var err error
for _, transport := range m.managedTransports {
err = E.Append(err, transport.close(), func(err error) error {
return E.Cause(err, "close http client")
})
}
m.managedTransports = nil
m.sharedTransports = nil
return err
}