Skip to content

feat(ws): add WithClientProxy option for explicit proxy configuration#191

Open
qiushido wants to merge 1 commit intolarksuite:v3_mainfrom
qiushido:feat/ws-client-proxy
Open

feat(ws): add WithClientProxy option for explicit proxy configuration#191
qiushido wants to merge 1 commit intolarksuite:v3_mainfrom
qiushido:feat/ws-client-proxy

Conversation

@qiushido
Copy link
Copy Markdown

当前版本的 websocket 客户端在建立长链接时,底层默认使用了 ws.DefaultDialer 和 http.DefaultClient。这导致了在某些需要通过代理访问企业内网或外部服务的场景下,开发者无法为 websocket 客户端单独、显式地配置网络代理。

💡 方案 (Solution)
在 ws.Client 结构体内维护私有的 dialer 和 httpClient 实例,取代原先的全局单例,避免并发情况下的全局污染。
新增 WithClientProxy(proxyUrl string) 配置项:

  • 解析传入的 HTTP 代理地址。
  • 将代理应用到 ws.Client 专属的 Dialer 中。
  • 深拷贝当前环境下的 http.DefaultTransport(若不存在则初始化基础 Transport),将代理应用其中,并构建专属的 http.Client 以用于获取建连 URL 时的 HTTP 请求。
    如果未显式调用 WithClientProxy,默认行为与之前保持一致(支持读取环境变量代理)。

💻 使用示例 (Example)

cli := larkws.NewClient(
    os.Getenv("APP_ID"), 
    os.Getenv("APP_SECRET"),
    larkws.WithEventHandler(eventHandler),
    // 显式配置 WebSocket 客户端的网络代理
    larkws.WithClientProxy("http://127.0.0.1:8888"),
)
err := cli.Start(context.Background())

Copilot AI review requested due to automatic review settings February 28, 2026 08:17
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an explicit proxy configuration option for the WebSocket client by moving away from global singletons (ws.DefaultDialer, http.DefaultClient) and introducing per-client networking primitives.

Changes:

  • Add per-ws.Client instances of *websocket.Dialer and *http.Client to avoid global-side effects.
  • Introduce WithClientProxy(proxyUrl string) to apply an explicit proxy to both the WebSocket dialer and the HTTP client used to fetch the connection URL.
  • Route connection establishment and endpoint discovery through the client-owned dialer/http client.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

proxy, err := url.Parse(proxyUrl)
if err != nil {
return
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url.Parse will succeed for many invalid proxy strings (e.g., missing scheme/host), and the resulting misconfiguration will only fail later at dial/request time. Consider validating that the parsed URL is absolute and has a supported scheme (http/https) and non-empty host, or using a stricter parser so invalid proxy inputs are rejected early.

Suggested change
}
}
// Validate that the proxy URL is absolute, uses http/https, and has a host.
if !proxy.IsAbs() || proxy.Host == "" {
return
}
if proxy.Scheme != "http" && proxy.Scheme != "https" {
return
}

Copilot uses AI. Check for mistakes.
return
}
proxy, err := url.Parse(proxyUrl)
if err != nil {
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When proxyUrl is non-empty but fails to parse, the option silently returns without applying any proxy. This makes misconfiguration hard to diagnose in production; consider surfacing this (e.g., store an error on the client to be returned from Start, or log a warning if a logger has already been provided).

Suggested change
if err != nil {
if err != nil {
if cli.logger != nil {
cli.logger.Warn(context.Background(), "invalid proxy URL %q: %v", proxyUrl, err)
}

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +103
var transport *http.Transport
if t, ok := http.DefaultTransport.(*http.Transport); ok {
transport = t.Clone()
} else {
transport = &http.Transport{}
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback transport = &http.Transport{} does not inherit the sensible defaults used by http.DefaultTransport (notably Dial/TLS handshake timeouts). If http.DefaultTransport has been replaced with a non-*http.Transport, this fallback can lead to connections without timeouts and unexpected hangs; consider initializing the transport with the same defaults as the standard library’s default transport instead of a zero-value Transport.

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +91
func WithClientProxy(proxyUrl string) ClientOption {
return func(cli *Client) {
if proxyUrl == "" {
return
}
proxy, err := url.Parse(proxyUrl)
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithClientProxy takes proxyUrl as a parameter name. For exported Go APIs, initialisms are typically capitalized (e.g., proxyURL) to match Go naming conventions and avoid inconsistent public surface naming.

Suggested change
func WithClientProxy(proxyUrl string) ClientOption {
return func(cli *Client) {
if proxyUrl == "" {
return
}
proxy, err := url.Parse(proxyUrl)
func WithClientProxy(proxyURL string) ClientOption {
return func(cli *Client) {
if proxyURL == "" {
return
}
proxy, err := url.Parse(proxyURL)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants