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. "encoding/json"
  17. "net/http"
  18. "sort"
  19. "github.com/gorilla/mux"
  20. "github.com/prometheus/client_golang/prometheus/promhttp"
  21. "github.com/fatedier/frp/pkg/config/types"
  22. v1 "github.com/fatedier/frp/pkg/config/v1"
  23. "github.com/fatedier/frp/pkg/metrics/mem"
  24. httppkg "github.com/fatedier/frp/pkg/util/http"
  25. "github.com/fatedier/frp/pkg/util/log"
  26. netpkg "github.com/fatedier/frp/pkg/util/net"
  27. "github.com/fatedier/frp/pkg/util/version"
  28. )
  29. // TODO(fatedier): add an API to clean status of all offline proxies.
  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.Info("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.Info("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. }
  128. type UDPOutConf struct {
  129. BaseOutConf
  130. RemotePort int `json:"remotePort"`
  131. }
  132. type HTTPOutConf struct {
  133. BaseOutConf
  134. v1.DomainConfig
  135. Locations []string `json:"locations"`
  136. HostHeaderRewrite string `json:"hostHeaderRewrite"`
  137. }
  138. type HTTPSOutConf struct {
  139. BaseOutConf
  140. v1.DomainConfig
  141. }
  142. type STCPOutConf struct {
  143. BaseOutConf
  144. }
  145. type XTCPOutConf struct {
  146. BaseOutConf
  147. }
  148. func getConfByType(proxyType string) any {
  149. switch v1.ProxyType(proxyType) {
  150. case v1.ProxyTypeTCP:
  151. return &TCPOutConf{}
  152. case v1.ProxyTypeTCPMUX:
  153. return &TCPMuxOutConf{}
  154. case v1.ProxyTypeUDP:
  155. return &UDPOutConf{}
  156. case v1.ProxyTypeHTTP:
  157. return &HTTPOutConf{}
  158. case v1.ProxyTypeHTTPS:
  159. return &HTTPSOutConf{}
  160. case v1.ProxyTypeSTCP:
  161. return &STCPOutConf{}
  162. case v1.ProxyTypeXTCP:
  163. return &XTCPOutConf{}
  164. default:
  165. return nil
  166. }
  167. }
  168. // Get proxy info.
  169. type ProxyStatsInfo struct {
  170. Name string `json:"name"`
  171. Conf interface{} `json:"conf"`
  172. ClientVersion string `json:"clientVersion,omitempty"`
  173. TodayTrafficIn int64 `json:"todayTrafficIn"`
  174. TodayTrafficOut int64 `json:"todayTrafficOut"`
  175. CurConns int64 `json:"curConns"`
  176. LastStartTime string `json:"lastStartTime"`
  177. LastCloseTime string `json:"lastCloseTime"`
  178. Status string `json:"status"`
  179. }
  180. type GetProxyInfoResp struct {
  181. Proxies []*ProxyStatsInfo `json:"proxies"`
  182. }
  183. // /api/proxy/:type
  184. func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
  185. res := GeneralResponse{Code: 200}
  186. params := mux.Vars(r)
  187. proxyType := params["type"]
  188. defer func() {
  189. log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  190. w.WriteHeader(res.Code)
  191. if len(res.Msg) > 0 {
  192. _, _ = w.Write([]byte(res.Msg))
  193. }
  194. }()
  195. log.Info("Http request: [%s]", r.URL.Path)
  196. proxyInfoResp := GetProxyInfoResp{}
  197. proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
  198. sort.Slice(proxyInfoResp.Proxies, func(i, j int) bool {
  199. return proxyInfoResp.Proxies[i].Name < proxyInfoResp.Proxies[j].Name
  200. })
  201. buf, _ := json.Marshal(&proxyInfoResp)
  202. res.Msg = string(buf)
  203. }
  204. func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
  205. proxyStats := mem.StatsCollector.GetProxiesByType(proxyType)
  206. proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
  207. for _, ps := range proxyStats {
  208. proxyInfo := &ProxyStatsInfo{}
  209. if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok {
  210. content, err := json.Marshal(pxy.GetConfigurer())
  211. if err != nil {
  212. log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
  213. continue
  214. }
  215. proxyInfo.Conf = getConfByType(ps.Type)
  216. if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
  217. log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
  218. continue
  219. }
  220. proxyInfo.Status = "online"
  221. if pxy.GetLoginMsg() != nil {
  222. proxyInfo.ClientVersion = pxy.GetLoginMsg().Version
  223. }
  224. } else {
  225. proxyInfo.Status = "offline"
  226. }
  227. proxyInfo.Name = ps.Name
  228. proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
  229. proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
  230. proxyInfo.CurConns = ps.CurConns
  231. proxyInfo.LastStartTime = ps.LastStartTime
  232. proxyInfo.LastCloseTime = ps.LastCloseTime
  233. proxyInfos = append(proxyInfos, proxyInfo)
  234. }
  235. return
  236. }
  237. // Get proxy info by name.
  238. type GetProxyStatsResp struct {
  239. Name string `json:"name"`
  240. Conf interface{} `json:"conf"`
  241. TodayTrafficIn int64 `json:"todayTrafficIn"`
  242. TodayTrafficOut int64 `json:"todayTrafficOut"`
  243. CurConns int64 `json:"curConns"`
  244. LastStartTime string `json:"lastStartTime"`
  245. LastCloseTime string `json:"lastCloseTime"`
  246. Status string `json:"status"`
  247. }
  248. // /api/proxy/:type/:name
  249. func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
  250. res := GeneralResponse{Code: 200}
  251. params := mux.Vars(r)
  252. proxyType := params["type"]
  253. name := params["name"]
  254. defer func() {
  255. log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  256. w.WriteHeader(res.Code)
  257. if len(res.Msg) > 0 {
  258. _, _ = w.Write([]byte(res.Msg))
  259. }
  260. }()
  261. log.Info("Http request: [%s]", r.URL.Path)
  262. var proxyStatsResp GetProxyStatsResp
  263. proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
  264. if res.Code != 200 {
  265. return
  266. }
  267. buf, _ := json.Marshal(&proxyStatsResp)
  268. res.Msg = string(buf)
  269. }
  270. func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
  271. proxyInfo.Name = proxyName
  272. ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
  273. if ps == nil {
  274. code = 404
  275. msg = "no proxy info found"
  276. } else {
  277. if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
  278. content, err := json.Marshal(pxy.GetConfigurer())
  279. if err != nil {
  280. log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
  281. code = 400
  282. msg = "parse conf error"
  283. return
  284. }
  285. proxyInfo.Conf = getConfByType(ps.Type)
  286. if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
  287. log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
  288. code = 400
  289. msg = "parse conf error"
  290. return
  291. }
  292. proxyInfo.Status = "online"
  293. } else {
  294. proxyInfo.Status = "offline"
  295. }
  296. proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
  297. proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
  298. proxyInfo.CurConns = ps.CurConns
  299. proxyInfo.LastStartTime = ps.LastStartTime
  300. proxyInfo.LastCloseTime = ps.LastCloseTime
  301. code = 200
  302. }
  303. return
  304. }
  305. // /api/traffic/:name
  306. type GetProxyTrafficResp struct {
  307. Name string `json:"name"`
  308. TrafficIn []int64 `json:"trafficIn"`
  309. TrafficOut []int64 `json:"trafficOut"`
  310. }
  311. func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
  312. res := GeneralResponse{Code: 200}
  313. params := mux.Vars(r)
  314. name := params["name"]
  315. defer func() {
  316. log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  317. w.WriteHeader(res.Code)
  318. if len(res.Msg) > 0 {
  319. _, _ = w.Write([]byte(res.Msg))
  320. }
  321. }()
  322. log.Info("Http request: [%s]", r.URL.Path)
  323. trafficResp := GetProxyTrafficResp{}
  324. trafficResp.Name = name
  325. proxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)
  326. if proxyTrafficInfo == nil {
  327. res.Code = 404
  328. res.Msg = "no proxy info found"
  329. return
  330. }
  331. trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
  332. trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
  333. buf, _ := json.Marshal(&trafficResp)
  334. res.Msg = string(buf)
  335. }
  336. // DELETE /api/proxies?status=offline
  337. func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
  338. res := GeneralResponse{Code: 200}
  339. log.Info("Http request: [%s]", r.URL.Path)
  340. defer func() {
  341. log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
  342. w.WriteHeader(res.Code)
  343. if len(res.Msg) > 0 {
  344. _, _ = w.Write([]byte(res.Msg))
  345. }
  346. }()
  347. status := r.URL.Query().Get("status")
  348. if status != "offline" {
  349. res.Code = 400
  350. res.Msg = "status only support offline"
  351. return
  352. }
  353. cleared, total := mem.StatsCollector.ClearOfflineProxies()
  354. log.Info("cleared [%d] offline proxies, total [%d] proxies", cleared, total)
  355. }