mirror of
https://github.com/fatedier/frp.git
synced 2026-02-09 01:59:14 +08:00
refactor: separate API handlers into dedicated packages with improved HTTP utilities (#5127)
This commit is contained in:
346
server/api/controller.go
Normal file
346
server/api/controller.go
Normal file
@@ -0,0 +1,346 @@
|
||||
// Copyright 2025 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/metrics/mem"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/server/proxy"
|
||||
"github.com/fatedier/frp/server/registry"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
// dependencies
|
||||
serverCfg *v1.ServerConfig
|
||||
clientRegistry *registry.ClientRegistry
|
||||
pxyManager ProxyManager
|
||||
}
|
||||
|
||||
type ProxyManager interface {
|
||||
GetByName(name string) (proxy.Proxy, bool)
|
||||
}
|
||||
|
||||
func NewController(
|
||||
serverCfg *v1.ServerConfig,
|
||||
clientRegistry *registry.ClientRegistry,
|
||||
pxyManager ProxyManager,
|
||||
) *Controller {
|
||||
return &Controller{
|
||||
serverCfg: serverCfg,
|
||||
clientRegistry: clientRegistry,
|
||||
pxyManager: pxyManager,
|
||||
}
|
||||
}
|
||||
|
||||
// /api/serverinfo
|
||||
func (c *Controller) APIServerInfo(ctx *httppkg.Context) (any, error) {
|
||||
serverStats := mem.StatsCollector.GetServer()
|
||||
svrResp := ServerInfoResp{
|
||||
Version: version.Full(),
|
||||
BindPort: c.serverCfg.BindPort,
|
||||
VhostHTTPPort: c.serverCfg.VhostHTTPPort,
|
||||
VhostHTTPSPort: c.serverCfg.VhostHTTPSPort,
|
||||
TCPMuxHTTPConnectPort: c.serverCfg.TCPMuxHTTPConnectPort,
|
||||
KCPBindPort: c.serverCfg.KCPBindPort,
|
||||
QUICBindPort: c.serverCfg.QUICBindPort,
|
||||
SubdomainHost: c.serverCfg.SubDomainHost,
|
||||
MaxPoolCount: c.serverCfg.Transport.MaxPoolCount,
|
||||
MaxPortsPerClient: c.serverCfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: c.serverCfg.Transport.HeartbeatTimeout,
|
||||
AllowPortsStr: types.PortsRangeSlice(c.serverCfg.AllowPorts).String(),
|
||||
TLSForce: c.serverCfg.Transport.TLS.Force,
|
||||
|
||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||
CurConns: serverStats.CurConns,
|
||||
ClientCounts: serverStats.ClientCounts,
|
||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||
}
|
||||
// For API that returns struct, we can just return it.
|
||||
// But current GeneralResponse.Msg in legacy code expects a JSON string.
|
||||
// Since MakeHTTPHandlerFunc handles struct by encoding to JSON, we can return svrResp directly?
|
||||
// The original code wraps it in GeneralResponse{Msg: string(json)}.
|
||||
// If we return svrResp, the response body will be the JSON of svrResp.
|
||||
// We should check if the frontend expects { "code": 200, "msg": "{...}" } or just {...}.
|
||||
// Looking at previous code:
|
||||
// res := GeneralResponse{Code: 200}
|
||||
// buf, _ := json.Marshal(&svrResp)
|
||||
// res.Msg = string(buf)
|
||||
// Response body: {"code": 200, "msg": "{\"version\":...}"}
|
||||
// Wait, is it double encoded JSON? Yes it seems so!
|
||||
// Let's check dashboard_api.go original code again.
|
||||
// Yes: res.Msg = string(buf).
|
||||
// So the frontend expects { "code": 200, "msg": "JSON_STRING" }.
|
||||
// This is kind of ugly, but we must preserve compatibility.
|
||||
|
||||
return svrResp, nil
|
||||
}
|
||||
|
||||
// /api/clients
|
||||
func (c *Controller) APIClientList(ctx *httppkg.Context) (any, error) {
|
||||
if c.clientRegistry == nil {
|
||||
return nil, fmt.Errorf("client registry unavailable")
|
||||
}
|
||||
|
||||
userFilter := ctx.Query("user")
|
||||
clientIDFilter := ctx.Query("clientId")
|
||||
runIDFilter := ctx.Query("runId")
|
||||
statusFilter := strings.ToLower(ctx.Query("status"))
|
||||
|
||||
records := c.clientRegistry.List()
|
||||
items := make([]ClientInfoResp, 0, len(records))
|
||||
for _, info := range records {
|
||||
if userFilter != "" && info.User != userFilter {
|
||||
continue
|
||||
}
|
||||
if clientIDFilter != "" && info.ClientID != clientIDFilter {
|
||||
continue
|
||||
}
|
||||
if runIDFilter != "" && info.RunID != runIDFilter {
|
||||
continue
|
||||
}
|
||||
if !matchStatusFilter(info.Online, statusFilter) {
|
||||
continue
|
||||
}
|
||||
items = append(items, buildClientInfoResp(info))
|
||||
}
|
||||
|
||||
slices.SortFunc(items, func(a, b ClientInfoResp) int {
|
||||
if v := cmp.Compare(a.User, b.User); v != 0 {
|
||||
return v
|
||||
}
|
||||
if v := cmp.Compare(a.ClientID, b.ClientID); v != 0 {
|
||||
return v
|
||||
}
|
||||
return cmp.Compare(a.Key, b.Key)
|
||||
})
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// /api/clients/{key}
|
||||
func (c *Controller) APIClientDetail(ctx *httppkg.Context) (any, error) {
|
||||
key := ctx.Param("key")
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("missing client key")
|
||||
}
|
||||
|
||||
if c.clientRegistry == nil {
|
||||
return nil, fmt.Errorf("client registry unavailable")
|
||||
}
|
||||
|
||||
info, ok := c.clientRegistry.GetByKey(key)
|
||||
if !ok {
|
||||
return nil, httppkg.NewError(http.StatusNotFound, fmt.Sprintf("client %s not found", key))
|
||||
}
|
||||
|
||||
return buildClientInfoResp(info), nil
|
||||
}
|
||||
|
||||
// /api/proxy/:type
|
||||
func (c *Controller) APIProxyByType(ctx *httppkg.Context) (any, error) {
|
||||
proxyType := ctx.Param("type")
|
||||
|
||||
proxyInfoResp := GetProxyInfoResp{}
|
||||
proxyInfoResp.Proxies = c.getProxyStatsByType(proxyType)
|
||||
slices.SortFunc(proxyInfoResp.Proxies, func(a, b *ProxyStatsInfo) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
return proxyInfoResp, nil
|
||||
}
|
||||
|
||||
// /api/proxy/:type/:name
|
||||
func (c *Controller) APIProxyByTypeAndName(ctx *httppkg.Context) (any, error) {
|
||||
proxyType := ctx.Param("type")
|
||||
name := ctx.Param("name")
|
||||
|
||||
proxyStatsResp, code, msg := c.getProxyStatsByTypeAndName(proxyType, name)
|
||||
if code != 200 {
|
||||
return nil, httppkg.NewError(code, msg)
|
||||
}
|
||||
|
||||
return proxyStatsResp, nil
|
||||
}
|
||||
|
||||
// /api/traffic/:name
|
||||
func (c *Controller) APIProxyTraffic(ctx *httppkg.Context) (any, error) {
|
||||
name := ctx.Param("name")
|
||||
|
||||
trafficResp := GetProxyTrafficResp{}
|
||||
trafficResp.Name = name
|
||||
proxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)
|
||||
|
||||
if proxyTrafficInfo == nil {
|
||||
return nil, httppkg.NewError(http.StatusNotFound, "no proxy info found")
|
||||
}
|
||||
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||
|
||||
return trafficResp, nil
|
||||
}
|
||||
|
||||
// DELETE /api/proxies?status=offline
|
||||
func (c *Controller) DeleteProxies(ctx *httppkg.Context) (any, error) {
|
||||
status := ctx.Query("status")
|
||||
if status != "offline" {
|
||||
return nil, httppkg.NewError(http.StatusBadRequest, "status only support offline")
|
||||
}
|
||||
cleared, total := mem.StatsCollector.ClearOfflineProxies()
|
||||
log.Infof("cleared [%d] offline proxies, total [%d] proxies", cleared, total)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Controller) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
proxyStats := mem.StatsCollector.GetProxiesByType(proxyType)
|
||||
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
||||
for _, ps := range proxyStats {
|
||||
proxyInfo := &ProxyStatsInfo{}
|
||||
if pxy, ok := c.pxyManager.GetByName(ps.Name); ok {
|
||||
content, err := json.Marshal(pxy.GetConfigurer())
|
||||
if err != nil {
|
||||
log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
continue
|
||||
}
|
||||
proxyInfo.Conf = getConfByType(ps.Type)
|
||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||
log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
continue
|
||||
}
|
||||
proxyInfo.Status = "online"
|
||||
if pxy.GetLoginMsg() != nil {
|
||||
proxyInfo.ClientVersion = pxy.GetLoginMsg().Version
|
||||
}
|
||||
} else {
|
||||
proxyInfo.Status = "offline"
|
||||
}
|
||||
proxyInfo.Name = ps.Name
|
||||
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
proxyInfos = append(proxyInfos, proxyInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Controller) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||
proxyInfo.Name = proxyName
|
||||
ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||
if ps == nil {
|
||||
code = 404
|
||||
msg = "no proxy info found"
|
||||
} else {
|
||||
if pxy, ok := c.pxyManager.GetByName(proxyName); ok {
|
||||
content, err := json.Marshal(pxy.GetConfigurer())
|
||||
if err != nil {
|
||||
log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Conf = getConfByType(ps.Type)
|
||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||
log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Status = "online"
|
||||
} else {
|
||||
proxyInfo.Status = "offline"
|
||||
}
|
||||
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
code = 200
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func buildClientInfoResp(info registry.ClientInfo) ClientInfoResp {
|
||||
resp := ClientInfoResp{
|
||||
Key: info.Key,
|
||||
User: info.User,
|
||||
ClientID: info.ClientID,
|
||||
RunID: info.RunID,
|
||||
Hostname: info.Hostname,
|
||||
ClientIP: info.IP,
|
||||
FirstConnectedAt: toUnix(info.FirstConnectedAt),
|
||||
LastConnectedAt: toUnix(info.LastConnectedAt),
|
||||
Online: info.Online,
|
||||
}
|
||||
if !info.DisconnectedAt.IsZero() {
|
||||
resp.DisconnectedAt = info.DisconnectedAt.Unix()
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func toUnix(t time.Time) int64 {
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
func matchStatusFilter(online bool, filter string) bool {
|
||||
switch strings.ToLower(filter) {
|
||||
case "", "all":
|
||||
return true
|
||||
case "online":
|
||||
return online
|
||||
case "offline":
|
||||
return !online
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func getConfByType(proxyType string) any {
|
||||
switch v1.ProxyType(proxyType) {
|
||||
case v1.ProxyTypeTCP:
|
||||
return &TCPOutConf{}
|
||||
case v1.ProxyTypeTCPMUX:
|
||||
return &TCPMuxOutConf{}
|
||||
case v1.ProxyTypeUDP:
|
||||
return &UDPOutConf{}
|
||||
case v1.ProxyTypeHTTP:
|
||||
return &HTTPOutConf{}
|
||||
case v1.ProxyTypeHTTPS:
|
||||
return &HTTPSOutConf{}
|
||||
case v1.ProxyTypeSTCP:
|
||||
return &STCPOutConf{}
|
||||
case v1.ProxyTypeXTCP:
|
||||
return &XTCPOutConf{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
131
server/api/types.go
Normal file
131
server/api/types.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2025 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
type ServerInfoResp struct {
|
||||
Version string `json:"version"`
|
||||
BindPort int `json:"bindPort"`
|
||||
VhostHTTPPort int `json:"vhostHTTPPort"`
|
||||
VhostHTTPSPort int `json:"vhostHTTPSPort"`
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort"`
|
||||
KCPBindPort int `json:"kcpBindPort"`
|
||||
QUICBindPort int `json:"quicBindPort"`
|
||||
SubdomainHost string `json:"subdomainHost"`
|
||||
MaxPoolCount int64 `json:"maxPoolCount"`
|
||||
MaxPortsPerClient int64 `json:"maxPortsPerClient"`
|
||||
HeartBeatTimeout int64 `json:"heartbeatTimeout"`
|
||||
AllowPortsStr string `json:"allowPortsStr,omitempty"`
|
||||
TLSForce bool `json:"tlsForce,omitempty"`
|
||||
|
||||
TotalTrafficIn int64 `json:"totalTrafficIn"`
|
||||
TotalTrafficOut int64 `json:"totalTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
ClientCounts int64 `json:"clientCounts"`
|
||||
ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
|
||||
}
|
||||
|
||||
type ClientInfoResp struct {
|
||||
Key string `json:"key"`
|
||||
User string `json:"user"`
|
||||
ClientID string `json:"clientID"`
|
||||
RunID string `json:"runID"`
|
||||
Hostname string `json:"hostname"`
|
||||
ClientIP string `json:"clientIP,omitempty"`
|
||||
FirstConnectedAt int64 `json:"firstConnectedAt"`
|
||||
LastConnectedAt int64 `json:"lastConnectedAt"`
|
||||
DisconnectedAt int64 `json:"disconnectedAt,omitempty"`
|
||||
Online bool `json:"online"`
|
||||
}
|
||||
|
||||
type BaseOutConf struct {
|
||||
v1.ProxyBaseConfig
|
||||
}
|
||||
|
||||
type TCPOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remotePort"`
|
||||
}
|
||||
|
||||
type TCPMuxOutConf struct {
|
||||
BaseOutConf
|
||||
v1.DomainConfig
|
||||
Multiplexer string `json:"multiplexer"`
|
||||
RouteByHTTPUser string `json:"routeByHTTPUser"`
|
||||
}
|
||||
|
||||
type UDPOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remotePort"`
|
||||
}
|
||||
|
||||
type HTTPOutConf struct {
|
||||
BaseOutConf
|
||||
v1.DomainConfig
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite"`
|
||||
}
|
||||
|
||||
type HTTPSOutConf struct {
|
||||
BaseOutConf
|
||||
v1.DomainConfig
|
||||
}
|
||||
|
||||
type STCPOutConf struct {
|
||||
BaseOutConf
|
||||
}
|
||||
|
||||
type XTCPOutConf struct {
|
||||
BaseOutConf
|
||||
}
|
||||
|
||||
// Get proxy info.
|
||||
type ProxyStatsInfo struct {
|
||||
Name string `json:"name"`
|
||||
Conf any `json:"conf"`
|
||||
ClientVersion string `json:"clientVersion,omitempty"`
|
||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
LastStartTime string `json:"lastStartTime"`
|
||||
LastCloseTime string `json:"lastCloseTime"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type GetProxyInfoResp struct {
|
||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||
}
|
||||
|
||||
// Get proxy info by name.
|
||||
type GetProxyStatsResp struct {
|
||||
Name string `json:"name"`
|
||||
Conf any `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
LastStartTime string `json:"lastStartTime"`
|
||||
LastCloseTime string `json:"lastCloseTime"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// /api/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
Name string `json:"name"`
|
||||
TrafficIn []int64 `json:"trafficIn"`
|
||||
TrafficOut []int64 `json:"trafficOut"`
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
"github.com/fatedier/frp/server/proxy"
|
||||
"github.com/fatedier/frp/server/registry"
|
||||
)
|
||||
|
||||
type ControlManager struct {
|
||||
@@ -147,7 +148,7 @@ type Control struct {
|
||||
// Server configuration information
|
||||
serverCfg *v1.ServerConfig
|
||||
|
||||
clientRegistry *ClientRegistry
|
||||
clientRegistry *registry.ClientRegistry
|
||||
|
||||
xl *xlog.Logger
|
||||
ctx context.Context
|
||||
|
||||
@@ -1,563 +0,0 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/metrics/mem"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
|
||||
helper.Router.HandleFunc("/healthz", svr.healthz)
|
||||
subRouter := helper.Router.NewRoute().Subrouter()
|
||||
|
||||
subRouter.Use(helper.AuthMiddleware.Middleware)
|
||||
|
||||
// metrics
|
||||
if svr.cfg.EnablePrometheus {
|
||||
subRouter.Handle("/metrics", promhttp.Handler())
|
||||
}
|
||||
|
||||
// apis
|
||||
subRouter.HandleFunc("/api/serverinfo", svr.apiServerInfo).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
|
||||
subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
|
||||
subRouter.HandleFunc("/api/clients", svr.apiClientList).Methods("GET")
|
||||
subRouter.HandleFunc("/api/clients/{key}", svr.apiClientDetail).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(
|
||||
netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
|
||||
).Methods("GET")
|
||||
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
}
|
||||
|
||||
type serverInfoResp struct {
|
||||
Version string `json:"version"`
|
||||
BindPort int `json:"bindPort"`
|
||||
VhostHTTPPort int `json:"vhostHTTPPort"`
|
||||
VhostHTTPSPort int `json:"vhostHTTPSPort"`
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort"`
|
||||
KCPBindPort int `json:"kcpBindPort"`
|
||||
QUICBindPort int `json:"quicBindPort"`
|
||||
SubdomainHost string `json:"subdomainHost"`
|
||||
MaxPoolCount int64 `json:"maxPoolCount"`
|
||||
MaxPortsPerClient int64 `json:"maxPortsPerClient"`
|
||||
HeartBeatTimeout int64 `json:"heartbeatTimeout"`
|
||||
AllowPortsStr string `json:"allowPortsStr,omitempty"`
|
||||
TLSForce bool `json:"tlsForce,omitempty"`
|
||||
|
||||
TotalTrafficIn int64 `json:"totalTrafficIn"`
|
||||
TotalTrafficOut int64 `json:"totalTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
ClientCounts int64 `json:"clientCounts"`
|
||||
ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
|
||||
}
|
||||
|
||||
type clientInfoResp struct {
|
||||
Key string `json:"key"`
|
||||
User string `json:"user"`
|
||||
ClientID string `json:"clientID"`
|
||||
RunID string `json:"runID"`
|
||||
Hostname string `json:"hostname"`
|
||||
ClientIP string `json:"clientIP,omitempty"`
|
||||
FirstConnectedAt int64 `json:"firstConnectedAt"`
|
||||
LastConnectedAt int64 `json:"lastConnectedAt"`
|
||||
DisconnectedAt int64 `json:"disconnectedAt,omitempty"`
|
||||
Online bool `json:"online"`
|
||||
}
|
||||
|
||||
// /healthz
|
||||
func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
// /api/serverinfo
|
||||
func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
serverStats := mem.StatsCollector.GetServer()
|
||||
svrResp := serverInfoResp{
|
||||
Version: version.Full(),
|
||||
BindPort: svr.cfg.BindPort,
|
||||
VhostHTTPPort: svr.cfg.VhostHTTPPort,
|
||||
VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
|
||||
TCPMuxHTTPConnectPort: svr.cfg.TCPMuxHTTPConnectPort,
|
||||
KCPBindPort: svr.cfg.KCPBindPort,
|
||||
QUICBindPort: svr.cfg.QUICBindPort,
|
||||
SubdomainHost: svr.cfg.SubDomainHost,
|
||||
MaxPoolCount: svr.cfg.Transport.MaxPoolCount,
|
||||
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: svr.cfg.Transport.HeartbeatTimeout,
|
||||
AllowPortsStr: types.PortsRangeSlice(svr.cfg.AllowPorts).String(),
|
||||
TLSForce: svr.cfg.Transport.TLS.Force,
|
||||
|
||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||
CurConns: serverStats.CurConns,
|
||||
ClientCounts: serverStats.ClientCounts,
|
||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(&svrResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
// /api/clients
|
||||
func (svr *Service) apiClientList(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.RequestURI(), res.Code)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
log.Infof("http request: [%s]", r.URL.RequestURI())
|
||||
|
||||
if svr.clientRegistry == nil {
|
||||
res.Code = http.StatusInternalServerError
|
||||
res.Msg = "client registry unavailable"
|
||||
return
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
userFilter := query.Get("user")
|
||||
clientIDFilter := query.Get("clientId")
|
||||
runIDFilter := query.Get("runId")
|
||||
statusFilter := strings.ToLower(query.Get("status"))
|
||||
|
||||
records := svr.clientRegistry.List()
|
||||
items := make([]clientInfoResp, 0, len(records))
|
||||
for _, info := range records {
|
||||
if userFilter != "" && info.User != userFilter {
|
||||
continue
|
||||
}
|
||||
if clientIDFilter != "" && info.ClientID != clientIDFilter {
|
||||
continue
|
||||
}
|
||||
if runIDFilter != "" && info.RunID != runIDFilter {
|
||||
continue
|
||||
}
|
||||
if !matchStatusFilter(info.Online, statusFilter) {
|
||||
continue
|
||||
}
|
||||
items = append(items, buildClientInfoResp(info))
|
||||
}
|
||||
|
||||
slices.SortFunc(items, func(a, b clientInfoResp) int {
|
||||
if v := cmp.Compare(a.User, b.User); v != 0 {
|
||||
return v
|
||||
}
|
||||
if v := cmp.Compare(a.ClientID, b.ClientID); v != 0 {
|
||||
return v
|
||||
}
|
||||
return cmp.Compare(a.Key, b.Key)
|
||||
})
|
||||
|
||||
buf, _ := json.Marshal(items)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
// /api/clients/{key}
|
||||
func (svr *Service) apiClientDetail(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.RequestURI(), res.Code)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
log.Infof("http request: [%s]", r.URL.RequestURI())
|
||||
|
||||
vars := mux.Vars(r)
|
||||
key := vars["key"]
|
||||
if key == "" {
|
||||
res.Code = http.StatusBadRequest
|
||||
res.Msg = "missing client key"
|
||||
return
|
||||
}
|
||||
|
||||
if svr.clientRegistry == nil {
|
||||
res.Code = http.StatusInternalServerError
|
||||
res.Msg = "client registry unavailable"
|
||||
return
|
||||
}
|
||||
|
||||
info, ok := svr.clientRegistry.GetByKey(key)
|
||||
if !ok {
|
||||
res.Code = http.StatusNotFound
|
||||
res.Msg = fmt.Sprintf("client %s not found", key)
|
||||
return
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(buildClientInfoResp(info))
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
type BaseOutConf struct {
|
||||
v1.ProxyBaseConfig
|
||||
}
|
||||
|
||||
type TCPOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remotePort"`
|
||||
}
|
||||
|
||||
type TCPMuxOutConf struct {
|
||||
BaseOutConf
|
||||
v1.DomainConfig
|
||||
Multiplexer string `json:"multiplexer"`
|
||||
RouteByHTTPUser string `json:"routeByHTTPUser"`
|
||||
}
|
||||
|
||||
type UDPOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remotePort"`
|
||||
}
|
||||
|
||||
type HTTPOutConf struct {
|
||||
BaseOutConf
|
||||
v1.DomainConfig
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite"`
|
||||
}
|
||||
|
||||
type HTTPSOutConf struct {
|
||||
BaseOutConf
|
||||
v1.DomainConfig
|
||||
}
|
||||
|
||||
type STCPOutConf struct {
|
||||
BaseOutConf
|
||||
}
|
||||
|
||||
type XTCPOutConf struct {
|
||||
BaseOutConf
|
||||
}
|
||||
|
||||
func getConfByType(proxyType string) any {
|
||||
switch v1.ProxyType(proxyType) {
|
||||
case v1.ProxyTypeTCP:
|
||||
return &TCPOutConf{}
|
||||
case v1.ProxyTypeTCPMUX:
|
||||
return &TCPMuxOutConf{}
|
||||
case v1.ProxyTypeUDP:
|
||||
return &UDPOutConf{}
|
||||
case v1.ProxyTypeHTTP:
|
||||
return &HTTPOutConf{}
|
||||
case v1.ProxyTypeHTTPS:
|
||||
return &HTTPSOutConf{}
|
||||
case v1.ProxyTypeSTCP:
|
||||
return &STCPOutConf{}
|
||||
case v1.ProxyTypeXTCP:
|
||||
return &XTCPOutConf{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get proxy info.
|
||||
type ProxyStatsInfo struct {
|
||||
Name string `json:"name"`
|
||||
Conf any `json:"conf"`
|
||||
ClientVersion string `json:"clientVersion,omitempty"`
|
||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
LastStartTime string `json:"lastStartTime"`
|
||||
LastCloseTime string `json:"lastCloseTime"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type GetProxyInfoResp struct {
|
||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||
}
|
||||
|
||||
// /api/proxy/:type
|
||||
func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
|
||||
proxyInfoResp := GetProxyInfoResp{}
|
||||
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||
slices.SortFunc(proxyInfoResp.Proxies, func(a, b *ProxyStatsInfo) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
buf, _ := json.Marshal(&proxyInfoResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
proxyStats := mem.StatsCollector.GetProxiesByType(proxyType)
|
||||
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
|
||||
for _, ps := range proxyStats {
|
||||
proxyInfo := &ProxyStatsInfo{}
|
||||
if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok {
|
||||
content, err := json.Marshal(pxy.GetConfigurer())
|
||||
if err != nil {
|
||||
log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
continue
|
||||
}
|
||||
proxyInfo.Conf = getConfByType(ps.Type)
|
||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||
log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
continue
|
||||
}
|
||||
proxyInfo.Status = "online"
|
||||
if pxy.GetLoginMsg() != nil {
|
||||
proxyInfo.ClientVersion = pxy.GetLoginMsg().Version
|
||||
}
|
||||
} else {
|
||||
proxyInfo.Status = "offline"
|
||||
}
|
||||
proxyInfo.Name = ps.Name
|
||||
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
proxyInfos = append(proxyInfos, proxyInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get proxy info by name.
|
||||
type GetProxyStatsResp struct {
|
||||
Name string `json:"name"`
|
||||
Conf any `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
LastStartTime string `json:"lastStartTime"`
|
||||
LastCloseTime string `json:"lastCloseTime"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// /api/proxy/:type/:name
|
||||
func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
|
||||
var proxyStatsResp GetProxyStatsResp
|
||||
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||
if res.Code != 200 {
|
||||
return
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(&proxyStatsResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||
proxyInfo.Name = proxyName
|
||||
ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||
if ps == nil {
|
||||
code = 404
|
||||
msg = "no proxy info found"
|
||||
} else {
|
||||
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
||||
content, err := json.Marshal(pxy.GetConfigurer())
|
||||
if err != nil {
|
||||
log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Conf = getConfByType(ps.Type)
|
||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||
log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Status = "online"
|
||||
} else {
|
||||
proxyInfo.Status = "offline"
|
||||
}
|
||||
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
|
||||
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
code = 200
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// /api/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
Name string `json:"name"`
|
||||
TrafficIn []int64 `json:"trafficIn"`
|
||||
TrafficOut []int64 `json:"trafficOut"`
|
||||
}
|
||||
|
||||
func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
|
||||
trafficResp := GetProxyTrafficResp{}
|
||||
trafficResp.Name = name
|
||||
proxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)
|
||||
|
||||
if proxyTrafficInfo == nil {
|
||||
res.Code = 404
|
||||
res.Msg = "no proxy info found"
|
||||
return
|
||||
}
|
||||
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||
|
||||
buf, _ := json.Marshal(&trafficResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
// DELETE /api/proxies?status=offline
|
||||
func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Infof("http request: [%s]", r.URL.Path)
|
||||
defer func() {
|
||||
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
_, _ = w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
status := r.URL.Query().Get("status")
|
||||
if status != "offline" {
|
||||
res.Code = 400
|
||||
res.Msg = "status only support offline"
|
||||
return
|
||||
}
|
||||
cleared, total := mem.StatsCollector.ClearOfflineProxies()
|
||||
log.Infof("cleared [%d] offline proxies, total [%d] proxies", cleared, total)
|
||||
}
|
||||
|
||||
func buildClientInfoResp(info ClientInfo) clientInfoResp {
|
||||
resp := clientInfoResp{
|
||||
Key: info.Key,
|
||||
User: info.User,
|
||||
ClientID: info.ClientID,
|
||||
RunID: info.RunID,
|
||||
Hostname: info.Hostname,
|
||||
ClientIP: info.IP,
|
||||
FirstConnectedAt: toUnix(info.FirstConnectedAt),
|
||||
LastConnectedAt: toUnix(info.LastConnectedAt),
|
||||
Online: info.Online,
|
||||
}
|
||||
if !info.DisconnectedAt.IsZero() {
|
||||
resp.DisconnectedAt = info.DisconnectedAt.Unix()
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func toUnix(t time.Time) int64 {
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
func matchStatusFilter(online bool, filter string) bool {
|
||||
switch strings.ToLower(filter) {
|
||||
case "", "all":
|
||||
return true
|
||||
case "online":
|
||||
return online
|
||||
case "offline":
|
||||
return !online
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,18 @@
|
||||
package server
|
||||
// Copyright 2025 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/fatedier/golib/crypto"
|
||||
"github.com/fatedier/golib/net/mux"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
"github.com/samber/lo"
|
||||
|
||||
@@ -47,11 +48,13 @@ import (
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/pkg/util/vhost"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
"github.com/fatedier/frp/server/api"
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
"github.com/fatedier/frp/server/group"
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
"github.com/fatedier/frp/server/ports"
|
||||
"github.com/fatedier/frp/server/proxy"
|
||||
"github.com/fatedier/frp/server/registry"
|
||||
"github.com/fatedier/frp/server/visitor"
|
||||
)
|
||||
|
||||
@@ -97,7 +100,7 @@ type Service struct {
|
||||
ctlManager *ControlManager
|
||||
|
||||
// Track logical clients keyed by user.clientID.
|
||||
clientRegistry *ClientRegistry
|
||||
clientRegistry *registry.ClientRegistry
|
||||
|
||||
// Manage all proxies
|
||||
pxyManager *proxy.Manager
|
||||
@@ -159,7 +162,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
|
||||
|
||||
svr := &Service{
|
||||
ctlManager: NewControlManager(),
|
||||
clientRegistry: NewClientRegistry(),
|
||||
clientRegistry: registry.NewClientRegistry(),
|
||||
pxyManager: proxy.NewManager(),
|
||||
pluginManager: plugin.NewManager(),
|
||||
rc: &controller.ResourceController{
|
||||
@@ -687,3 +690,41 @@ func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVis
|
||||
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||
newMsg.UseEncryption, newMsg.UseCompression, visitorUser)
|
||||
}
|
||||
|
||||
func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
|
||||
helper.Router.HandleFunc("/healthz", healthz)
|
||||
subRouter := helper.Router.NewRoute().Subrouter()
|
||||
|
||||
subRouter.Use(helper.AuthMiddleware)
|
||||
subRouter.Use(httppkg.NewRequestLogger)
|
||||
|
||||
// metrics
|
||||
if svr.cfg.EnablePrometheus {
|
||||
subRouter.Handle("/metrics", promhttp.Handler())
|
||||
}
|
||||
|
||||
apiController := api.NewController(svr.cfg, svr.clientRegistry, svr.pxyManager)
|
||||
|
||||
// apis
|
||||
subRouter.HandleFunc("/api/serverinfo", httppkg.MakeHTTPHandlerFunc(apiController.APIServerInfo)).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}", httppkg.MakeHTTPHandlerFunc(apiController.APIProxyByType)).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}/{name}", httppkg.MakeHTTPHandlerFunc(apiController.APIProxyByTypeAndName)).Methods("GET")
|
||||
subRouter.HandleFunc("/api/traffic/{name}", httppkg.MakeHTTPHandlerFunc(apiController.APIProxyTraffic)).Methods("GET")
|
||||
subRouter.HandleFunc("/api/clients", httppkg.MakeHTTPHandlerFunc(apiController.APIClientList)).Methods("GET")
|
||||
subRouter.HandleFunc("/api/clients/{key}", httppkg.MakeHTTPHandlerFunc(apiController.APIClientDetail)).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxies", httppkg.MakeHTTPHandlerFunc(apiController.DeleteProxies)).Methods("DELETE")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(
|
||||
netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
|
||||
).Methods("GET")
|
||||
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
}
|
||||
|
||||
func healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user