diff --git a/README_zh.md b/README_zh.md
index 95ede3ee..a07e840c 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -11,11 +11,6 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
Gold Sponsors
-
-
-
-
-
diff --git a/Release.md b/Release.md
index c1649b92..d0414efe 100644
--- a/Release.md
+++ b/Release.md
@@ -1,5 +1,8 @@
### Features
* Added a new plugin `tls2raw`: Enables TLS termination and forwarding of decrypted raw traffic to local service.
+* Added a default timeout of 30 seconds for the frpc subcommands to prevent commands from being stuck for a long time due to network issues.
+
+### Fixes
* Fixed the issue that when `loginFailExit = false`, the frpc stop command cannot be stopped correctly if the server is not successfully connected after startup.
diff --git a/cmd/frpc/sub/admin.go b/cmd/frpc/sub/admin.go
index 5d478d44..abe80635 100644
--- a/cmd/frpc/sub/admin.go
+++ b/cmd/frpc/sub/admin.go
@@ -15,9 +15,11 @@
package sub
import (
+ "context"
"fmt"
"os"
"strings"
+ "time"
"github.com/rodaine/table"
"github.com/spf13/cobra"
@@ -27,24 +29,24 @@ import (
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
)
+var adminAPITimeout = 30 * time.Second
+
func init() {
- rootCmd.AddCommand(NewAdminCommand(
- "reload",
- "Hot-Reload frpc configuration",
- ReloadHandler,
- ))
+ commands := []struct {
+ name string
+ description string
+ handler func(*v1.ClientCommonConfig) error
+ }{
+ {"reload", "Hot-Reload frpc configuration", ReloadHandler},
+ {"status", "Overview of all proxies status", StatusHandler},
+ {"stop", "Stop the running frpc", StopHandler},
+ }
- rootCmd.AddCommand(NewAdminCommand(
- "status",
- "Overview of all proxies status",
- StatusHandler,
- ))
-
- rootCmd.AddCommand(NewAdminCommand(
- "stop",
- "Stop the running frpc",
- StopHandler,
- ))
+ for _, cmdConfig := range commands {
+ cmd := NewAdminCommand(cmdConfig.name, cmdConfig.description, cmdConfig.handler)
+ cmd.Flags().DurationVar(&adminAPITimeout, "api-timeout", adminAPITimeout, "Timeout for admin API calls")
+ rootCmd.AddCommand(cmd)
+ }
}
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
@@ -73,7 +75,9 @@ func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) er
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
- if err := client.Reload(strictConfigMode); err != nil {
+ ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
+ defer cancel()
+ if err := client.Reload(ctx, strictConfigMode); err != nil {
return err
}
fmt.Println("reload success")
@@ -83,7 +87,9 @@ func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
- res, err := client.GetAllProxyStatus()
+ ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
+ defer cancel()
+ res, err := client.GetAllProxyStatus(ctx)
if err != nil {
return err
}
@@ -109,7 +115,9 @@ func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
- if err := client.Stop(); err != nil {
+ ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
+ defer cancel()
+ if err := client.Stop(ctx); err != nil {
return err
}
fmt.Println("stop success")
diff --git a/pkg/sdk/client/client.go b/pkg/sdk/client/client.go
index 57bf7746..13713e27 100644
--- a/pkg/sdk/client/client.go
+++ b/pkg/sdk/client/client.go
@@ -1,6 +1,7 @@
package client
import (
+ "context"
"encoding/json"
"fmt"
"io"
@@ -31,8 +32,8 @@ func (c *Client) SetAuth(user, pwd string) {
c.authPwd = pwd
}
-func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
- req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
+func (c *Client) GetProxyStatus(ctx context.Context, name string) (*client.ProxyStatusResp, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil)
if err != nil {
return nil, err
}
@@ -54,8 +55,8 @@ func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
return nil, fmt.Errorf("no proxy status found")
}
-func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
- req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
+func (c *Client) GetAllProxyStatus(ctx context.Context) (client.StatusResp, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil)
if err != nil {
return nil, err
}
@@ -70,7 +71,7 @@ func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
return allStatus, nil
}
-func (c *Client) Reload(strictMode bool) error {
+func (c *Client) Reload(ctx context.Context, strictMode bool) error {
v := url.Values{}
if strictMode {
v.Set("strictConfig", "true")
@@ -79,7 +80,7 @@ func (c *Client) Reload(strictMode bool) error {
if len(v) > 0 {
queryStr = "?" + v.Encode()
}
- req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload"+queryStr, nil)
+ req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/reload"+queryStr, nil)
if err != nil {
return err
}
@@ -87,8 +88,8 @@ func (c *Client) Reload(strictMode bool) error {
return err
}
-func (c *Client) Stop() error {
- req, err := http.NewRequest("POST", "http://"+c.address+"/api/stop", nil)
+func (c *Client) Stop(ctx context.Context) error {
+ req, err := http.NewRequestWithContext(ctx, "POST", "http://"+c.address+"/api/stop", nil)
if err != nil {
return err
}
@@ -96,16 +97,16 @@ func (c *Client) Stop() error {
return err
}
-func (c *Client) GetConfig() (string, error) {
- req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil)
+func (c *Client) GetConfig(ctx context.Context) (string, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/config", nil)
if err != nil {
return "", err
}
return c.do(req)
}
-func (c *Client) UpdateConfig(content string) error {
- req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
+func (c *Client) UpdateConfig(ctx context.Context, content string) error {
+ req, err := http.NewRequestWithContext(ctx, "PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
if err != nil {
return err
}
diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go
index 561a52e7..a60d71d2 100644
--- a/pkg/util/version/version.go
+++ b/pkg/util/version/version.go
@@ -14,7 +14,7 @@
package version
-var version = "0.59.0"
+var version = "0.60.0"
func Full() string {
return version
diff --git a/test/e2e/legacy/basic/client.go b/test/e2e/legacy/basic/client.go
index 98f675b8..daed8b22 100644
--- a/test/e2e/legacy/basic/client.go
+++ b/test/e2e/legacy/basic/client.go
@@ -1,6 +1,7 @@
package basic
import (
+ "context"
"fmt"
"strconv"
"strings"
@@ -54,7 +55,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
framework.NewRequestExpect(f).Port(p3Port).Ensure()
client := f.APIClientForFrpc(adminPort)
- conf, err := client.GetConfig()
+ conf, err := client.GetConfig(context.Background())
framework.ExpectNoError(err)
newP2Port := f.AllocPort()
@@ -65,10 +66,10 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
newClientConf = newClientConf[:p3Index]
}
- err = client.UpdateConfig(newClientConf)
+ err = client.UpdateConfig(context.Background(), newClientConf)
framework.ExpectNoError(err)
- err = client.Reload(true)
+ err = client.Reload(context.Background(), true)
framework.ExpectNoError(err)
time.Sleep(time.Second)
@@ -120,7 +121,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
framework.NewRequestExpect(f).Port(testPort).Ensure()
client := f.APIClientForFrpc(adminPort)
- err := client.Stop()
+ err := client.Stop(context.Background())
framework.ExpectNoError(err)
time.Sleep(3 * time.Second)
diff --git a/test/e2e/legacy/basic/server.go b/test/e2e/legacy/basic/server.go
index 62bfd62a..4399439d 100644
--- a/test/e2e/legacy/basic/server.go
+++ b/test/e2e/legacy/basic/server.go
@@ -1,6 +1,7 @@
package basic
import (
+ "context"
"fmt"
"net"
"strconv"
@@ -101,7 +102,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
client := f.APIClientForFrpc(adminPort)
// tcp random port
- status, err := client.GetProxyStatus("tcp")
+ status, err := client.GetProxyStatus(context.Background(), "tcp")
framework.ExpectNoError(err)
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
@@ -112,7 +113,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
framework.NewRequestExpect(f).Port(port).Ensure()
// udp random port
- status, err = client.GetProxyStatus("udp")
+ status, err = client.GetProxyStatus(context.Background(), "udp")
framework.ExpectNoError(err)
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
diff --git a/test/e2e/v1/basic/client.go b/test/e2e/v1/basic/client.go
index ef5e262f..fd269d75 100644
--- a/test/e2e/v1/basic/client.go
+++ b/test/e2e/v1/basic/client.go
@@ -1,6 +1,7 @@
package basic
import (
+ "context"
"fmt"
"strconv"
"strings"
@@ -57,7 +58,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
framework.NewRequestExpect(f).Port(p3Port).Ensure()
client := f.APIClientForFrpc(adminPort)
- conf, err := client.GetConfig()
+ conf, err := client.GetConfig(context.Background())
framework.ExpectNoError(err)
newP2Port := f.AllocPort()
@@ -68,10 +69,10 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
newClientConf = newClientConf[:p3Index]
}
- err = client.UpdateConfig(newClientConf)
+ err = client.UpdateConfig(context.Background(), newClientConf)
framework.ExpectNoError(err)
- err = client.Reload(true)
+ err = client.Reload(context.Background(), true)
framework.ExpectNoError(err)
time.Sleep(time.Second)
@@ -124,7 +125,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
framework.NewRequestExpect(f).Port(testPort).Ensure()
client := f.APIClientForFrpc(adminPort)
- err := client.Stop()
+ err := client.Stop(context.Background())
framework.ExpectNoError(err)
time.Sleep(3 * time.Second)
diff --git a/test/e2e/v1/basic/config.go b/test/e2e/v1/basic/config.go
index 7dc3cedb..314e7fc4 100644
--- a/test/e2e/v1/basic/config.go
+++ b/test/e2e/v1/basic/config.go
@@ -1,6 +1,7 @@
package basic
import (
+ "context"
"fmt"
"github.com/onsi/ginkgo/v2"
@@ -72,7 +73,7 @@ var _ = ginkgo.Describe("[Feature: Config]", func() {
client := f.APIClientForFrpc(adminPort)
checkProxyFn := func(name string, localPort, remotePort int) {
- status, err := client.GetProxyStatus(name)
+ status, err := client.GetProxyStatus(context.Background(), name)
framework.ExpectNoError(err)
framework.ExpectContainSubstring(status.LocalAddr, fmt.Sprintf(":%d", localPort))
diff --git a/test/e2e/v1/basic/server.go b/test/e2e/v1/basic/server.go
index fe8af4d1..a3fe5992 100644
--- a/test/e2e/v1/basic/server.go
+++ b/test/e2e/v1/basic/server.go
@@ -1,6 +1,7 @@
package basic
import (
+ "context"
"fmt"
"net"
"strconv"
@@ -112,7 +113,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
client := f.APIClientForFrpc(adminPort)
// tcp random port
- status, err := client.GetProxyStatus("tcp")
+ status, err := client.GetProxyStatus(context.Background(), "tcp")
framework.ExpectNoError(err)
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
@@ -123,7 +124,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
framework.NewRequestExpect(f).Port(port).Ensure()
// udp random port
- status, err = client.GetProxyStatus("udp")
+ status, err = client.GetProxyStatus(context.Background(), "udp")
framework.ExpectNoError(err)
_, portStr, err = net.SplitHostPort(status.RemoteAddr)