1
0

admin_api.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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. "encoding/json"
  17. "fmt"
  18. "io"
  19. "net"
  20. "net/http"
  21. "os"
  22. "sort"
  23. "strconv"
  24. "strings"
  25. "time"
  26. "github.com/samber/lo"
  27. "github.com/fatedier/frp/client/proxy"
  28. "github.com/fatedier/frp/pkg/config"
  29. "github.com/fatedier/frp/pkg/config/v1/validation"
  30. httppkg "github.com/fatedier/frp/pkg/util/http"
  31. "github.com/fatedier/frp/pkg/util/log"
  32. netpkg "github.com/fatedier/frp/pkg/util/net"
  33. )
  34. type GeneralResponse struct {
  35. Code int
  36. Msg string
  37. }
  38. func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
  39. helper.Router.HandleFunc("/healthz", svr.healthz)
  40. subRouter := helper.Router.NewRoute().Subrouter()
  41. subRouter.Use(helper.AuthMiddleware.Middleware)
  42. // api, see admin_api.go
  43. subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
  44. subRouter.HandleFunc("/api/stop", svr.apiStop).Methods("POST")
  45. subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
  46. subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
  47. subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
  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. // /healthz
  58. func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
  59. w.WriteHeader(200)
  60. }
  61. // GET /api/reload
  62. func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
  63. res := GeneralResponse{Code: 200}
  64. strictConfigMode := false
  65. strictStr := r.URL.Query().Get("strictConfig")
  66. if strictStr != "" {
  67. strictConfigMode, _ = strconv.ParseBool(strictStr)
  68. }
  69. log.Info("api request [/api/reload]")
  70. defer func() {
  71. log.Info("api response [/api/reload], code [%d]", res.Code)
  72. w.WriteHeader(res.Code)
  73. if len(res.Msg) > 0 {
  74. _, _ = w.Write([]byte(res.Msg))
  75. }
  76. }()
  77. cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.configFilePath, strictConfigMode)
  78. if err != nil {
  79. res.Code = 400
  80. res.Msg = err.Error()
  81. log.Warn("reload frpc proxy config error: %s", res.Msg)
  82. return
  83. }
  84. if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs); err != nil {
  85. res.Code = 400
  86. res.Msg = err.Error()
  87. log.Warn("reload frpc proxy config error: %s", res.Msg)
  88. return
  89. }
  90. if err := svr.UpdateAllConfigurer(proxyCfgs, visitorCfgs); err != nil {
  91. res.Code = 500
  92. res.Msg = err.Error()
  93. log.Warn("reload frpc proxy config error: %s", res.Msg)
  94. return
  95. }
  96. log.Info("success reload conf")
  97. }
  98. // POST /api/stop
  99. func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
  100. res := GeneralResponse{Code: 200}
  101. log.Info("api request [/api/stop]")
  102. defer func() {
  103. log.Info("api response [/api/stop], code [%d]", res.Code)
  104. w.WriteHeader(res.Code)
  105. if len(res.Msg) > 0 {
  106. _, _ = w.Write([]byte(res.Msg))
  107. }
  108. }()
  109. go svr.GracefulClose(100 * time.Millisecond)
  110. }
  111. type StatusResp map[string][]ProxyStatusResp
  112. type ProxyStatusResp struct {
  113. Name string `json:"name"`
  114. Type string `json:"type"`
  115. Status string `json:"status"`
  116. Err string `json:"err"`
  117. LocalAddr string `json:"local_addr"`
  118. Plugin string `json:"plugin"`
  119. RemoteAddr string `json:"remote_addr"`
  120. }
  121. func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
  122. psr := ProxyStatusResp{
  123. Name: status.Name,
  124. Type: status.Type,
  125. Status: status.Phase,
  126. Err: status.Err,
  127. }
  128. baseCfg := status.Cfg.GetBaseConfig()
  129. if baseCfg.LocalPort != 0 {
  130. psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
  131. }
  132. psr.Plugin = baseCfg.Plugin.Type
  133. if status.Err == "" {
  134. psr.RemoteAddr = status.RemoteAddr
  135. if lo.Contains([]string{"tcp", "udp"}, status.Type) {
  136. psr.RemoteAddr = serverAddr + psr.RemoteAddr
  137. }
  138. }
  139. return psr
  140. }
  141. // GET /api/status
  142. func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
  143. var (
  144. buf []byte
  145. res StatusResp = make(map[string][]ProxyStatusResp)
  146. )
  147. log.Info("Http request [/api/status]")
  148. defer func() {
  149. log.Info("Http response [/api/status]")
  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. sort.Slice(arrs, func(i, j int) bool {
  168. return strings.Compare(arrs[i].Name, arrs[j].Name) < 0
  169. })
  170. }
  171. }
  172. // GET /api/config
  173. func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
  174. res := GeneralResponse{Code: 200}
  175. log.Info("Http get request [/api/config]")
  176. defer func() {
  177. log.Info("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.Warn("%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.Warn("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.Info("Http put request [/api/config]")
  202. defer func() {
  203. log.Info("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.Warn("%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.Warn("%s", res.Msg)
  221. return
  222. }
  223. if err := os.WriteFile(svr.configFilePath, body, 0o644); err != nil {
  224. res.Code = 500
  225. res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
  226. log.Warn("%s", res.Msg)
  227. return
  228. }
  229. }