mirror of
https://github.com/fatedier/frp.git
synced 2026-03-21 09:19:19 +08:00
refactor: separate API handlers into dedicated packages with improved HTTP utilities (#5127)
This commit is contained in:
@@ -15,44 +15,29 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/client/api"
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
|
||||
helper.Router.HandleFunc("/healthz", svr.healthz)
|
||||
apiController := newAPIController(svr)
|
||||
|
||||
// Healthz endpoint without auth
|
||||
helper.Router.HandleFunc("/healthz", healthz)
|
||||
|
||||
// API routes and static files with auth
|
||||
subRouter := helper.Router.NewRoute().Subrouter()
|
||||
|
||||
subRouter.Use(helper.AuthMiddleware.Middleware)
|
||||
|
||||
// api, see admin_api.go
|
||||
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
subRouter.HandleFunc("/api/stop", svr.apiStop).Methods("POST")
|
||||
subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||
subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
|
||||
// view
|
||||
subRouter.Use(helper.AuthMiddleware)
|
||||
subRouter.Use(httppkg.NewRequestLogger)
|
||||
subRouter.HandleFunc("/api/reload", httppkg.MakeHTTPHandlerFunc(apiController.Reload)).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/api/stop", httppkg.MakeHTTPHandlerFunc(apiController.Stop)).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/api/status", httppkg.MakeHTTPHandlerFunc(apiController.Status)).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/api/config", httppkg.MakeHTTPHandlerFunc(apiController.GetConfig)).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/api/config", httppkg.MakeHTTPHandlerFunc(apiController.PutConfig)).Methods(http.MethodPut)
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(
|
||||
netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
|
||||
@@ -62,202 +47,28 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper)
|
||||
})
|
||||
}
|
||||
|
||||
// /healthz
|
||||
func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
func healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// GET /api/reload
|
||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
strictConfigMode := false
|
||||
strictStr := r.URL.Query().Get("strictConfig")
|
||||
if strictStr != "" {
|
||||
strictConfigMode, _ = strconv.ParseBool(strictStr)
|
||||
}
|
||||
|
||||
log.Infof("api request [/api/reload]")
|
||||
defer func() {
|
||||
log.Infof("api response [/api/reload], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.configFilePath, strictConfigMode)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warnf("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs, svr.unsafeFeatures); err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warnf("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if err := svr.UpdateAllConfigurer(proxyCfgs, visitorCfgs); err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = err.Error()
|
||||
log.Warnf("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
log.Infof("success reload conf")
|
||||
func newAPIController(svr *Service) *api.Controller {
|
||||
return api.NewController(api.ControllerParams{
|
||||
GetProxyStatus: svr.getAllProxyStatus,
|
||||
ServerAddr: svr.common.ServerAddr,
|
||||
ConfigFilePath: svr.configFilePath,
|
||||
UnsafeFeatures: svr.unsafeFeatures,
|
||||
UpdateConfig: svr.UpdateAllConfigurer,
|
||||
GracefulClose: svr.GracefulClose,
|
||||
})
|
||||
}
|
||||
|
||||
// POST /api/stop
|
||||
func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Infof("api request [/api/stop]")
|
||||
defer func() {
|
||||
log.Infof("api response [/api/stop], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
go svr.GracefulClose(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
type StatusResp map[string][]ProxyStatusResp
|
||||
|
||||
type ProxyStatusResp struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Err string `json:"err"`
|
||||
LocalAddr string `json:"local_addr"`
|
||||
Plugin string `json:"plugin"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
|
||||
psr := ProxyStatusResp{
|
||||
Name: status.Name,
|
||||
Type: status.Type,
|
||||
Status: status.Phase,
|
||||
Err: status.Err,
|
||||
}
|
||||
baseCfg := status.Cfg.GetBaseConfig()
|
||||
if baseCfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = baseCfg.Plugin.Type
|
||||
|
||||
if status.Err == "" {
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
if slices.Contains([]string{"tcp", "udp"}, status.Type) {
|
||||
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||
}
|
||||
}
|
||||
return psr
|
||||
}
|
||||
|
||||
// GET /api/status
|
||||
func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res StatusResp = make(map[string][]ProxyStatusResp)
|
||||
)
|
||||
|
||||
log.Infof("http request [/api/status]")
|
||||
defer func() {
|
||||
log.Infof("http response [/api/status]")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
buf, _ = json.Marshal(&res)
|
||||
_, _ = w.Write(buf)
|
||||
}()
|
||||
|
||||
// getAllProxyStatus returns all proxy statuses.
|
||||
func (svr *Service) getAllProxyStatus() []*proxy.WorkingStatus {
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
if ctl == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ps := ctl.pm.GetAllProxyStatus()
|
||||
for _, status := range ps {
|
||||
res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.common.ServerAddr))
|
||||
}
|
||||
|
||||
for _, arrs := range res {
|
||||
if len(arrs) <= 1 {
|
||||
continue
|
||||
}
|
||||
slices.SortFunc(arrs, func(a, b ProxyStatusResp) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/config
|
||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Infof("http get request [/api/config]")
|
||||
defer func() {
|
||||
log.Infof("http get response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
if svr.configFilePath == "" {
|
||||
res.Code = 400
|
||||
res.Msg = "frpc has no config file path"
|
||||
log.Warnf("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(svr.configFilePath)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warnf("load frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
res.Msg = string(content)
|
||||
}
|
||||
|
||||
// PUT /api/config
|
||||
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Infof("http put request [/api/config]")
|
||||
defer func() {
|
||||
log.Infof("http put response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
// get new config content
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||
log.Warnf("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
res.Code = 400
|
||||
res.Msg = "body can't be empty"
|
||||
log.Warnf("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile(svr.configFilePath, body, 0o600); err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
log.Warnf("%s", res.Msg)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
return ctl.pm.GetAllProxyStatus()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user