diff --git a/server/control.go b/server/control.go index 607027c6..832dc5f8 100644 --- a/server/control.go +++ b/server/control.go @@ -94,8 +94,28 @@ func (cm *ControlManager) Close() error { return nil } +// CloseAllProxyByName Finds the tunnel name and closes all tunnels on the same connection. +func (cm *ControlManager) CloseAllProxyByName(proxyName string) error { + cm.mu.RLock() + var target *Control + for _, ctl := range cm.ctlsByRunID { + ctl.mu.RLock() + _, ok := ctl.proxies[proxyName] + ctl.mu.RUnlock() + if ok { + target = ctl + break + } + } + cm.mu.RUnlock() + if target == nil { + return fmt.Errorf("no proxy found with name [%s]", proxyName) + } + return target.Close() +} + // KickByProxyName finds the Control that manages the given proxy (tunnel) name and closes -// the entire control connection (disconnects the frpc). Returns an error if no such proxy is found. +// Bug: The client does not display the kickout message. func (cm *ControlManager) KickByProxyName(proxyName string) error { cm.mu.RLock() var target *Control @@ -113,6 +133,9 @@ func (cm *ControlManager) KickByProxyName(proxyName string) error { if target == nil { return fmt.Errorf("no proxy found with name [%s]", proxyName) } + + xl := target.xl + xl.Infof("kick client with proxy [%s] by server administrator request", proxyName) return target.Close() } diff --git a/server/dashboard_api.go b/server/dashboard_api.go index 933e8f6a..3c6a4340 100644 --- a/server/dashboard_api.go +++ b/server/dashboard_api.go @@ -52,6 +52,7 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) subRouter.HandleFunc("/api/serverinfo", svr.apiServerInfo).Methods("GET") subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET") subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET") + subRouter.HandleFunc("/api/proxy/{name}/close", svr.apiCloseProxyByName).Methods("POST") subRouter.HandleFunc("/api/proxy/{name}/kick", svr.apiKickProxyByName).Methods("POST") subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET") subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE") @@ -334,8 +335,40 @@ func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request res.Msg = string(buf) } +// POST /api/proxy/:name/close +// Close the proxy with given name only. The client connection remains active. +func (svr *Service) apiCloseProxyByName(w http.ResponseWriter, r *http.Request) { + res := GeneralResponse{Code: 200} + params := mux.Vars(r) + name := params["name"] + + defer func() { + log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code) + w.WriteHeader(res.Code) + if len(res.Msg) > 0 { + _, _ = w.Write([]byte(res.Msg)) + } + }() + log.Infof("http request: [%s]", r.URL.Path) + + if name == "" { + res.Code = 400 + res.Msg = "proxy name required" + return + } + + if err := svr.ctlManager.CloseAllProxyByName(name); err != nil { + res.Code = 404 + res.Msg = err.Error() + return + } + + res.Msg = "ok" +} + // POST /api/proxy/:name/kick // Kick the client (frpc) that owns the proxy with given name. +// This will disconnect the entire frpc client and close all its proxies. func (svr *Service) apiKickProxyByName(w http.ResponseWriter, r *http.Request) { res := GeneralResponse{Code: 200} params := mux.Vars(r)