Go 实现一个 HTTP 代理——正向代理、透明代理、HTTPS 中间人代理
Go 实现一个 HTTP 代理——正向代理、透明代理、HTTPS 中间人代理
适读人群:Go 网络编程进阶、需要实现代理服务的工程师 | 阅读时长:约 18 分钟 | 核心价值:三种代理模式的完整 Go 实现,重点讲 HTTPS MITM 的证书动态生成
去年有个做网络安全培训的团队找我,他们需要一个内部的 HTTP/HTTPS 流量审计工具,用于企业安全培训演示——让学员能实际看到 HTTP 请求的明文内容,包括 HTTPS 解密后的数据。这是一个合法的企业安全培训场景,设备是自己的,证书是自己装的。
我用 Go 给他们实现了这套工具。这篇文章把三种代理模式的实现逻辑完整写出来。
三种代理模式
- 正向代理(Forward Proxy):客户端显式配置,发送
GET http://example.com/path这种绝对 URL - 透明代理(Transparent Proxy):客户端无感知,靠 iptables 重定向流量
- HTTPS 中间人代理(MITM):拦截并解密 HTTPS,需要在客户端安装根证书
正向代理实现
package proxy
import (
"io"
"log"
"net"
"net/http"
"time"
)
// ForwardProxy 正向 HTTP 代理
type ForwardProxy struct {
transport *http.Transport
}
func NewForwardProxy() *ForwardProxy {
return &ForwardProxy{
transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
}
func (p *ForwardProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// CONNECT 方法用于建立 HTTPS 隧道
if r.Method == http.MethodConnect {
p.handleTunnel(w, r)
return
}
// 普通 HTTP 请求
p.handleHTTP(w, r)
}
// handleHTTP 处理普通 HTTP 请求
func (p *ForwardProxy) handleHTTP(w http.ResponseWriter, r *http.Request) {
// 移除代理相关 header
r.Header.Del("Proxy-Connection")
r.Header.Del("Proxy-Authenticate")
r.Header.Del("Proxy-Authorization")
r.Header.Del("Te")
r.Header.Del("Trailers")
r.Header.Del("Transfer-Encoding")
r.Header.Del("Upgrade")
resp, err := p.transport.RoundTrip(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()
// 复制响应 header
for key, values := range resp.Header {
for _, v := range values {
w.Header().Add(key, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
// handleTunnel 处理 CONNECT 隧道(HTTPS 透传,不解密)
func (p *ForwardProxy) handleTunnel(w http.ResponseWriter, r *http.Request) {
// 连接目标服务器
destConn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
// 告知客户端隧道已建立
w.WriteHeader(http.StatusOK)
// 劫持 HTTP 连接
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "hijacking not supported", http.StatusInternalServerError)
destConn.Close()
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
destConn.Close()
return
}
// 双向转发数据
go transfer(destConn, clientConn)
go transfer(clientConn, destConn)
}
func transfer(dest io.WriteCloser, src io.ReadCloser) {
defer dest.Close()
defer src.Close()
io.Copy(dest, src)
}
// Start 启动代理服务
func Start(addr string) error {
proxy := NewForwardProxy()
log.Printf("Forward proxy listening on %s", addr)
return http.ListenAndServe(addr, proxy)
}HTTPS 中间人代理
MITM 代理的核心是:拦截 CONNECT 请求,伪装成目标服务器(用动态生成的证书),解密客户端发来的 HTTPS 流量后,再以真实身份连接目标服务器。
package mitm
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"net/http"
"os"
"sync"
"time"
)
// CertificateAuthority 根证书,用于签发动态证书
type CertificateAuthority struct {
cert *x509.Certificate
key *rsa.PrivateKey
mu sync.RWMutex
// 证书缓存,避免重复生成
cache map[string]*tls.Certificate
}
// NewCA 从文件加载根证书(需要提前生成)
func NewCA(certFile, keyFile string) (*CertificateAuthority, error) {
certPEM, err := os.ReadFile(certFile)
if err != nil {
return nil, err
}
keyPEM, err := os.ReadFile(keyFile)
if err != nil {
return nil, err
}
certBlock, _ := pem.Decode(certPEM)
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return nil, err
}
keyBlock, _ := pem.Decode(keyPEM)
key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
if err != nil {
return nil, err
}
return &CertificateAuthority{
cert: cert,
key: key,
cache: make(map[string]*tls.Certificate),
}, nil
}
// FakeCert 为指定域名生成伪造证书
func (ca *CertificateAuthority) FakeCert(host string) (*tls.Certificate, error) {
ca.mu.RLock()
if cert, ok := ca.cache[host]; ok {
ca.mu.RUnlock()
return cert, nil
}
ca.mu.RUnlock()
ca.mu.Lock()
defer ca.mu.Unlock()
// 生成新的 RSA 密钥对
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
// 构建证书模板
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
Organization: []string{"MITM Proxy"},
CommonName: host,
},
DNSNames: []string{host},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
}
// 如果是 IP
if ip := net.ParseIP(host); ip != nil {
template.IPAddresses = []net.IP{ip}
template.DNSNames = nil
}
// 用 CA 签发
certDER, err := x509.CreateCertificate(rand.Reader, template, ca.cert, &privKey.PublicKey, ca.key)
if err != nil {
return nil, err
}
tlsCert := &tls.Certificate{
Certificate: [][]byte{certDER},
PrivateKey: privKey,
}
ca.cache[host] = tlsCert
return tlsCert, nil
}
// MITMProxy HTTPS 中间人代理
type MITMProxy struct {
ca *CertificateAuthority
transport *http.Transport
interceptor func(req *http.Request, resp *http.Response) // 请求拦截回调
}
func NewMITMProxy(ca *CertificateAuthority, interceptor func(*http.Request, *http.Response)) *MITMProxy {
return &MITMProxy{
ca: ca,
transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
},
interceptor: interceptor,
}
}
func (p *MITMProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodConnect {
p.handleHTTP(w, r)
return
}
// 拦截 CONNECT,进行 MITM
p.handleMITM(w, r)
}
func (p *MITMProxy) handleMITM(w http.ResponseWriter, r *http.Request) {
host, _, _ := net.SplitHostPort(r.Host)
// 为目标域名生成证书
fakeCert, err := p.ca.FakeCert(host)
if err != nil {
http.Error(w, "cert generation failed", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
// Hijack 原始连接
hijacker := w.(http.Hijacker)
clientConn, _, err := hijacker.Hijack()
if err != nil {
return
}
// 用伪造证书和客户端建立 TLS 连接
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*fakeCert},
}
tlsClientConn := tls.Server(clientConn, tlsConfig)
if err := tlsClientConn.Handshake(); err != nil {
tlsClientConn.Close()
return
}
// 把客户端的请求当作普通 HTTP 处理(已解密)
fakeServer := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// 补全 URL
req.URL.Scheme = "https"
req.URL.Host = r.Host
// 转发到真实服务器
resp, err := p.transport.RoundTrip(req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()
// 拦截回调:可以在这里记录、修改
if p.interceptor != nil {
p.interceptor(req, resp)
}
for key, values := range resp.Header {
for _, v := range values {
w.Header().Add(key, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}),
}
fakeServer.Serve(&singleConnListener{conn: tlsClientConn})
}踩坑实录
踩坑 1:HTTPS 隧道双向 transfer 的 goroutine 泄漏
现象:代理跑了一段时间,goroutine 数量持续增长,内存慢慢泄漏。
原因:transfer 函数里 io.Copy 在一端关闭连接后会退出,但另一个 goroutine 还在等待对端数据,而对端因为已经关闭不会再发数据,io.Copy 就一直 block 住了。
解法:使用 SetDeadline 在一端关闭时强制让另一端的 Copy 超时退出,或者用 context 配合 net.Conn 的 SetDeadline。
踩坑 2:伪造证书被浏览器拒绝
现象:在客户端安装了根证书,但访问某些网站时仍然报证书错误。
原因:部分网站启用了 HSTS Preloading,浏览器内置了这些域名不允许自签证书。另外,Certificate Transparency (CT) 日志检查也会导致问题。
解法:这类场景只能通过系统级代理绕过,或者使用专用浏览器实例关闭相关安全策略。
踩坑 3:HTTP/2 协议下 MITM 失败
现象:某些网站 MITM 后响应异常,页面加载不完整。
原因:客户端和服务器之间使用了 HTTP/2,MITM 代理如果不支持 HTTP/2,会导致协议降级或失败。
解法:在伪造的 TLS 配置里明确声明支持 HTTP/2 的 ALPN:
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*fakeCert},
NextProtos: []string{"h2", "http/1.1"},
}合规说明
本文实现的 MITM 代理仅适用于:
- 自己拥有的设备上的流量分析
- 企业安全培训演示(设备和证书均由企业管控)
- 自动化测试(拦截并 mock API 响应)
不要用于拦截他人流量,这在大多数国家是违法行为。
