Qt GRPC 拦截器概述
拦截器概述
客户端拦截器提供了一种轻量级的机制,用于实现应在多个 RPC 调用中一致应用的行为。它们对于与特定 RPC 方法无关的横切关注点特别有用,例如:
- 分布式追踪
- 日志记录
- 身份验证和授权
- 指标与可观测性
- 策略执行
- 客户端缓存
- 故障注入
拦截器通过QGrpcInterceptorChain 安装在通道上。每个拦截器实现一个或多个拦截器接口(例如QGrpcStartInterceptor 和QGrpcFinishedInterceptor )。当RPC到达该阶段时,Qt GRPC 通道会调用相应的接口,并提供当前RPC的上下文。
拦截器在单个 RPC 的层面上运行。它们可以在 RPC 进行过程中检查和修改请求或响应的元数据及有效载荷。它们旨在实现按调用行为,而非管理通道或传输配置。
注意: 在Qt GRPC 中,拦截器 安装在通道上,并适用于通过该通道执行的所有 RPC。拦截器链在构建时即已确定,此后无法修改。若要更改拦截行为,请创建一个具有不同拦截器链的新通道。
功能与挂钩点
每个拦截接口代表 RPC 生命周期中的一个挂钩点。拦截器类可以通过多重继承实现多个接口。
Qt GRPC 会检测拦截器实现了哪些拦截接口,并将其作为capabilities 暴露出来。Qt GRPC 利用这些能力,仅分发实际已实现的钩子。
| 接口 | 挂钩点 | 方向 | 可修改 |
|---|---|---|---|
| QGrpcStartInterceptor | RPC 发起之前 | 出站 | 初始消息对象、调用选项 |
| QGrpcWriteMessageInterceptor | 在写入出站消息之前 | 出站 | 消息对象 |
| QGrpcWritesDoneInterceptor | 当客户端指示写入完成时 | 出站 | — |
| QGrpcCancelInterceptor | 当 RPC 被取消时 | 出站 | — |
| QGrpcInitialMetadataInterceptor | 收到初始元数据时 | 入站 | 元数据 |
| QGrpcMessageReceivedInterceptor | 收到消息负载时 | 入站 | 序列化后的消息数据 |
| QGrpcTrailingMetadataInterceptor | 当接收到尾部元数据时 | 入站 | 元数据 |
| QGrpcFinishedInterceptor | 当 RPC 完成时 | 入站 | 最终状态 |
所有回调都会收到一个QGrpcInterceptionContext ,该对象提供有关被拦截的RPC的信息,例如RPC descriptor 、channel 或当前生效的callOptions 。该上下文对象仅在回调期间有效,之后不得再使用。
注意:请保持 拦截逻辑轻量级,并避免在回调中进行阻塞操作。应在回调之外执行耗时操作(例如 I/O 或令牌刷新),并在钩子中仅应用结果。
方向与流程
当使用多个拦截器时,其顺序至关重要。可以将拦截器视为在应用程序与网络之间排成一列。 链中靠前的拦截器会首先处理 RPC,并在将其传递给下一个拦截器之前对其进行修改。更靠近网络的拦截器则拥有最后一次机会来观察或调整实际发送的内容。
拦截器按预定义的顺序添加到QGrpcInterceptorChain 中。QtGrpc 会随着RPC的流转遍历该链:
- 对于出站阶段(应用程序 → 网络),回调函数按拦截器添加到链中的顺序被调用。
- 对于入站阶段(网络 → 应用程序),回调按相反顺序被调用,以便“最靠近网络”的最后一个拦截器最先看到入站数据。

出站阶段对应于由客户端发起的操作,例如发送消息或完成写入序列(例如writeMessage 或writesDone )。这些事件从应用程序通过拦截器链向网络方向传播。
入站阶段对应于 RPC 产生的事件,例如接收消息或调用完成(例如,messageReceived 或finished )。这些事件从网络经由拦截器链传回应用程序。
所有权与生命周期
QGrpcInterceptorChain 支持两种主要的生命周期模型:
- 拥有型:使用
std::unique_ptr<T>()添加拦截器。成功后,拦截器链将拥有这些拦截器,并在拦截器链(以及相应的通道)被销毁时销毁这些拦截器。 - 非拥有模式:使用原始指针
T*添加拦截器。拦截器链不承担所有权。调用方必须确保拦截器对象在通道可能调用它们的整个期间内保持有效。
这两种模式也可以在同一条链中混合使用。虽然系统支持混合使用拥有型和非拥有型拦截器,但需格外谨慎,以确保非拥有型拦截器的生命周期长于所有可能使用它们的通道。
通过将同一个拦截器实例添加到多个链中,多个通道可以共享非拥有型拦截器。这对于共享日志记录或共享指标收集非常有用,但需要仔细管理生命周期和并发访问。
线程安全与共享状态
拦截器回调在拥有该拦截器链的通道的线程中运行。
可重入拦截器无需任何同步操作。
只有当拦截器访问可能被多个线程使用的共享数据时,才需要进行同步。例如,当位于不同线程中的通道使用同一个拦截器实例,或者多个拦截器实例访问相同的共享数据时,就会发生这种情况。
当必须跨线程访问共享数据时,建议将拦截器实例设为线程局部,并将共享数据存储在单独同步的对象中。
struct SharedData
{
QReadWriteLock lock;
// shared data protected by lock
};
class MyInterceptor : public QGrpcStartInterceptor
{
public:
explicit MyInterceptor(std::shared_ptr<SharedData> data)
: m_sharedData(std::move(data)) {}
Continuation onStart(~~~) override; {
// access shared data here
}
private:
std::shared_ptr<SharedData> m_sharedData;
};
~~~
auto sharedData = std::make_shared<SharedData>();
bool ok = chain1.set(
std::make_unique<MyInterceptor>(sharedData),
std::make_unique<MyInterceptor>(sharedData)
);
// repeats for chain2
~~~
auto channelThread1 = std::make_shared<QGrpcHttp2Channel>(
QUrl("address:port"), std::move(chain1));
auto channelThread2 = std::make_shared<QGrpcHttp2Channel>(
QUrl("address:port"), std::move(chain2));© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.