
Go RPC 中怎么给所有方法加统一日志或鉴权?
用 net/rpc 自带的 Server.RegisterName 无法直接插拦截逻辑,必须在服务端方法内部手动加——但这样重复写、难维护。真正可行的方式是包装 rpc.Server 的 ServeCodec 或改用 http.ServeHTTP 做一层代理,不过最轻量且可控的做法是:在注册前,把原始服务对象用结构体嵌套+方法重写的方式“装饰”一遍。
- 不要试图修改
rpc.Server源码或反射劫持方法表——Go 的reflect.Value.Call在 RPC 方法签名不匹配时会 panic,且无法获取上下文参数 - 推荐用匿名字段嵌入原服务,再为每个导出方法提供同名包装器,例如:
func (s *loggedService) Add(r *Request, t *int) error { log.Printf("call Add"); return s.Service.Add(r, t) } - 注意:RPC 方法签名必须严格符合
func(*T1, *T2) error,包装器里不能漏掉指针类型或改返回值顺序,否则注册时静默失败
为什么不用中间件函数封装 rpc.ServeConn?
因为 rpc.ServeConn 是底层连接级处理,它只负责解包请求、调用对应方法、回写响应,不暴露“方法名”和“参数内容”——你没法在它之前判断该不该鉴权、记哪条日志。等你拿到的是已解析好的 *rpc.Request 和 codec.ServerCodec,此时方法早已执行完毕或正在执行中。
-
rpc.ServeConn的输入是io.ReadWriteCloser,你最多能读到原始字节流,但 Go RPC 默认用gob编码,没有 schema 就无法安全反序列化请求头 - 若强行在
ServeConn外套一层 net.Conn 包装器(如connWrapper),只能做连接层审计(比如 IP 限流),对业务方法无感知 - 真正需要横切的场景(如按 method 名做黑白名单),必须落到服务端方法调用前一刻,也就是“服务对象方法被反射调用”的那个切面
简单拦截器模式在 gRPC 里还适用吗?
不适用。gRPC 原生支持 UnaryInterceptor 和 StreamInterceptor,接口清晰、上下文透传完整,根本不需要手写装饰器。但如果你正从 net/rpc 迁移到 gRPC,别想着复用旧拦截逻辑——gRPC 的拦截器接收 context.Context 和 interface{} 参数,而 net/rpc 完全没有 context,参数是硬编码的两个指针。
-
gRPC拦截器里能拿到完整method字符串(如/helloworld.Greeter/SayHello),net/rpc只有结构体内方法名(如Add),二者元信息粒度不同 - 别试图用
gob解码net/rpc请求体去模拟 gRPC 拦截——编码格式不公开、无文档、版本敏感,容易在 Go 升级后突然失效 - 如果项目已重度依赖
net/rpc又急需拦截能力,优先考虑升级到gRPC,而不是给老代码打补丁
最容易被忽略的坑:RPC 方法的 receiver 类型影响拦截器生效
Go 的 net/rpc 要求注册的服务对象必须是导出类型,且方法 receiver 必须是**指针或值类型一致**。如果你用结构体嵌套实现拦截器,但原始服务是以值类型定义 receiver(如 func (s Service) Add(...)),而拦截器用指针嵌入(type loggedService struct{ *Service }),那么 loggedService 就不会继承那些值 receiver 方法——RPC 注册时会跳过它们,不报错,但调用直接返回 method not found。
立即学习“go语言免费学习笔记(深入)”;
- 检查原始服务方法的 receiver:运行
go doc yourpkg.Service,看方法列表前缀是(Service)还是(*Service) - 拦截器结构体的嵌入字段类型必须完全匹配,
*Service就嵌*Service,Service就嵌Service,混用必丢方法 - RPC 不支持 interface{} 注册,所以别想用
interface{ Add(...) }抽象一层再装饰——注册时会 panic:"service has no exported methods"










