下面的代码是保存为socks2http.go , 执行如下代码
go mod init example.com/m
go mod tidy
GOOS=linux GOARCH=amd64 go build socks2http.go
# 可以通过 go tool dist list 获取
PLATFORMS=(
"linux/amd64"
"linux/arm64"
"darwin/amd64"
"darwin/arm64"
"windows/amd64"
)
# 运行
socks2http -http=:8181 -socks=127.0.0.1:41080
源码如下:
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"time"
"golang.org/x/net/proxy"
)
var (
httpAddr = flag.String("http", ":8080", "HTTP proxy listen address")
socksAddr = flag.String("socks", "127.0.0.1:1080", "SOCKS5 proxy address")
help = flag.Bool("help", false, "Show this help message")
)
func printUsage() {
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0])
fmt.Fprintln(os.Stderr, "Convert a SOCKS5 proxy to an HTTP proxy.")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Options:")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Example:")
fmt.Fprintln(os.Stderr, " ./socks2http -http=:8118 -socks=127.0.0.1:1080")
}
func main() {
// 解析命令行参数
flag.Parse()
// 如果用户请求 help,或没有提供任何非-flag 参数(且未设置有效选项)
// 注意:flag.NArg() == 0 表示没有额外 positional arguments
if *help || (len(os.Args) == 1) {
printUsage()
os.Exit(0)
}
// 创建 SOCKS5 拨号器
socksDialer, err := proxy.SOCKS5("tcp", *socksAddr, nil, &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
})
if err != nil {
log.Fatalf("Failed to create SOCKS5 dialer: %v", err)
}
// 自定义 DialContext 使用 SOCKS5
transport := &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return socksDialer.Dial(network, addr)
},
// 禁用 HTTP/2 避免某些兼容性问题(可选)
ForceAttemptHTTP2: false,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
// 创建 HTTP 代理处理器
proxyHandler := &httpProxyHandler{
transport: transport,
socksAddr: *socksAddr,
}
server := &http.Server{
Addr: *httpAddr,
Handler: proxyHandler,
}
fmt.Printf("HTTP proxy listening on %s, forwarding to SOCKS5 %s\n", *httpAddr, *socksAddr)
log.Fatal(server.ListenAndServe())
}
// httpProxyHandler 实现 HTTP 代理逻辑
type httpProxyHandler struct {
transport *http.Transport
socksAddr string
}
func (h *httpProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 处理 CONNECT 请求(用于 HTTPS)
if r.Method == http.MethodConnect {
h.handleConnect(w, r)
return
}
// 处理普通 HTTP 请求(GET/POST 等)
h.handleHTTP(w, r)
}
func (h *httpProxyHandler) handleConnect(w http.ResponseWriter, r *http.Request) {
// 建立到目标服务器的连接(通过 SOCKS5)
targetConn, err := h.transport.DialContext(context.Background(), "tcp", r.Host)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to connect to %s: %v", r.Host, err), http.StatusServiceUnavailable)
return
}
clientIP, _, _ := net.SplitHostPort(r.RemoteAddr)
log.Printf("[HTTPS] Client %s -> Target %s %s %s", clientIP, r.URL.Host, r.Method, r.URL.Path)
defer targetConn.Close()
// 通知客户端连接已建立
w.WriteHeader(http.StatusOK)
// Hijack HTTP 连接,进行双向数据转发
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
log.Printf("Hijack failed: %v", err)
return
}
defer clientConn.Close()
// 双向复制数据
go io.Copy(targetConn, clientConn)
io.Copy(clientConn, targetConn)
}
func (h *httpProxyHandler) handleHTTP(w http.ResponseWriter, r *http.Request) {
clientIP, _, _ := net.SplitHostPort(r.RemoteAddr)
log.Printf("[HTTP] Client %s -> Target %s %s %s", clientIP, r.URL.Host, r.Method, r.URL.Path)
// 修改请求 URL 为绝对路径(代理要求)
if r.URL.Scheme == "" {
r.URL.Scheme = "http"
}
if r.URL.Host == "" {
r.URL.Host = r.Host
}
// 使用自定义 transport 发送请求(走 SOCKS5)
resp, err := h.transport.RoundTrip(r)
if err != nil {
http.Error(w, fmt.Sprintf("Request failed: %v", err), http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
// 复制响应头
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
w.WriteHeader(resp.StatusCode)
// 复制响应体
io.Copy(w, resp.Body)
}