应直接实现 net/http.RoundTripper 接口以拦截请求,而非封装 http.Client;需保留默认 Transport 以维持连接复用、HTTP/2 和 Keep-Alive;HTTPS 代理须正确处理 CONNECT 隧道并透传 Host;中间件逻辑应在 RoundTrip 内完成且不破坏 context 和 body;性能优化关键在 DNS 缓存、调大连接池参数及复用 TLS。
为什么直接用
而不是封装
代理模式在 Go 中最常被误用的点,就是试图包装整个
实例——它本身已是组合体,且并发安全设计依赖底层
。真正需要拦截和转发请求逻辑的位置,是
接口。自定义实现该接口,才能干净地控制 DNS、TLS、连接复用、重试等关键路径。
常见错误现象:
的
或
字段被反复覆盖,导致中间件行为丢失或 panic;或在每个请求前手动改写
,却忘了同步处理
头和 TLS SNI。
必须保留原
(或其拷贝)作为“下游”
,否则连接池、HTTP/2 支持、Keep-Alive 全部失效
若需修改请求头(如加
),应在
方法内操作
,而非构造新请求对象
不要在
中做阻塞 I/O(如读文件、查数据库),这会卡住整个连接池
如何让代理支持 HTTP/HTTPS 双协议并正确透传 Host
Go 的
默认对 HTTPS 请求走 CONNECT 隧道,而 HTTP 是直连。代理若要同时处理两者,必须区分协议并分别处理:HTTP 请求重写
后交给下游;HTTPS 请求则需响应
方法,并建立隧道连接。
容易踩的坑:
在 CONNECT 请求中为空,必须从原始请求行解析;
在隧道建立后不再用于
路由
,但需透传给上游服务器作 SNI 和虚拟主机识别。
立即学习
“
go语言免费学习笔记(深入)
”;
对
的请求,返回
并复制双向流,不调用下游
对普通 HTTP 请求,用
构造目标地址,避免手动拼接 URL 出错
务必设置
(而非
),否则某些服务(如 Cloudflare)会拒绝请求
如何安全地注入中间件逻辑(鉴权、日志、限流)
代理的中间件不能靠装饰
实现——那是服务端逻辑。代理运行在客户端侧(
),所有中间件必须在
方法内完成,且不能破坏上下文传播和错误链路。
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
典型问题:在中间件里用
包裹原请求,却忘了把新 context 注入到下游
调用中,导致超时失效;或记录日志时直接打印
,造成 body 流被消费后下游无法读取。
使用
创建可修改副本,再注入 header 或修改 URL
日志中只记录
、
、
状态码
和耗时,避免碰触
限流应作用于连接池层级(如用
控制
调用频率),而非单个请求 body
为什么代理
性能瓶颈
往往出在 DNS 和连接复用上
一个吞吐量差的代理,90% 情况下不是因为逻辑复杂,而是 DNS 查询未缓存、TLS 握手未复用、或连接池参数不合理。Go 默认
的
是 2,对高并发代理几乎等于自废武功。
实测影响:未调优时,每秒仅能处理数百请求;开启连接复用 + DNS 缓存后,轻松破万 QPS。
启用 DNS 缓存:用
或
配合
自建缓存,避免每次请求都触发系统解析
调大连接池:
、
、
强制复用 TLS 连接:确保下游
中
和
不返回新配置
最易被忽略的是:代理自身作为客户端,它的
配置和它所转发的目标服务的兼容性。比如目标要求 HTTP/2,而代理的
关闭了
,就会降级成 HTTP/1.1 并可能失败。
net/http.RoundTripperhttp.Clienthttp.ClientTransportRoundTripperhttp.ClientCheckRedirectTransportreq.URLHosthttp.DefaultTransportRoundTripperX-Forwarded-ForRoundTrip*http.RequestRoundTriphttp.Transportreq.URLCONNECTreq.URL.Schemereq.Hostreq.Method == "CONNECT"200 Connection EstablishedRoundTripreq.URL.ResolveReferencereq.Host = upstreamHostreq.URL.Hosthttp.HandlerRoundTripperRoundTripcontext.WithTimeoutRoundTripreq.Bodyreq.Clone(req.Context())req.Methodreq.URL.String()Bodygolang.org/x/time/rate.LimiterRoundTriphttp.TransportMaxIdleConnsPerHostgithub.com/miekg/dnsnet.Resolvertime.CacheMaxIdleConns: 1000MaxIdleConnsPerHost: 1000IdleConnTimeout: 30 * time.SecondTransport.TLSClientConfigGetClientCertificateGetConfigForClientTransportTransportForceAttemptHTTP2