1
0

dashboard_api.go 12 KB

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