Add SSM API service
This commit is contained in:
181
service/ssmapi/api.go
Normal file
181
service/ssmapi/api.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package ssmapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
type APIServer struct {
|
||||
logger logger.Logger
|
||||
traffic *TrafficManager
|
||||
user *UserManager
|
||||
}
|
||||
|
||||
func NewAPIServer(logger logger.Logger, traffic *TrafficManager, user *UserManager) *APIServer {
|
||||
return &APIServer{
|
||||
logger: logger,
|
||||
traffic: traffic,
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIServer) Route(r chi.Router) {
|
||||
r.Route("/server/v1", func(r chi.Router) {
|
||||
r.Use(func(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
s.logger.Debug(request.Method, " ", request.RequestURI, " ", sHTTP.SourceAddress(request))
|
||||
handler.ServeHTTP(writer, request)
|
||||
})
|
||||
})
|
||||
r.Get("/", s.getServerInfo)
|
||||
r.Get("/users", s.listUser)
|
||||
r.Post("/users", s.addUser)
|
||||
r.Get("/users/{username}", s.getUser)
|
||||
r.Put("/users/{username}", s.updateUser)
|
||||
r.Delete("/users/{username}", s.deleteUser)
|
||||
r.Get("/stats", s.getStats)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *APIServer) getServerInfo(writer http.ResponseWriter, request *http.Request) {
|
||||
render.JSON(writer, request, render.M{
|
||||
"server": "sing-box " + C.Version,
|
||||
"apiVersion": "v1",
|
||||
})
|
||||
}
|
||||
|
||||
type UserObject struct {
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"uPSK,omitempty"`
|
||||
DownlinkBytes int64 `json:"downlinkBytes"`
|
||||
UplinkBytes int64 `json:"uplinkBytes"`
|
||||
DownlinkPackets int64 `json:"downlinkPackets"`
|
||||
UplinkPackets int64 `json:"uplinkPackets"`
|
||||
TCPSessions int64 `json:"tcpSessions"`
|
||||
UDPSessions int64 `json:"udpSessions"`
|
||||
}
|
||||
|
||||
func (s *APIServer) listUser(writer http.ResponseWriter, request *http.Request) {
|
||||
render.JSON(writer, request, render.M{
|
||||
"users": s.user.List(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *APIServer) addUser(writer http.ResponseWriter, request *http.Request) {
|
||||
var addRequest struct {
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"uPSK"`
|
||||
}
|
||||
err := render.DecodeJSON(request.Body, &addRequest)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
err = s.user.Add(addRequest.UserName, addRequest.Password)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
func (s *APIServer) getUser(writer http.ResponseWriter, request *http.Request) {
|
||||
userName := chi.URLParam(request, "username")
|
||||
if userName == "" {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
uPSK, loaded := s.user.Get(userName)
|
||||
if !loaded {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
user := UserObject{
|
||||
UserName: userName,
|
||||
Password: uPSK,
|
||||
}
|
||||
s.traffic.ReadUser(&user)
|
||||
render.JSON(writer, request, user)
|
||||
}
|
||||
|
||||
func (s *APIServer) updateUser(writer http.ResponseWriter, request *http.Request) {
|
||||
userName := chi.URLParam(request, "username")
|
||||
if userName == "" {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var updateRequest struct {
|
||||
Password string `json:"uPSK"`
|
||||
}
|
||||
err := render.DecodeJSON(request.Body, &updateRequest)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
_, loaded := s.user.Get(userName)
|
||||
if !loaded {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
err = s.user.Update(userName, updateRequest.Password)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *APIServer) deleteUser(writer http.ResponseWriter, request *http.Request) {
|
||||
userName := chi.URLParam(request, "username")
|
||||
if userName == "" {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, loaded := s.user.Get(userName)
|
||||
if !loaded {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
err := s.user.Delete(userName)
|
||||
if err != nil {
|
||||
render.Status(request, http.StatusBadRequest)
|
||||
render.PlainText(writer, request, err.Error())
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *APIServer) getStats(writer http.ResponseWriter, request *http.Request) {
|
||||
requireClear := chi.URLParam(request, "clear") == "true"
|
||||
|
||||
users := s.user.List()
|
||||
s.traffic.ReadUsers(users)
|
||||
for i := range users {
|
||||
users[i].Password = ""
|
||||
}
|
||||
uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal()
|
||||
|
||||
if requireClear {
|
||||
s.traffic.Clear()
|
||||
}
|
||||
|
||||
render.JSON(writer, request, render.M{
|
||||
"uplinkBytes": uplinkBytes,
|
||||
"downlinkBytes": downlinkBytes,
|
||||
"uplinkPackets": uplinkPackets,
|
||||
"downlinkPackets": downlinkPackets,
|
||||
"tcpSessions": tcpSessions,
|
||||
"udpSessions": udpSessions,
|
||||
"users": users,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user