dashboard_api.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // Copyright 2017 fatedier, fatedier@gmail.com
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package server
  15. import (
  16. "cmp"
  17. "encoding/json"
  18. "net/http"
  19. "slices"
  20. "github.com/gorilla/mux"
  21. "github.com/prometheus/client_golang/prometheus/promhttp"
  22. "github.com/fatedier/frp/pkg/config/types"
  23. v1 "github.com/fatedier/frp/pkg/config/v1"
  24. "github.com/fatedier/frp/pkg/metrics/mem"
  25. httppkg "github.com/fatedier/frp/pkg/util/http"
  26. "github.com/fatedier/frp/pkg/util/log"
  27. netpkg "github.com/fatedier/frp/pkg/util/net"
  28. "github.com/fatedier/frp/pkg/util/version"
  29. )
  30. type GeneralResponse struct {
  31. Code int
  32. Msg string
  33. }
  34. func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
  35. helper.Router.HandleFunc("/healthz", svr.healthz)
  36. subRouter := helper.Router.NewRoute().Subrouter()
  37. subRouter.Use(helper.AuthMiddleware.Middleware)
  38. // metrics
  39. if svr.cfg.EnablePrometheus {
  40. subRouter.Handle("/metrics", promhttp.Handler())
  41. }
  42. // apis
  43. subRouter.HandleFunc("/api/serverinfo", svr.apiServerInfo).Methods("GET")
  44. subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
  45. subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
  46. subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
  47. subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE")
  48. // view
  49. subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
  50. subRouter.PathPrefix("/static/").Handler(
  51. netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
  52. ).Methods("GET")
  53. subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  54. http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
  55. })
  56. }
  57. type serverInfoResp struct {
  58. Version string `json:"version"`
  59. BindPort int `json:"bindPort"`
  60. VhostHTTPPort int `json:"vhostHTTPPort"`
  61. VhostHTTPSPort int `json:"vhostHTTPSPort"`
  62. TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort"`
  63. KCPBindPort int `json:"kcpBindPort"`
  64. QUICBindPort int `json:"quicBindPort"`
  65. SubdomainHost string `json:"subdomainHost"`
  66. MaxPoolCount int64 `json:"maxPoolCount"`
  67. MaxPortsPerClient int64 `json:"maxPortsPerClient"`
  68. HeartBeatTimeout int64 `json:"heartbeatTimeout"`
  69. AllowPortsStr string `json:"allowPortsStr,omitempty"`
  70. TLSForce bool `json:"tlsForce,omitempty"`
  71. TotalTrafficIn int64 `json:"totalTrafficIn"`
  72. TotalTrafficOut int64 `json:"totalTrafficOut"`
  73. CurConns int64 `json:"curConns"`
  74. ClientCounts int64 `json:"clientCounts"`
  75. ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
  76. }
  77. // /healthz
  78. func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
  79. w.WriteHeader(200)
  80. }
  81. // /api/serverinfo
  82. func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
  83. res := GeneralResponse{Code: 200}
  84. defer func() {
  85. log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  86. w.WriteHeader(res.Code)
  87. if len(res.Msg) > 0 {
  88. _, _ = w.Write([]byte(res.Msg))
  89. }
  90. }()
  91. log.Infof("Http request: [%s]", r.URL.Path)
  92. serverStats := mem.StatsCollector.GetServer()
  93. svrResp := serverInfoResp{
  94. Version: version.Full(),
  95. BindPort: svr.cfg.BindPort,
  96. VhostHTTPPort: svr.cfg.VhostHTTPPort,
  97. VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
  98. TCPMuxHTTPConnectPort: svr.cfg.TCPMuxHTTPConnectPort,
  99. KCPBindPort: svr.cfg.KCPBindPort,
  100. QUICBindPort: svr.cfg.QUICBindPort,
  101. SubdomainHost: svr.cfg.SubDomainHost,
  102. MaxPoolCount: svr.cfg.Transport.MaxPoolCount,
  103. MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
  104. HeartBeatTimeout: svr.cfg.Transport.HeartbeatTimeout,
  105. AllowPortsStr: types.PortsRangeSlice(svr.cfg.AllowPorts).String(),
  106. TLSForce: svr.cfg.Transport.TLS.Force,
  107. TotalTrafficIn: serverStats.TotalTrafficIn,
  108. TotalTrafficOut: serverStats.TotalTrafficOut,
  109. CurConns: serverStats.CurConns,
  110. ClientCounts: serverStats.ClientCounts,
  111. ProxyTypeCounts: serverStats.ProxyTypeCounts,
  112. }
  113. buf, _ := json.Marshal(&svrResp)
  114. res.Msg = string(buf)
  115. }
  116. type BaseOutConf struct {
  117. v1.ProxyBaseConfig
  118. }
  119. type TCPOutConf struct {
  120. BaseOutConf
  121. RemotePort int `json:"remotePort"`
  122. }
  123. type TCPMuxOutConf struct {
  124. BaseOutConf
  125. v1.DomainConfig
  126. Multiplexer string `json:"multiplexer"`
  127. RouteByHTTPUser string `json:"routeByHTTPUser"`
  128. }
  129. type UDPOutConf struct {
  130. BaseOutConf
  131. RemotePort int `json:"remotePort"`
  132. }
  133. type HTTPOutConf struct {
  134. BaseOutConf
  135. v1.DomainConfig
  136. Locations []string `json:"locations"`
  137. HostHeaderRewrite string `json:"hostHeaderRewrite"`
  138. }
  139. type HTTPSOutConf struct {
  140. BaseOutConf
  141. v1.DomainConfig
  142. }
  143. type STCPOutConf struct {
  144. BaseOutConf
  145. }
  146. type XTCPOutConf struct {
  147. BaseOutConf
  148. }
  149. func getConfByType(proxyType string) any {
  150. switch v1.ProxyType(proxyType) {
  151. case v1.ProxyTypeTCP:
  152. return &TCPOutConf{}
  153. case v1.ProxyTypeTCPMUX:
  154. return &TCPMuxOutConf{}
  155. case v1.ProxyTypeUDP:
  156. return &UDPOutConf{}
  157. case v1.ProxyTypeHTTP:
  158. return &HTTPOutConf{}
  159. case v1.ProxyTypeHTTPS:
  160. return &HTTPSOutConf{}
  161. case v1.ProxyTypeSTCP:
  162. return &STCPOutConf{}
  163. case v1.ProxyTypeXTCP:
  164. return &XTCPOutConf{}
  165. default:
  166. return nil
  167. }
  168. }
  169. // Get proxy info.
  170. type ProxyStatsInfo struct {
  171. Name string `json:"name"`
  172. Conf interface{} `json:"conf"`
  173. ClientVersion string `json:"clientVersion,omitempty"`
  174. TodayTrafficIn int64 `json:"todayTrafficIn"`
  175. TodayTrafficOut int64 `json:"todayTrafficOut"`
  176. CurConns int64 `json:"curConns"`
  177. LastStartTime string `json:"lastStartTime"`
  178. LastCloseTime string `json:"lastCloseTime"`
  179. Status string `json:"status"`
  180. }
  181. type GetProxyInfoResp struct {
  182. Proxies []*ProxyStatsInfo `json:"proxies"`
  183. }
  184. // /api/proxy/:type
  185. func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
  186. res := GeneralResponse{Code: 200}
  187. params := mux.Vars(r)
  188. proxyType := params["type"]
  189. defer func() {
  190. log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  191. w.WriteHeader(res.Code)
  192. if len(res.Msg) > 0 {
  193. _, _ = w.Write([]byte(res.Msg))
  194. }
  195. }()
  196. log.Infof("Http request: [%s]", r.URL.Path)
  197. proxyInfoResp := GetProxyInfoResp{}
  198. proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
  199. slices.SortFunc(proxyInfoResp.Proxies, func(a, b *ProxyStatsInfo) int {
  200. return cmp.Compare(a.Name, b.Name)
  201. })
  202. buf, _ := json.Marshal(&proxyInfoResp)
  203. res.Msg = string(buf)
  204. }
  205. func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
  206. proxyStats := mem.StatsCollector.GetProxiesByType(proxyType)
  207. proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
  208. for _, ps := range proxyStats {
  209. proxyInfo := &ProxyStatsInfo{}
  210. if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok {
  211. content, err := json.Marshal(pxy.GetConfigurer())
  212. if err != nil {
  213. log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
  214. continue
  215. }
  216. proxyInfo.Conf = getConfByType(ps.Type)
  217. if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
  218. log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
  219. continue
  220. }
  221. proxyInfo.Status = "online"
  222. if pxy.GetLoginMsg() != nil {
  223. proxyInfo.ClientVersion = pxy.GetLoginMsg().Version
  224. }
  225. } else {
  226. proxyInfo.Status = "offline"
  227. }
  228. proxyInfo.Name = ps.Name
  229. proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
  230. proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
  231. proxyInfo.CurConns = ps.CurConns
  232. proxyInfo.LastStartTime = ps.LastStartTime
  233. proxyInfo.LastCloseTime = ps.LastCloseTime
  234. proxyInfos = append(proxyInfos, proxyInfo)
  235. }
  236. return
  237. }
  238. // Get proxy info by name.
  239. type GetProxyStatsResp struct {
  240. Name string `json:"name"`
  241. Conf interface{} `json:"conf"`
  242. TodayTrafficIn int64 `json:"todayTrafficIn"`
  243. TodayTrafficOut int64 `json:"todayTrafficOut"`
  244. CurConns int64 `json:"curConns"`
  245. LastStartTime string `json:"lastStartTime"`
  246. LastCloseTime string `json:"lastCloseTime"`
  247. Status string `json:"status"`
  248. }
  249. // /api/proxy/:type/:name
  250. func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
  251. res := GeneralResponse{Code: 200}
  252. params := mux.Vars(r)
  253. proxyType := params["type"]
  254. name := params["name"]
  255. defer func() {
  256. log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  257. w.WriteHeader(res.Code)
  258. if len(res.Msg) > 0 {
  259. _, _ = w.Write([]byte(res.Msg))
  260. }
  261. }()
  262. log.Infof("Http request: [%s]", r.URL.Path)
  263. var proxyStatsResp GetProxyStatsResp
  264. proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
  265. if res.Code != 200 {
  266. return
  267. }
  268. buf, _ := json.Marshal(&proxyStatsResp)
  269. res.Msg = string(buf)
  270. }
  271. func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
  272. proxyInfo.Name = proxyName
  273. ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
  274. if ps == nil {
  275. code = 404
  276. msg = "no proxy info found"
  277. } else {
  278. if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
  279. content, err := json.Marshal(pxy.GetConfigurer())
  280. if err != nil {
  281. log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
  282. code = 400
  283. msg = "parse conf error"
  284. return
  285. }
  286. proxyInfo.Conf = getConfByType(ps.Type)
  287. if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
  288. log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
  289. code = 400
  290. msg = "parse conf error"
  291. return
  292. }
  293. proxyInfo.Status = "online"
  294. } else {
  295. proxyInfo.Status = "offline"
  296. }
  297. proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
  298. proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
  299. proxyInfo.CurConns = ps.CurConns
  300. proxyInfo.LastStartTime = ps.LastStartTime
  301. proxyInfo.LastCloseTime = ps.LastCloseTime
  302. code = 200
  303. }
  304. return
  305. }
  306. // /api/traffic/:name
  307. type GetProxyTrafficResp struct {
  308. Name string `json:"name"`
  309. TrafficIn []int64 `json:"trafficIn"`
  310. TrafficOut []int64 `json:"trafficOut"`
  311. }
  312. func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
  313. res := GeneralResponse{Code: 200}
  314. params := mux.Vars(r)
  315. name := params["name"]
  316. defer func() {
  317. log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  318. w.WriteHeader(res.Code)
  319. if len(res.Msg) > 0 {
  320. _, _ = w.Write([]byte(res.Msg))
  321. }
  322. }()
  323. log.Infof("Http request: [%s]", r.URL.Path)
  324. trafficResp := GetProxyTrafficResp{}
  325. trafficResp.Name = name
  326. proxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)
  327. if proxyTrafficInfo == nil {
  328. res.Code = 404
  329. res.Msg = "no proxy info found"
  330. return
  331. }
  332. trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
  333. trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
  334. buf, _ := json.Marshal(&trafficResp)
  335. res.Msg = string(buf)
  336. }
  337. // DELETE /api/proxies?status=offline
  338. func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
  339. res := GeneralResponse{Code: 200}
  340. log.Infof("Http request: [%s]", r.URL.Path)
  341. defer func() {
  342. log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  343. w.WriteHeader(res.Code)
  344. if len(res.Msg) > 0 {
  345. _, _ = w.Write([]byte(res.Msg))
  346. }
  347. }()
  348. status := r.URL.Query().Get("status")
  349. if status != "offline" {
  350. res.Code = 400
  351. res.Msg = "status only support offline"
  352. return
  353. }
  354. cleared, total := mem.StatsCollector.ClearOfflineProxies()
  355. log.Infof("cleared [%d] offline proxies, total [%d] proxies", cleared, total)
  356. }