gRPC C++ 无原生拦截器API,稳定方案为客户端封装Stub+重载方法、服务端继承Service并重写Request*方法;experimental::Interceptor已废弃且不可靠。

gRPC C++ 没有原生 interceptor API,别直接搜 grpc::Interceptor
gRPC 官方 C++ 库(截至 v1.60)压根没提供类似 Java 或 Go 那种开箱即用的拦截器接口。你搜到的很多文章讲的是 grpc::experimental::Interceptor 或基于 ChannelArguments 的 hack 方式,但这些要么已废弃,要么只在特定版本/构建配置下可用,且不被稳定 ABI 保证。强行套用会导致编译失败、链接不到符号,或者运行时静默失效。
- 真正稳定可用的机制只有:客户端靠
grpc::Channel封装 + 自定义 stub;服务端靠继承grpc::Service并重写Request*方法 - 所有“拦截逻辑”必须手动注入到 RPC 调用链路中,没有中间件注册点
- 如果你依赖 Bazel 构建且用了
grpc_cpp_plugin生成的 stub,那它默认不预留拦截钩子 —— 钩子得你自己加
客户端怎么加日志/超时/Token 注入:封装 Stub + 重载方法
不能改生成的 *.grpc.pb.h,但可以写一个包装类,在调用 stub->Method() 前后插逻辑。关键不是“拦截”,而是“代理”。
- 构造时传入原始
std::shared_ptr<:channel></:channel>,用它 new 出原始 stub - 每个 RPC 方法都重写:比如
MyServiceStub::SayHello()内部先调PreCallHook()(注入 header、打日志),再调raw_stub_->SayHello(),最后调PostCallHook()(统计耗时、处理 status) - 注意
grpc::ClientContext必须由调用方传入或内部 new —— 如果你在包装里 new,外部就无法控制 deadline/cancellation;建议要求调用方传入,你只往里面AddMetadata() - 异步流式调用(
AsyncClientReaderWriter)没法简单包装,因为生命周期和回调绑定紧密,得用std::shared_ptr管理上下文并透传 tag
class LoggingStub {
public:
explicit LoggingStub(std::shared_ptr<grpc::Channel> ch)
: raw_stub_(greeter::Greeter::NewStub(ch)) {}
<p>grpc::Status SayHello(
grpc::ClientContext<em> ctx,
const greeter::HelloRequest& req,
greeter::HelloReply</em> resp) {
ctx->AddMetadata("x-request-id", GenerateID());
auto start = std::chrono::steady_clock::now();
auto status = raw<em>stub</em>->SayHello(ctx, req, resp);
auto dur = std::chrono::steady_clock::now() - start;
LOG(INFO) << "SayHello: " << status.ok() << " in " << dur.count() << "ms";
return status;
}</p><p>private:
std::unique_ptr<greeter::Greeter::Stub> raw<em>stub</em>;
};</p>服务端如何做鉴权/审计:重写 Service::Request* 和 Service::AsyncNotifyWhenDone
服务端唯一可控入口是 Service 子类里的 Request* 方法(如 RequestSayHello),它们在 RPC 刚到达、还没进业务 handler 时被调用。这里能拿到 grpc::ServerContext* 和原始 buffer,但拿不到反序列化后的 request 对象 —— 所以鉴权得在反序列化前做(比如检查 header),审计日志得在反序列化后、handler 前/后补。
-
RequestSayHello是同步阻塞式入口,适合轻量检查(如ctx->auth_context()->FindProperty("x-token")) - 如果要解析完整 request 再鉴权(比如校验 body 签名),得自己调
ParseFromBoundedZeroCopyStream(),但要注意 buffer 生命周期 —— 它只在当前 callback 有效 - 不要试图在
Request*里直接调业务逻辑:这会破坏 gRPC 的线程模型,导致并发错乱;正确做法是把 context/request/refptr 交给业务线程池,再用Finish()回写 response - 所有
AsyncService的Request*方法都必须配对调用NotifyWhenDone,否则 server 会漏掉 completion queue 事件,连接卡死
为什么不用 grpc::experimental::InterceptedChannel?它早就不维护了
这个 experimental 接口在 v1.25 后就被标记为 deprecated,v1.40+ 彻底移除头文件声明。即使你从旧版本拷代码过来,也会遇到:
立即学习“C++免费学习笔记(深入)”;
-
InterceptedChannel构造函数需要std::unique_ptr<:experimental::interceptorfactoryinterface></:experimental::interceptorfactoryinterface>,但该 interface 的CreateInterceptor返回裸指针,且无 RAII 管理,极易内存泄漏 - 它只支持 unary call,streaming 全挂;metadata 修改在 client-side 只对第一个 message 生效
- 链接时大概率报
undefined reference to 'grpc::experimental::InterceptedChannel::InterceptedChannel'—— 因为对应 symbol 在静态库中被 strip 掉了 - 官方 issue #22789 明确说:“C++ 不会提供通用 interceptor 层,推荐用户封装 stub / 继承 service”
真要统一横切逻辑,就老老实实写模板基类或宏展开,别指望框架替你扛。



















