Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines
This commit is contained in:
+8
-5
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user