Go如何向非CONNECT类型Envoy代理发送WebSocket请求
Go WebSocket客户端对接非CONNECT型Envoy代理的推荐方案
核心思路
通过自定义http.Transport拦截WebSocket请求,将请求的实际发送目标替换为Envoy代理地址,但保留原始请求的路径、查询参数及WebSocket相关头信息。这样客户端代码可以完全按照直接访问原始目标的方式编写,无需额外修改请求结构或添加自定义头。
基于gorilla/websocket的实现示例
gorilla/websocket是Go生态中最常用的WebSocket库,支持通过Dialer关联自定义的http.Client,我们可以利用这一点实现请求转发:
package main import ( "net/http" "net/url" "log" "time" "github.com/gorilla/websocket" ) func main() { // 配置Envoy代理地址 proxyAddr := "ws://127.0.0.1:8080" proxyURL, err := url.Parse(proxyAddr) if err != nil { log.Fatalf("解析代理地址失败: %v", err) } // 创建自定义Transport,拦截并修改请求目标 customTransport := &http.Transport{} originalRoundTrip := customTransport.RoundTrip customTransport.RoundTrip = func(req *http.Request) (*http.Response, error) { // 将请求的Scheme和Host替换为代理地址 req.URL.Scheme = proxyURL.Scheme req.URL.Host = proxyURL.Host // 传递原始目标的Host信息给Envoy,用于路由匹配 req.Header.Set("X-Forwarded-Host", req.Host) return originalRoundTrip(req) } // 关联自定义Transport到http.Client client := &http.Client{Transport: customTransport} // 配置WebSocket Dialer,使用自定义Client的网络连接逻辑 dialer := websocket.Dialer{ NetDialContext: client.Transport.(*http.Transport).DialContext, HandshakeTimeout: 10 * time.Second, } // 客户端代码完全按照直接访问原始目标编写 targetWSURL := "ws://your-original-target.com/ws/chat?room=123" conn, resp, err := dialer.Dial(targetWSURL, nil) if err != nil { log.Fatalf("WebSocket连接失败: %v, 响应: %v", err, resp) } defer conn.Close() // 后续WebSocket收发逻辑与直接连接目标完全一致 // 示例:发送消息 err = conn.WriteMessage(websocket.TextMessage, []byte("hello from client")) if err != nil { log.Fatalf("发送消息失败: %v", err) } }
其他WebSocket库的适配方式
只要目标库支持自定义http.Client或http.Transport,都可以套用类似逻辑:
- 若库允许直接传入
http.Client,直接替换其Transport即可 - 若库依赖全局
http.DefaultClient,可以修改http.DefaultClient.Transport实现全局代理(注意可能影响其他HTTP请求)
Envoy端配置配合
确保Envoy监听HTTP端口,并配置路由规则匹配X-Forwarded-Host头或原始请求路径:
# Envoy路由示例 routes: - match: headers: x-forwarded-host: exact: "your-original-target.com" route: cluster: your-target-cluster
Envoy默认支持WebSocket的HTTP升级,无需额外配置监听器类型。
方案优势
相比直接向代理地址发请求并添加自定义头的方式,该方案:
- 客户端代码更简洁直观,与直接访问目标的代码无差异
- 无需硬编码代理地址,可通过配置集中管理
- 保留原始请求的所有上下文信息,便于Envoy进行路由和日志记录
内容的提问来源于stack exchange,提问作者CppNoob
相关产品推荐
相关产品推荐

