admin_api.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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 client
  15. import (
  16. "cmp"
  17. "encoding/json"
  18. "fmt"
  19. "io"
  20. "net"
  21. "net/http"
  22. "os"
  23. "slices"
  24. "strconv"
  25. "time"
  26. "github.com/fatedier/frp/client/proxy"
  27. "github.com/fatedier/frp/pkg/config"
  28. "github.com/fatedier/frp/pkg/config/v1/validation"
  29. httppkg "github.com/fatedier/frp/pkg/util/http"
  30. "github.com/fatedier/frp/pkg/util/log"
  31. netpkg "github.com/fatedier/frp/pkg/util/net"
  32. )
  33. type GeneralResponse struct {
  34. Code int
  35. Msg string
  36. }
  37. func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
  38. helper.Router.HandleFunc("/healthz", svr.healthz)
  39. subRouter := helper.Router.NewRoute().Subrouter()
  40. subRouter.Use(helper.AuthMiddleware.Middleware)
  41. // api, see admin_api.go
  42. subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
  43. subRouter.HandleFunc("/api/stop", svr.apiStop).Methods("POST")
  44. subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
  45. subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
  46. subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
  47. // view
  48. subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
  49. subRouter.PathPrefix("/static/").Handler(
  50. netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
  51. ).Methods("GET")
  52. subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  53. http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
  54. })
  55. }
  56. // /healthz
  57. func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
  58. w.WriteHeader(200)
  59. }
  60. // GET /api/reload
  61. func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
  62. res := GeneralResponse{Code: 200}
  63. strictConfigMode := false
  64. strictStr := r.URL.Query().Get("strictConfig")
  65. if strictStr != "" {
  66. strictConfigMode, _ = strconv.ParseBool(strictStr)
  67. }
  68. log.Infof("api request [/api/reload]")
  69. defer func() {
  70. log.Infof("api response [/api/reload], code [%d]", res.Code)
  71. w.WriteHeader(res.Code)
  72. if len(res.Msg) > 0 {
  73. _, _ = w.Write([]byte(res.Msg))
  74. }
  75. }()
  76. cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.configFilePath, strictConfigMode)
  77. if err != nil {
  78. res.Code = 400
  79. res.Msg = err.Error()
  80. log.Warnf("reload frpc proxy config error: %s", res.Msg)
  81. return
  82. }
  83. if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs, svr.unsafeFeatures); err != nil {
  84. res.Code = 400
  85. res.Msg = err.Error()
  86. log.Warnf("reload frpc proxy config error: %s", res.Msg)
  87. return
  88. }
  89. if err := svr.UpdateAllConfigurer(proxyCfgs, visitorCfgs); err != nil {
  90. res.Code = 500
  91. res.Msg = err.Error()
  92. log.Warnf("reload frpc proxy config error: %s", res.Msg)
  93. return
  94. }
  95. log.Infof("success reload conf")
  96. }
  97. // POST /api/stop
  98. func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
  99. res := GeneralResponse{Code: 200}
  100. log.Infof("api request [/api/stop]")
  101. defer func() {
  102. log.Infof("api response [/api/stop], code [%d]", res.Code)
  103. w.WriteHeader(res.Code)
  104. if len(res.Msg) > 0 {
  105. _, _ = w.Write([]byte(res.Msg))
  106. }
  107. }()
  108. go svr.GracefulClose(100 * time.Millisecond)
  109. }
  110. type StatusResp map[string][]ProxyStatusResp
  111. type ProxyStatusResp struct {
  112. Name string `json:"name"`
  113. Type string `json:"type"`
  114. Status string `json:"status"`
  115. Err string `json:"err"`
  116. LocalAddr string `json:"local_addr"`
  117. Plugin string `json:"plugin"`
  118. RemoteAddr string `json:"remote_addr"`
  119. }
  120. func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
  121. psr := ProxyStatusResp{
  122. Name: status.Name,
  123. Type: status.Type,
  124. Status: status.Phase,
  125. Err: status.Err,
  126. }
  127. baseCfg := status.Cfg.GetBaseConfig()
  128. if baseCfg.LocalPort != 0 {
  129. psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
  130. }
  131. psr.Plugin = baseCfg.Plugin.Type
  132. if status.Err == "" {
  133. psr.RemoteAddr = status.RemoteAddr
  134. if slices.Contains([]string{"tcp", "udp"}, status.Type) {
  135. psr.RemoteAddr = serverAddr + psr.RemoteAddr
  136. }
  137. }
  138. return psr
  139. }
  140. // GET /api/status
  141. func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
  142. var (
  143. buf []byte
  144. res StatusResp = make(map[string][]ProxyStatusResp)
  145. )
  146. log.Infof("http request [/api/status]")
  147. defer func() {
  148. log.Infof("http response [/api/status]")
  149. w.Header().Set("Content-Type", "application/json")
  150. buf, _ = json.Marshal(&res)
  151. _, _ = w.Write(buf)
  152. }()
  153. svr.ctlMu.RLock()
  154. ctl := svr.ctl
  155. svr.ctlMu.RUnlock()
  156. if ctl == nil {
  157. return
  158. }
  159. ps := ctl.pm.GetAllProxyStatus()
  160. for _, status := range ps {
  161. res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.common.ServerAddr))
  162. }
  163. for _, arrs := range res {
  164. if len(arrs) <= 1 {
  165. continue
  166. }
  167. slices.SortFunc(arrs, func(a, b ProxyStatusResp) int {
  168. return cmp.Compare(a.Name, b.Name)
  169. })
  170. }
  171. }
  172. // GET /api/config
  173. func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
  174. res := GeneralResponse{Code: 200}
  175. log.Infof("http get request [/api/config]")
  176. defer func() {
  177. log.Infof("http get response [/api/config], code [%d]", res.Code)
  178. w.WriteHeader(res.Code)
  179. if len(res.Msg) > 0 {
  180. _, _ = w.Write([]byte(res.Msg))
  181. }
  182. }()
  183. if svr.configFilePath == "" {
  184. res.Code = 400
  185. res.Msg = "frpc has no config file path"
  186. log.Warnf("%s", res.Msg)
  187. return
  188. }
  189. content, err := os.ReadFile(svr.configFilePath)
  190. if err != nil {
  191. res.Code = 400
  192. res.Msg = err.Error()
  193. log.Warnf("load frpc config file error: %s", res.Msg)
  194. return
  195. }
  196. res.Msg = string(content)
  197. }
  198. // PUT /api/config
  199. func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
  200. res := GeneralResponse{Code: 200}
  201. log.Infof("http put request [/api/config]")
  202. defer func() {
  203. log.Infof("http put response [/api/config], code [%d]", res.Code)
  204. w.WriteHeader(res.Code)
  205. if len(res.Msg) > 0 {
  206. _, _ = w.Write([]byte(res.Msg))
  207. }
  208. }()
  209. // get new config content
  210. body, err := io.ReadAll(r.Body)
  211. if err != nil {
  212. res.Code = 400
  213. res.Msg = fmt.Sprintf("read request body error: %v", err)
  214. log.Warnf("%s", res.Msg)
  215. return
  216. }
  217. if len(body) == 0 {
  218. res.Code = 400
  219. res.Msg = "body can't be empty"
  220. log.Warnf("%s", res.Msg)
  221. return
  222. }
  223. if err := os.WriteFile(svr.configFilePath, body, 0o600); err != nil {
  224. res.Code = 500
  225. res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
  226. log.Warnf("%s", res.Msg)
  227. return
  228. }
  229. }