go 语言 net/rpc 原生不支持拦截,但可通过服务端包装 Handler 或客户端封装 Call 实现轻量拦截;生产环境推荐直接使用原生支持拦截的 gRPC。

Go 语言标准库的 net/rpc 本身不支持中间件或拦截机制,但可以通过封装服务端和客户端逻辑,结合自定义编解码器、Server.RegisterName 的包装、或使用更现代的方案(如 gRPC 或第三方 RPC 框架)来实现请求/响应拦截。下面提供两种实用、轻量、原生兼容的方式:
方式一:服务端拦截 —— 包装 Handler 函数
利用 rpc.Server 的可扩展性,在注册方法前对函数做一层包装,实现统一日志、鉴权、耗时统计等。
- 定义一个通用拦截器类型:
type Interceptor func(ctx context.Context, method String, req, resp Interface{}) Error - 在注册服务时,用反射或手动包装每个方法,将原始 handler 套进拦截逻辑中;
- 推荐做法:不直接注册原始结构体,而是注册一个代理对象,其方法调用前先执行拦截器,再转发到真实服务:
type InterceptedService Struct {
svc interface{}
interceptors []Interceptor
}
func (s *InterceptedService) Call(ctx context.Context, method string, req, resp interface{}) error {
for _, i := range s.interceptors {
if err := i(ctx, method, req, resp); err != nil {
return err
}
}
// 调用真实方法(需配合 reflect 或 codegen 实现)
return callRealMethod(s.svc, method, req, resp)
}
方式二:客户端拦截 —— 封装 Client.Call
标准 *rpc.Client 是导出的,但 Call 方法不可覆盖。可行做法是封装一个代理 Client 类型:
- 定义
type InterceptedClient struct { client *rpc.Client; interceptors []ClientInterceptor }; - 实现自己的
Call方法,在调用client.Call前后插入逻辑(如添加 trace ID、记录开始时间、校验响应字段); - 示例拦截器:
func LogInterceptor(method string, req, resp interface{}, start time.Time, err error) {
log.printf(“[RPC] %s: %v → %v | took %v | err: %v”,
method, req, resp, time.Since(start), err)
}
进阶:用 gRPC 替代 net/rpc(推荐生产环境)
如果项目允许升级协议,gRPC 原生支持拦截器(UnaryInterceptor / streamInterceptor),语义清晰、生态成熟:
立即学习“go语言免费学习笔记(深入)”;
- 服务端拦截只需实现
grpc.UnaryServerInterceptor函数,并传给grpc.NewServer(opts...); - 客户端拦截同理,用
grpc.WithUnaryInterceptor; - 可轻松集成 prometheus 监控、OpenTelemetry 链路追踪、JWT 验证等;
例如服务端日志拦截器:
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf(“→ %s: %+v”, info.FullMethod, req)
resp, err := handler(ctx, req)
log.Printf(“← %s: %+v (err=%v)”, info.FullMethod, resp, err)
return resp, err
}
小结与建议
- 原生
net/rpc拦截需手动包装,适合简单场景或学习目的; - 客户端拦截比服务端更容易落地,只需代理
Call; - 新项目强烈建议直接采用 gRPC,拦截、超时、重试、加密等能力开箱即用;
- 若必须用
net/rpc且需强扩展性,可考虑基于gob编解码层注入元数据(如在请求体前加 header 字节),但会破坏协议兼容性。
基本上就这些。不复杂但容易忽略的是:拦截逻辑要尽量无副作用、避免阻塞、注意 context 取消传播。