mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-18 17:12:47 +03:00
Add daemon support
This commit is contained in:
147
experimental/daemon/server.go
Normal file
147
experimental/daemon/server.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"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/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Listen string `json:"listen"`
|
||||
ListenPort uint16 `json:"listen_port"`
|
||||
Secret string `json:"secret"`
|
||||
WorkingDirectory string `json:"working_directory"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
options Options
|
||||
httpServer *http.Server
|
||||
instance Instance
|
||||
}
|
||||
|
||||
func NewServer(options Options) *Server {
|
||||
return &Server{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
tcpConn, err := net.Listen("tcp", net.JoinHostPort(s.options.Listen, F.ToString(s.options.ListenPort)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router := chi.NewRouter()
|
||||
router.Use(cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||||
AllowedHeaders: []string{"Content-Type", "Authorization"},
|
||||
MaxAge: 300,
|
||||
}).Handler)
|
||||
if s.options.Secret != "" {
|
||||
router.Use(s.authentication)
|
||||
}
|
||||
router.Get("/ping", s.ping)
|
||||
router.Get("/status", s.status)
|
||||
router.Post("/run", s.run)
|
||||
router.Get("/stop", s.stop)
|
||||
router.Route("/debug/pprof", func(r chi.Router) {
|
||||
r.HandleFunc("/", pprof.Index)
|
||||
r.HandleFunc("/cmdline", pprof.Cmdline)
|
||||
r.HandleFunc("/profile", pprof.Profile)
|
||||
r.HandleFunc("/symbol", pprof.Symbol)
|
||||
r.HandleFunc("/trace", pprof.Trace)
|
||||
})
|
||||
httpServer := &http.Server{
|
||||
Handler: router,
|
||||
}
|
||||
go httpServer.Serve(tcpConn)
|
||||
s.httpServer = httpServer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) authentication(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if websocket.IsWebSocketUpgrade(request) && request.URL.Query().Get("token") != "" {
|
||||
token := request.URL.Query().Get("token")
|
||||
if token != s.options.Secret {
|
||||
render.Status(request, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(writer, request)
|
||||
return
|
||||
}
|
||||
header := request.Header.Get("Authorization")
|
||||
bearer, token, found := strings.Cut(header, " ")
|
||||
hasInvalidHeader := bearer != "Bearer"
|
||||
hasInvalidSecret := !found || token != s.options.Secret
|
||||
if hasInvalidHeader || hasInvalidSecret {
|
||||
render.Status(request, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(writer, request)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.httpServer),
|
||||
&s.instance,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Server) ping(writer http.ResponseWriter, request *http.Request) {
|
||||
render.PlainText(writer, request, "pong")
|
||||
}
|
||||
|
||||
type StatusResponse struct {
|
||||
Running bool `json:"running"`
|
||||
}
|
||||
|
||||
func (s *Server) status(writer http.ResponseWriter, request *http.Request) {
|
||||
render.JSON(writer, request, StatusResponse{
|
||||
Running: s.instance.Running(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) run(writer http.ResponseWriter, request *http.Request) {
|
||||
err := s.run0(request)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *Server) run0(request *http.Request) error {
|
||||
configContent, err := io.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read config")
|
||||
}
|
||||
var options option.Options
|
||||
err = json.Unmarshal(configContent, &options)
|
||||
if err != nil {
|
||||
return E.Cause(err, "decode config")
|
||||
}
|
||||
return s.instance.Start(options)
|
||||
}
|
||||
|
||||
func (s *Server) stop(writer http.ResponseWriter, request *http.Request) {
|
||||
s.instance.Close()
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user