mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-30 06:56:57 +00:00
Separate Clash mode management from Clash API server
This commit is contained in:
@@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -19,12 +18,12 @@ import (
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/clashmode"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/observable"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
"github.com/sagernet/ws"
|
||||
@@ -38,7 +37,7 @@ func init() {
|
||||
experimental.RegisterClashServerConstructor(NewServer)
|
||||
}
|
||||
|
||||
var _ adapter.ClashServer = (*Server)(nil)
|
||||
var _ adapter.LifecycleService = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
ctx context.Context
|
||||
@@ -51,20 +50,16 @@ type Server struct {
|
||||
httpServer *http.Server
|
||||
trafficManager *trafficcontrol.Manager
|
||||
urlTestHistory *urltest.HistoryStorage
|
||||
clashMode *clashmode.Manager
|
||||
logDebug bool
|
||||
|
||||
mode string
|
||||
modeList []string
|
||||
modeUpdateAccess sync.Mutex
|
||||
modeUpdateHooks []*observable.Subscriber[struct{}]
|
||||
|
||||
externalController bool
|
||||
externalUI string
|
||||
externalUIDownloadURL string
|
||||
externalUIDownloadDetour string
|
||||
}
|
||||
|
||||
func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||
func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.LifecycleService, error) {
|
||||
trafficManager := service.PtrFromContext[trafficcontrol.Manager](ctx)
|
||||
if trafficManager == nil {
|
||||
return nil, E.New("missing traffic manager")
|
||||
@@ -73,6 +68,10 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
||||
if urlTestHistory == nil {
|
||||
return nil, E.New("missing URL test history storage")
|
||||
}
|
||||
clashMode := service.PtrFromContext[clashmode.Manager](ctx)
|
||||
if clashMode == nil {
|
||||
return nil, E.New("missing clash mode manager")
|
||||
}
|
||||
chiRouter := chi.NewRouter()
|
||||
s := &Server{
|
||||
ctx: ctx,
|
||||
@@ -88,20 +87,12 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
||||
},
|
||||
trafficManager: trafficManager,
|
||||
urlTestHistory: urlTestHistory,
|
||||
clashMode: clashMode,
|
||||
logDebug: logFactory.Level() >= log.LevelDebug,
|
||||
modeList: options.ModeList,
|
||||
externalController: options.ExternalController != "",
|
||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
||||
}
|
||||
defaultMode := "Rule"
|
||||
if options.DefaultMode != "" {
|
||||
defaultMode = options.DefaultMode
|
||||
}
|
||||
if !common.Contains(s.modeList, defaultMode) {
|
||||
s.modeList = append([]string{defaultMode}, s.modeList...)
|
||||
}
|
||||
s.mode = defaultMode
|
||||
//goland:noinspection GoDeprecation
|
||||
//nolint:staticcheck
|
||||
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" {
|
||||
@@ -157,45 +148,34 @@ func (s *Server) Name() string {
|
||||
}
|
||||
|
||||
func (s *Server) Start(stage adapter.StartStage) error {
|
||||
switch stage {
|
||||
case adapter.StartStateStart:
|
||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||
if cacheFile != nil {
|
||||
mode := cacheFile.LoadMode()
|
||||
if common.Any(s.modeList, func(it string) bool {
|
||||
return strings.EqualFold(it, mode)
|
||||
}) {
|
||||
s.mode = mode
|
||||
}
|
||||
}
|
||||
case adapter.StartStateStarted:
|
||||
if s.externalController {
|
||||
s.checkAndDownloadExternalUI()
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
for range 3 {
|
||||
listener, err = net.Listen("tcp", s.httpServer.Addr)
|
||||
if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause(err, "external controller listen error")
|
||||
}
|
||||
s.logger.Info("restful api listening at ", listener.Addr())
|
||||
go func() {
|
||||
err = s.httpServer.Serve(listener)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Error("external controller serve error: ", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if stage != adapter.StartStateStarted {
|
||||
return nil
|
||||
}
|
||||
if s.externalController {
|
||||
s.checkAndDownloadExternalUI()
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
for range 3 {
|
||||
listener, err = net.Listen("tcp", s.httpServer.Addr)
|
||||
if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause(err, "external controller listen error")
|
||||
}
|
||||
s.logger.Info("restful api listening at ", listener.Addr())
|
||||
go func() {
|
||||
err = s.httpServer.Serve(listener)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Error("external controller serve error: ", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -205,49 +185,6 @@ func (s *Server) Close() error {
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) Mode() string {
|
||||
return s.mode
|
||||
}
|
||||
|
||||
func (s *Server) ModeList() []string {
|
||||
return s.modeList
|
||||
}
|
||||
|
||||
func (s *Server) AddModeUpdateHook(hook *observable.Subscriber[struct{}]) {
|
||||
s.modeUpdateAccess.Lock()
|
||||
defer s.modeUpdateAccess.Unlock()
|
||||
s.modeUpdateHooks = append(s.modeUpdateHooks, hook)
|
||||
}
|
||||
|
||||
func (s *Server) SetMode(newMode string) {
|
||||
if !common.Contains(s.modeList, newMode) {
|
||||
newMode = common.Find(s.modeList, func(it string) bool {
|
||||
return strings.EqualFold(it, newMode)
|
||||
})
|
||||
}
|
||||
if !common.Contains(s.modeList, newMode) {
|
||||
return
|
||||
}
|
||||
if newMode == s.mode {
|
||||
return
|
||||
}
|
||||
s.mode = newMode
|
||||
s.modeUpdateAccess.Lock()
|
||||
for _, hook := range s.modeUpdateHooks {
|
||||
hook.Emit(struct{}{})
|
||||
}
|
||||
s.modeUpdateAccess.Unlock()
|
||||
s.dnsRouter.ClearCache()
|
||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||
if cacheFile != nil {
|
||||
err := cacheFile.StoreMode(newMode)
|
||||
if err != nil {
|
||||
s.logger.Error(E.Cause(err, "save mode"))
|
||||
}
|
||||
}
|
||||
s.logger.Info("updated mode: ", newMode)
|
||||
}
|
||||
|
||||
func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user