Go 语言 net/rpc 原生不支持拦截,但可通过服务端包装 Handler 或客户端封装 Call 实现轻量拦截;生产环境推荐直接使用原生支持拦截的 gRPC。
Go 语言标准库的 net/rpc 本身不支持中间件或拦截机制,但可以通过封装服务端和客户端逻辑,结合自定义编解码器、Server.RegisterName 的包装、或使用更现代的方案(如 gRPC 或第三方 RPC 框架)来实现请求/响应拦截。下面提供两种实用、轻量、原生兼容的方式:
利用 rpc.Server 的可扩展性,在注册方法前对函数做一层包装,实现统一日志、鉴权、耗时统计等。
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)
}
标准 *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 原生支持拦截器(UnaryInterceptor / StreamInterceptor),语义清晰、生态成熟:
grpc.UnaryServerInterceptor 函数,并传给 grpc.NewServer(opts...);grpc.WithUnaryInterceptor;例如服务端日志拦截器:
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;net/rpc 且需强扩展性,可考虑基于 gob 编解码层注入元数据(如在请求体前加 header 字节),但会破坏协议兼容性。基本上就这些。不复杂但容易忽略的是:拦截逻辑要尽量无副作用、避免阻塞、注意 context 取消传播。