Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines

This commit is contained in:
世界
2026-04-14 22:59:46 +08:00
parent 3c770c95e0
commit 85defdb55d
113 changed files with 13622 additions and 8053 deletions
+8 -5
View File
@@ -33,6 +33,7 @@ type Router struct {
dnsTransport adapter.DNSTransportManager
connection adapter.ConnectionManager
network adapter.NetworkManager
httpClientManager adapter.HTTPClientManager
rules []adapter.Rule
needFindProcess bool
needFindNeighbor bool
@@ -58,6 +59,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
dnsTransport: service.FromContext[adapter.DNSTransportManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
network: service.FromContext[adapter.NetworkManager](ctx),
httpClientManager: service.FromContext[adapter.HTTPClientManager](ctx),
rules: make([]adapter.Rule, 0, len(options.Rules)),
ruleSetMap: make(map[string]adapter.RuleSet),
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
@@ -98,15 +100,15 @@ func (r *Router) Start(stage adapter.StartStage) error {
monitor := taskmonitor.New(r.logger, C.StartTimeout)
switch stage {
case adapter.StartStateStart:
var cacheContext *adapter.HTTPStartContext
var startContext *adapter.HTTPStartContext
if len(r.ruleSets) > 0 {
monitor.Start("initialize rule-set")
cacheContext = adapter.NewHTTPStartContext(r.ctx)
startContext = adapter.NewHTTPStartContext()
var ruleSetStartGroup task.Group
for i, ruleSet := range r.ruleSets {
ruleSetInPlace := ruleSet
ruleSetStartGroup.Append0(func(ctx context.Context) error {
err := ruleSetInPlace.StartContext(ctx, cacheContext)
err := ruleSetInPlace.StartContext(ctx, startContext)
if err != nil {
return E.Cause(err, "initialize rule-set[", i, "]")
}
@@ -121,8 +123,8 @@ func (r *Router) Start(stage adapter.StartStage) error {
return err
}
}
if cacheContext != nil {
cacheContext.Close()
if startContext != nil {
startContext.Close()
}
r.network.Initialize(r.ruleSets)
needFindProcess := r.needFindProcess
@@ -280,5 +282,6 @@ func (r *Router) NeighborResolver() adapter.NeighborResolver {
func (r *Router) ResetNetwork() {
r.network.ResetNetwork()
r.httpClientManager.ResetNetwork()
r.dns.ResetNetwork()
}
+1 -1
View File
@@ -19,7 +19,7 @@ func NewRuleSet(ctx context.Context, logger logger.ContextLogger, options option
case C.RuleSetTypeInline, C.RuleSetTypeLocal, "":
return NewLocalRuleSet(ctx, logger, options)
case C.RuleSetTypeRemote:
return NewRemoteRuleSet(ctx, logger, options), nil
return NewRemoteRuleSet(ctx, logger, options)
default:
return nil, E.New("unknown rule-set type: ", options.Type)
}
+42 -50
View File
@@ -3,9 +3,7 @@ package rule
import (
"bytes"
"context"
"crypto/tls"
"io"
"net"
"net/http"
"runtime"
"strings"
@@ -16,15 +14,13 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
@@ -41,7 +37,7 @@ type RemoteRuleSet struct {
outbound adapter.OutboundManager
options option.RuleSet
updateInterval time.Duration
dialer N.Dialer
httpClient *http.Client
access sync.RWMutex
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
@@ -54,7 +50,7 @@ type RemoteRuleSet struct {
refs atomic.Int32
}
func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) (*RemoteRuleSet, error) {
ctx, cancel := context.WithCancel(ctx)
var updateInterval time.Duration
if options.RemoteOptions.UpdateInterval > 0 {
@@ -70,7 +66,7 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options
options: options,
updateInterval: updateInterval,
pauseManager: service.FromContext[pause.Manager](ctx),
}
}, nil
}
func (s *RemoteRuleSet) Name() string {
@@ -83,20 +79,15 @@ func (s *RemoteRuleSet) String() string {
func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error {
s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx)
var dialer N.Dialer
if s.options.RemoteOptions.DownloadDetour != "" {
outbound, loaded := s.outbound.Outbound(s.options.RemoteOptions.DownloadDetour)
if !loaded {
return E.New("download detour not found: ", s.options.RemoteOptions.DownloadDetour)
}
dialer = outbound
} else {
dialer = s.outbound.Default()
transport, err := s.resolveTransport()
if err != nil {
return E.Cause(err, "create rule-set http client")
}
s.dialer = dialer
startContext.Register(transport)
s.httpClient = &http.Client{Transport: transport}
if s.cacheFile != nil {
if savedSet := s.cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil {
err := s.loadBytes(savedSet.Content)
err = s.loadBytes(savedSet.Content)
if err != nil {
return E.Cause(err, "restore cached rule-set")
}
@@ -105,7 +96,7 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
}
}
if s.lastUpdated.IsZero() {
err := s.fetch(ctx, startContext)
err = s.fetch(ctx, true)
if err != nil {
return E.Cause(err, "initial rule-set: ", s.options.Tag)
}
@@ -207,12 +198,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
func (s *RemoteRuleSet) loopUpdate() {
if time.Since(s.lastUpdated) > s.updateInterval {
err := s.fetch(s.ctx, nil)
if err != nil {
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
} else if s.refs.Load() == 0 {
s.rules = nil
}
s.updateOnce()
}
for {
runtime.GC()
@@ -226,7 +212,7 @@ func (s *RemoteRuleSet) loopUpdate() {
}
func (s *RemoteRuleSet) updateOnce() {
err := s.fetch(s.ctx, nil)
err := s.fetch(s.ctx, false)
if err != nil {
s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
} else if s.refs.Load() == 0 {
@@ -234,26 +220,8 @@ func (s *RemoteRuleSet) updateOnce() {
}
}
func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPStartContext) error {
func (s *RemoteRuleSet) fetch(ctx context.Context, isStart bool) error {
s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL)
var httpClient *http.Client
if startContext != nil {
httpClient = startContext.HTTPClient(s.options.RemoteOptions.DownloadDetour, s.dialer)
} else {
httpClient = &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
TLSHandshakeTimeout: C.TCPTimeout,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
TLSClientConfig: &tls.Config{
Time: ntp.TimeFuncFromContext(s.ctx),
RootCAs: adapter.RootPoolFromContext(s.ctx),
},
},
}
}
request, err := http.NewRequest("GET", s.options.RemoteOptions.URL, nil)
if err != nil {
return err
@@ -261,10 +229,14 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPSta
if s.lastEtag != "" {
request.Header.Set("If-None-Match", s.lastEtag)
}
response, err := httpClient.Do(request.WithContext(ctx))
if !isStart {
defer s.httpClient.CloseIdleConnections()
}
response, err := s.httpClient.Do(request.WithContext(ctx))
if err != nil {
return err
}
defer response.Body.Close()
switch response.StatusCode {
case http.StatusOK:
case http.StatusNotModified:
@@ -287,15 +259,12 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPSta
}
content, err := io.ReadAll(response.Body)
if err != nil {
response.Body.Close()
return err
}
err = s.loadBytes(content)
if err != nil {
response.Body.Close()
return err
}
response.Body.Close()
eTagHeader := response.Header.Get("Etag")
if eTagHeader != "" {
s.lastEtag = eTagHeader
@@ -315,6 +284,29 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, startContext *adapter.HTTPSta
return nil
}
func (s *RemoteRuleSet) resolveTransport() (adapter.HTTPTransport, error) {
httpClientManager := service.FromContext[adapter.HTTPClientManager](s.ctx)
if s.options.RemoteOptions.HTTPClient != nil && !s.options.RemoteOptions.HTTPClient.IsEmpty() {
if s.options.RemoteOptions.DownloadDetour != "" { //nolint:staticcheck
return nil, E.New("http_client is conflict with deprecated download_detour field")
}
return httpClientManager.ResolveTransport(s.ctx, s.logger, *s.options.RemoteOptions.HTTPClient)
}
if s.options.RemoteOptions.DownloadDetour != "" { //nolint:staticcheck
deprecated.Report(s.ctx, deprecated.OptionLegacyRuleSetDownloadDetour)
var httpClientOptions option.HTTPClientOptions
httpClientOptions.DialerOptions = option.DialerOptions{
Detour: s.options.RemoteOptions.DownloadDetour, //nolint:staticcheck
}
return httpClientManager.ResolveTransport(s.ctx, s.logger, httpClientOptions)
}
defaultTransport := httpClientManager.DefaultTransport()
if defaultTransport == nil {
return nil, E.New("default http client transport is not initialized")
}
return defaultTransport, nil
}
func (s *RemoteRuleSet) Close() error {
s.rules = nil
s.cancel()