mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
137 lines
3.6 KiB
Go
137 lines
3.6 KiB
Go
package httpclient
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"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
|
|
clients map[string]*Client
|
|
defaultTag string
|
|
defaultTransport http.RoundTripper
|
|
defaultTransportFallback func() (*Client, error)
|
|
fallbackClient *Client
|
|
}
|
|
|
|
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,
|
|
clients: make(map[string]*Client),
|
|
defaultTag: defaultTag,
|
|
}
|
|
}
|
|
|
|
func (m *Manager) Initialize(defaultTransportFallback func() (*Client, 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 != "" {
|
|
transport, err := m.resolveShared(m.defaultTag)
|
|
if err != nil {
|
|
return E.Cause(err, "resolve default http client")
|
|
}
|
|
m.defaultTransport = transport
|
|
} else if m.defaultTransportFallback != nil {
|
|
client, err := m.defaultTransportFallback()
|
|
if err != nil {
|
|
return E.Cause(err, "create default http client")
|
|
}
|
|
m.defaultTransport = client
|
|
m.fallbackClient = client
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) DefaultTransport() http.RoundTripper {
|
|
return m.defaultTransport
|
|
}
|
|
|
|
func (m *Manager) ResolveTransport(logger logger.ContextLogger, options option.HTTPClientOptions) (http.RoundTripper, 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
|
|
return NewClient(m.ctx, logger, options.Tag, resolvedOptions)
|
|
}
|
|
return m.resolveShared(options.Tag)
|
|
}
|
|
return NewClient(m.ctx, logger, "", options)
|
|
}
|
|
|
|
func (m *Manager) resolveShared(tag string) (http.RoundTripper, error) {
|
|
m.access.Lock()
|
|
defer m.access.Unlock()
|
|
if client, loaded := m.clients[tag]; loaded {
|
|
return client, nil
|
|
}
|
|
define, loaded := m.defines[tag]
|
|
if !loaded {
|
|
return nil, E.New("http_client not found: ", tag)
|
|
}
|
|
client, err := NewClient(m.ctx, m.logger, tag, define.Options())
|
|
if err != nil {
|
|
return nil, E.Cause(err, "create shared http_client[", tag, "]")
|
|
}
|
|
m.clients[tag] = client
|
|
return client, nil
|
|
}
|
|
|
|
func (m *Manager) Close() error {
|
|
m.access.Lock()
|
|
defer m.access.Unlock()
|
|
if m.clients == nil {
|
|
return nil
|
|
}
|
|
var err error
|
|
for _, client := range m.clients {
|
|
err = E.Append(err, client.Close(), func(err error) error {
|
|
return E.Cause(err, "close http client")
|
|
})
|
|
}
|
|
if m.fallbackClient != nil {
|
|
err = E.Append(err, m.fallbackClient.Close(), func(err error) error {
|
|
return E.Cause(err, "close default http client")
|
|
})
|
|
}
|
|
m.clients = nil
|
|
return err
|
|
}
|