You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Golang中HTTP(S)与SOCKS5代理互转及长连接问题咨询

Golang中HTTP(S)与SOCKS5代理互转及长连接问题咨询

嘿,Jason,咱们一个个拆解你的问题,这些都是Golang代理场景里很常见的实操坑,我帮你捋得明明白白:

问题1:SOCKS5是否支持Keepalive?长连接25-26秒断开的原因是什么?

首先明确:SOCKS5协议本身是支持TCP Keepalive的,但你遇到的25-26秒断开问题,大概率不是协议本身的限制,而是以下几个常见原因:

  • 代理服务器的空闲超时策略:很多公共或轻量SOCKS5代理会设置20-30秒的空闲连接超时(25-26秒刚好在这个区间),目的是避免空闲连接占用服务器资源。如果你的请求长时间没有数据交互,代理会主动断开连接。
  • Golang客户端的Keepalive参数未适配:Golang默认开启TCP Keepalive,但默认的探测间隔(大概15秒?不对,默认是2小时?哦不对,Golang的默认SetKeepAlivePeriod是0,会用系统默认值,很多系统默认是2小时)远长于代理的超时时间,导致代理先判定连接空闲并断开。你可以手动调整参数,比如:
    conn, err := dialer.Dial("tcp", targetAddr)
    if err != nil {
        // 处理错误
    }
    tcpConn, ok := conn.(*net.TCPConn)
    if ok {
        tcpConn.SetKeepAlive(true)
        tcpConn.SetKeepAlivePeriod(15 * time.Second) // 比代理超时短,定期发心跳
    }
    
  • 中间网络设备的超时:比如NAT网关、防火墙这类设备,通常会对空闲TCP连接设置20-30秒的超时,直接断开连接。这种情况下光调代理或客户端没用,得在应用层加心跳(比如定期发一个HEAD请求)维持连接活跃。

问题2:将HTTP(S)代理请求转成SOCKS5再转回,有没有实现方案?

当然有,本质是搭建一个代理中转服务,流程是:客户端→HTTP(S)代理中转服务→SOCKS5代理→目标服务器,然后响应原路返回。核心要做两件事:

  1. 让中转服务成为一个HTTP(S)代理,接收客户端的HTTP(S)请求;
  2. 中转服务将请求通过SOCKS5代理转发到目标服务器,再把响应转回给客户端。

具体实现可以看下面问题3里的中转服务代码,刚好能实现这个需求。

问题3:Golang中如何实现HTTP(S)转SOCKS5?

分两种场景给你讲:

场景1:客户端通过SOCKS5代理发送HTTP(S)请求

这是最常见的场景,用golang.org/x/net/proxy库就能快速实现:

import (
    "golang.org/x/net/proxy"
    "net/http"
    "time"
)

func main() {
    // 创建SOCKS5拨号器,替换成你的SOCKS5代理地址
    socksDialer, err := proxy.SOCKS5("tcp", "127.0.0.1:1080", nil, proxy.Direct)
    if err != nil {
        panic("创建SOCKS5拨号器失败: " + err.Error())
    }

    // 配置HTTP客户端,使用SOCKS5拨号器
    httpClient := &http.Client{
        Transport: &http.Transport{
            DialContext: socksDialer.DialContext,
            // 可选:调整连接池和超时参数,适配长连接
            MaxIdleConns:        10,
            IdleConnTimeout:     30 * time.Second,
            TLSHandshakeTimeout: 10 * time.Second,
        },
        Timeout: 60 * time.Second,
    }

    // 发送HTTP请求
    resp, err := httpClient.Get("https://example.com")
    if err != nil {
        panic("请求失败: " + err.Error())
    }
    defer resp.Body.Close()

    // 这里可以处理响应内容
}

HTTPS请求不需要额外配置,因为拨号器已经负责通过SOCKS5建立到目标服务器的TCP连接,后续的TLS握手由HTTP客户端自动处理。

场景2:搭建中转服务,实现HTTP(S)→SOCKS5→HTTP(S)的转发

就是你第二个问题要的方案,代码示例如下:

import (
    "io"
    "net"
    "net/http"
    "golang.org/x/net/proxy"
)

func main() {
    // 初始化SOCKS5拨号器,替换成你的SOCKS5代理地址
    socksDialer, err := proxy.SOCKS5("tcp", "127.0.0.1:1080", nil, proxy.Direct)
    if err != nil {
        panic("初始化SOCKS5拨号器失败: " + err.Error())
    }

    // 处理普通HTTP请求转发
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 构建通过SOCKS5的HTTP客户端
        client := &http.Client{
            Transport: &http.Transport{
                DialContext: socksDialer.DialContext,
            },
        }

        // 重置请求的URI和Scheme,避免代理前缀干扰
        r.RequestURI = ""
        r.URL.Scheme = "http"
        r.URL.Host = r.Host

        // 转发请求到目标服务器
        resp, err := client.Do(r)
        if err != nil {
            http.Error(w, "转发请求失败: "+err.Error(), http.StatusBadGateway)
            return
        }
        defer resp.Body.Close()

        // 把目标服务器的响应头和内容返回给客户端
        for k, v := range resp.Header {
            w.Header()[k] = v
        }
        w.WriteHeader(resp.StatusCode)
        io.Copy(w, resp.Body)
    })

    // 处理HTTPS的CONNECT请求(隧道转发)
    http.HandleFunc("CONNECT", func(w http.ResponseWriter, r *http.Request) {
        // 通过SOCKS5连接目标服务器
        targetConn, err := socksDialer.Dial("tcp", r.Host)
        if err != nil {
            http.Error(w, "连接目标服务器失败: "+err.Error(), http.StatusBadGateway)
            return
        }
        defer targetConn.Close()

        // 告诉客户端隧道已建立
        w.WriteHeader(http.StatusOK)
        // 升级为TCP连接,开始双向透传数据
        hijacker, ok := w.(http.Hijacker)
        if !ok {
            http.Error(w, "不支持连接劫持", http.StatusInternalServerError)
            return
        }
        clientConn, _, err := hijacker.Hijack()
        if err != nil {
            http.Error(w, "劫持连接失败: "+err.Error(), http.StatusInternalServerError)
            return
        }
        defer clientConn.Close()

        // 同时双向复制数据(客户端<->目标服务器)
        go io.Copy(clientConn, targetConn)
        io.Copy(targetConn, clientConn)
    })

    // 启动中转服务,监听8080端口
    err = http.ListenAndServe(":8080", nil)
    if err != nil {
        panic("启动中转服务失败: " + err.Error())
    }
}

启动这个服务后,客户端把HTTP(S)代理设置为localhost:8080,所有请求就会先经过中转服务,再通过SOCKS5代理转发,最后响应原路返回,完美实现你要的“转过去再转回来”的需求。

备注:内容来源于stack exchange,提问作者Jason Smith

火山引擎 最新活动