You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Golang中通过HTTP代理建立gRPC连接(多代理场景)

实现gRPC通过指定HTTP代理连接443端口TLS服务器

要解决多次使用不同HTTP代理连接同一gRPC服务器的问题,核心是通过grpc.WithDialContext指定自定义dialer,绕过环境变量的全局配置。以下是具体实现步骤和代码示例(以Go语言为例,基于gRPC官方SDK标准实现):

核心思路

自定义dialer需要完成三个关键步骤:

  1. 与指定HTTP代理建立TCP连接
  2. 向代理发送CONNECT请求,建立到目标gRPC服务器的隧道
  3. 在隧道连接之上完成TLS握手,适配服务器的TLS要求

自定义Dialer实现

import (
    "bufio"
    "context"
    "fmt"
    "net"
    "net/http"
    "net/url"
    "strings"
    "crypto/tls"
    "log"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

// proxyDialer 生成针对指定代理的自定义dialer函数
func proxyDialer(proxyAddr string) func(context.Context, string) (net.Conn, error) {
    return func(ctx context.Context, targetAddr string) (net.Conn, error) {
        // 1. 连接到HTTP代理服务器
        proxyConn, err := net.Dial("tcp", proxyAddr)
        if err != nil {
            return nil, fmt.Errorf("connect to proxy failed: %w", err)
        }

        // 2. 发送CONNECT请求建立隧道
        connectReq := &http.Request{
            Method: "CONNECT",
            URL:    &url.URL{Host: targetAddr},
            Host:   targetAddr,
        }
        if err := connectReq.Write(proxyConn); err != nil {
            proxyConn.Close()
            return nil, fmt.Errorf("send connect request failed: %w", err)
        }

        // 读取代理响应,确认隧道建立成功
        resp, err := http.ReadResponse(bufio.NewReader(proxyConn), connectReq)
        if err != nil {
            proxyConn.Close()
            return nil, fmt.Errorf("read proxy response failed: %w", err)
        }
        defer resp.Body.Close()

        if resp.StatusCode != http.StatusOK {
            proxyConn.Close()
            return nil, fmt.Errorf("proxy refused connect: %s", resp.Status)
        }

        // 3. 在隧道上完成TLS握手,匹配服务器证书域名
        tlsConfig := &tls.Config{
            ServerName: strings.Split(targetAddr, ":")[0],
        }
        tlsConn := tls.Client(proxyConn, tlsConfig)
        if err := tlsConn.Handshake(); err != nil {
            proxyConn.Close()
            return nil, fmt.Errorf("tls handshake failed: %w", err)
        }

        return tlsConn, nil
    }
}

使用自定义Dialer创建gRPC连接

func main() {
    targetGRPCServer := "your-grpc-server.com:443"
    // 可根据需求替换为不同代理地址
    proxyAddr1 := "proxy1.example.com:8080"
    proxyAddr2 := "proxy2.example.com:8080"

    // 创建TLS凭证(与gRPC服务器的TLS配置匹配)
    tlsCreds := credentials.NewTLS(&tls.Config{
        ServerName: strings.Split(targetGRPCServer, ":")[0],
    })

    // 使用代理1创建连接
    conn1, err := grpc.DialContext(
        context.Background(),
        targetGRPCServer,
        grpc.WithTransportCredentials(tlsCreds),
        grpc.WithDialContext(proxyDialer(proxyAddr1)),
    )
    if err != nil {
        log.Fatalf("dial with proxy1 failed: %v", err)
    }
    defer conn1.Close()

    // 使用代理2创建另一个连接
    conn2, err := grpc.DialContext(
        context.Background(),
        targetGRPCServer,
        grpc.WithTransportCredentials(tlsCreds),
        grpc.WithDialContext(proxyDialer(proxyAddr2)),
    )
    if err != nil {
        log.Fatalf("dial with proxy2 failed: %v", err)
    }
    defer conn2.Close()

    // 后续使用conn1、conn2分别创建客户端stub调用接口
}

额外适配场景

  • 代理需要认证:在发送CONNECT请求前添加Authorization头:
    connectReq.Header.Add("Proxy-Authorization", "Basic dXNlcjpwYXNzd29yZA==") // 替换为base64编码的user:pass
    
  • 代理本身使用HTTPS:将连接代理的方式改为TLS连接:
    proxyConn, err := tls.Dial("tcp", proxyAddr, &tls.Config{
        ServerName: strings.Split(proxyAddr, ":")[0],
    })
    

内容的提问来源于stack exchange,提问作者chrigeeel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 02:29:53