Go 标准库 net/http 不提供网络层包拦截,所谓“拦截”实为应用层干预:HTTP 服务端用中间件链式包装 Handler,客户端定制 http.Transport.RoundTrip,gRPC 使用 UnaryServerInterceptor,net/rpc 需手动封装代理。

Go 语言标准库的 net/http 本身不提供“网络层包拦截”能力(比如像 iptables 那样抓原始 TCP/IP 包),所谓“拦截数据过滤中间件”,实际指在应用层对 HTTP 请求/响应、RPC 调用或 HTTP 客户端出向流量做可控干预。关键判断是:你不需要写 syscall 或 raw socket,而是选对抽象层级——HTTP 中间件、http.Transport.RoundTrip、gRPC 拦截器或 net/rpc 封装。
HTTP 服务端请求拦截:用中间件控制进站流量
这是最常见场景,比如 IP 白名单、路径鉴权、请求体审计。核心是链式包装 http.Handler,并在逻辑分支中决定是否调用 next.ServeHTTP。
- 白名单校验必须早于任何读取
r.Body的操作,否则会因 body 已被读空导致后续 handler 失败;正确做法是先解析r.RemoteAddr,再用net.ParseIP和ipNet.Contains判断 - 若需检查请求体(如 JSON 字段过滤),必须用
io.ReadAll(r.Body)读完,再用bytes.NewReader重置r.Body,且要同步更新r.ContentLength - 修改
r.URL后未调用r = r.Clone(r.Context())可能引发 panic,尤其在并发环境下 - 示例片段:
func ipWhitelistMiddleware(whiteNets []*net.IPNet) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ip := net.ParseIP(strings.Split(r.RemoteAddr, ":")[0]) allowed := false for _, net := range whiteNets { if net.Contains(ip) { allowed = true break } } if !allowed { http.Error(w, "Forbidden", http.StatusForbidden) return } next.ServeHTTP(w, r) }) } }
HTTP 客户端出向请求拦截:定制 http.Transport
当你想统一控制所有由 http.Client 发出的请求(如添加 header、拒绝特定域名、记录耗时),应实现 http.RoundTripper 接口并替换 Client.Transport。
- 不要直接修改
http.DefaultTransport,它被全局共享,修改会影响其他包;应新建结构体嵌入原Transport,只重写RoundTrip - 过滤逻辑放在
RoundTrip开头:可检查req.URL.Host、req.Method、req.Header,不符合即返回错误或伪造响应 - 若需改写请求(如加签名 header),直接赋值
req.Header.Set即可;但注意不可修改req.URL后不处理相对路径问题 - 示例结构体:
type FilterTransport struct { Transport http.RoundTripper } func (t *FilterTransport) RoundTrip(req *http.Request) (*http.Response, error) { if req.URL.Host == "blocked.example.com" { return &http.Response{ StatusCode: 403, Body: io.NopCloser(strings.NewReader("blocked")), }, nil } return t.Transport.RoundTrip(req) }
gRPC 一元调用拦截:用 grpc.UnaryServerInterceptor
如果你用的是 gRPC(而非标准 net/rpc),拦截是最干净的——原生支持,无需手动包装或反射。
立即学习“go语言免费学习笔记(深入)”;
- 服务端拦截器函数签名固定:
func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) - 可在
handler调用前后插入逻辑:前置做 JWT 解析、限流计数;后置做响应日志、错误分类 - 客户端拦截同理,用
grpc.WithUnaryInterceptor注册,注意拦截器内不可阻塞,避免拖慢整个调用链 - 多个拦截器按注册顺序执行,但异常传播是线性的——任一拦截器返回 error,后续拦截器和 handler 都不会执行
net/rpc 请求拦截:只能靠服务端/客户端封装
net/rpc 标准库没有拦截机制,强行加逻辑必须侵入调用链路。
- 服务端侧:不直接
server.Register原始 struct,而是注册一个代理对象,其方法调用前先走拦截函数,再用reflect转发到真实方法 - 客户端侧:不直接用
*rpc.Client.Call,而是封装InterceptedClient类型,重写Call方法,在其中插入日志、超时控制、trace ID 注入等 - 强烈建议生产环境迁移到 gRPC——
net/rpc的拦截成本高、易出错、无生态工具支持(如 OpenTelemetry 集成)
真正容易被忽略的是:HTTP 中间件里读取并重放 r.Body 的细节、gRPC 拦截器中 context 传递的隐式覆盖、以及 net/rpc 场景下反射调用带来的 panic 风险。这些不是边缘 case,而是上线后第一波报错的集中地。


















