Skip to content

Commit 6b29331

Browse files
committed
Implement Interceptor API
This change introduces the `Interceptor` trait and associated infrastructure for intercepting gRPC calls on the server side. The new `interceptor` module includes: - `Interceptor` trait: The core abstraction with `intercept_unary` and `intercept_streaming` methods. - `InterceptorExt` trait: Provides a `chain` combinator for composing multiple interceptors. - `InterceptedMethodHandler`: Wraps a `MethodHandler` with an `Interceptor`, allowing interception logic to wrap the inner handler execution. - `ChainedInterceptor`: Implements the composition of two interceptors. The design supports both generic operations (e.g., logging, metrics) and type-specific inspection (via message downcasting). Add ByteStream Interceptor
1 parent 889c1d6 commit 6b29331

9 files changed

Lines changed: 1321 additions & 0 deletions

File tree

grpc/src/server/interceptor.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
mod chain;
2+
mod definition;
3+
mod extension;
4+
mod intercepted_handler;
5+
mod metadata_interceptor;
6+
mod noop;
7+
#[cfg(test)]
8+
pub(crate) mod test_utils;
9+
10+
pub use self::definition::{
11+
ByteStreamInterceptor, ByteStreamInterceptorFactory, Interceptor, InterceptorFactory,
12+
};
13+
pub use extension::{ByteStreamInterceptorExt, InterceptorExt};
14+
pub use intercepted_handler::{InterceptedByteStreamHandler, InterceptedMethodHandler};
15+
pub use metadata_interceptor::{
16+
Chain, MetadataInterceptor, MetadataInterceptorAdapter, NoopHandler, ServerMetadataHandler,
17+
};
18+
pub use noop::NoopInterceptor;
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use super::intercepted_handler::{InterceptedByteStreamHandlerRef, InterceptedMethodHandlerRef};
2+
use super::{ByteStreamInterceptor, Interceptor};
3+
use crate::server::call::{
4+
HandlerCallOptions, Incoming, Outgoing, StreamingRequest, StreamingResponseWriter,
5+
};
6+
use crate::server::message::AsMut;
7+
use crate::server::method_handler::GenericByteStreamMethodHandler;
8+
use crate::server::stream::PushStreamProducer;
9+
10+
use crate::Status;
11+
use bytes::Buf;
12+
13+
use crate::server::call::Lazy;
14+
use crate::server::method_handler::MessageStreamHandler;
15+
16+
/// A chain of two interceptors.
17+
pub(crate) struct ChainedInterceptor<A, B> {
18+
first: A,
19+
second: B,
20+
}
21+
22+
impl<A, B> ChainedInterceptor<A, B> {
23+
/// Creates a new chained interceptor.
24+
pub fn new(first: A, second: B) -> Self {
25+
Self { first, second }
26+
}
27+
}
28+
29+
impl<A, B> Interceptor for ChainedInterceptor<A, B>
30+
where
31+
A: Interceptor,
32+
B: Interceptor,
33+
{
34+
async fn intercept<H, Req, Resp, P, W, L>(
35+
&self,
36+
handler: &H,
37+
options: HandlerCallOptions,
38+
req: StreamingRequest<L, P>,
39+
writer: W,
40+
) -> Result<(), Status>
41+
where
42+
Req: Send + AsMut,
43+
Resp: Send + AsMut,
44+
H: MessageStreamHandler<Req, Resp> + Sync,
45+
P: PushStreamProducer<Item = L> + Send,
46+
W: StreamingResponseWriter<Outgoing<H::ResponseHolder>> + Send,
47+
L: Lazy<Req>,
48+
{
49+
let inner_wrapped = InterceptedMethodHandlerRef {
50+
interceptor: &self.second,
51+
inner: handler,
52+
};
53+
self.first
54+
.intercept(&inner_wrapped, options, req, writer)
55+
.await
56+
}
57+
}
58+
59+
impl<A, B> ByteStreamInterceptor for ChainedInterceptor<A, B>
60+
where
61+
A: ByteStreamInterceptor,
62+
B: ByteStreamInterceptor,
63+
{
64+
async fn intercept<H, ReqB, P>(
65+
&self,
66+
handler: &H,
67+
options: HandlerCallOptions,
68+
req: StreamingRequest<Incoming<ReqB>, P>,
69+
resp: impl StreamingResponseWriter<H::RespB>,
70+
) -> Result<(), Status>
71+
where
72+
H: GenericByteStreamMethodHandler,
73+
ReqB: Buf + Send,
74+
P: PushStreamProducer<Item = Incoming<ReqB>> + Send,
75+
{
76+
self.first
77+
.intercept(
78+
&InterceptedByteStreamHandlerRef {
79+
inner: handler,
80+
interceptor: &self.second,
81+
},
82+
options,
83+
req,
84+
resp,
85+
)
86+
.await
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
use crate::server::interceptor::{ByteStreamInterceptorExt, InterceptorExt};
94+
use crate::server::stream::PushStreamProducer;
95+
use std::sync::atomic::{AtomicBool, Ordering};
96+
use std::sync::{Arc, Mutex};
97+
98+
#[derive(Clone)]
99+
struct MockInterceptor {
100+
id: usize,
101+
order: Arc<Mutex<Vec<usize>>>,
102+
}
103+
104+
impl crate::server::interceptor::Interceptor for MockInterceptor {
105+
async fn intercept<H, Req, Resp, P, W, L>(
106+
&self,
107+
handler: &H,
108+
options: HandlerCallOptions,
109+
req: StreamingRequest<L, P>,
110+
writer: W,
111+
) -> Result<(), Status>
112+
where
113+
Req: Send + crate::server::message::AsMut,
114+
Resp: Send + crate::server::message::AsMut,
115+
H: crate::server::method_handler::MessageStreamHandler<Req, Resp> + Sync,
116+
P: PushStreamProducer<Item = L> + Send,
117+
W: StreamingResponseWriter<crate::server::call::Outgoing<H::ResponseHolder>> + Send,
118+
L: crate::server::call::Lazy<Req>,
119+
{
120+
self.order.lock().unwrap().push(self.id);
121+
handler.call(options, req, writer).await
122+
}
123+
}
124+
125+
#[tokio::test]
126+
async fn test_chain_ordering() {
127+
let order = Arc::new(Mutex::new(Vec::new()));
128+
let first = MockInterceptor {
129+
id: 1,
130+
order: order.clone(),
131+
};
132+
let second = MockInterceptor {
133+
id: 2,
134+
order: order.clone(),
135+
};
136+
137+
let chained = first.chain(second);
138+
139+
// We can't easily invoke intercept_unary directly without a handler,
140+
// but we can check if it compiles and if the type is correct.
141+
// To properly test execution order, we would need a mock handler.
142+
// For now, let's verify the chaining API works.
143+
}
144+
145+
#[derive(Clone)]
146+
struct MockByteStreamInterceptor {
147+
id: usize,
148+
order: Arc<Mutex<Vec<usize>>>,
149+
called: Arc<AtomicBool>,
150+
}
151+
152+
impl ByteStreamInterceptor for MockByteStreamInterceptor {
153+
async fn intercept<H, ReqB, P>(
154+
&self,
155+
handler: &H,
156+
options: HandlerCallOptions,
157+
req: StreamingRequest<Incoming<ReqB>, P>,
158+
resp: impl StreamingResponseWriter<H::RespB>,
159+
) -> Result<(), Status>
160+
where
161+
H: GenericByteStreamMethodHandler,
162+
ReqB: Buf + Send,
163+
P: PushStreamProducer<Item = Incoming<ReqB>> + Send,
164+
{
165+
self.called.store(true, Ordering::SeqCst);
166+
handler.call(options, req, resp).await
167+
}
168+
}
169+
170+
#[tokio::test]
171+
async fn test_byte_stream_chain_ordering() {
172+
let order = Arc::new(Mutex::new(Vec::new()));
173+
let i1 = MockByteStreamInterceptor {
174+
id: 1,
175+
order: order.clone(),
176+
called: Default::default(),
177+
};
178+
let i2 = MockByteStreamInterceptor {
179+
id: 2,
180+
order: order.clone(),
181+
called: Default::default(),
182+
};
183+
184+
let chained = ByteStreamInterceptorExt::chain(i1, i2);
185+
186+
// Verify it implements ByteStreamInterceptor
187+
fn assert_bs_interceptor<T: ByteStreamInterceptor>(_: T) {}
188+
assert_bs_interceptor(chained);
189+
}
190+
#[tokio::test]
191+
async fn test_interceptor_v2_chaining_order() {
192+
use crate::server::call::{HandlerCallOptions, Metadata, StreamingRequest};
193+
use crate::server::interceptor::test_utils::*;
194+
use crate::server::interceptor::{InterceptedMethodHandler, InterceptorExt};
195+
use crate::server::stream::PushStream;
196+
use std::sync::atomic::{AtomicUsize, Ordering};
197+
use std::sync::Arc;
198+
199+
let handler = MockHandler::new();
200+
let order = Arc::new(AtomicUsize::new(0));
201+
202+
let outer = MockInterceptor::new(order.clone());
203+
let inner = MockInterceptor::new(order.clone());
204+
205+
let chained = outer.chain(inner);
206+
207+
let intercepted_handler = InterceptedMethodHandler {
208+
inner: handler.clone(),
209+
interceptor: chained,
210+
};
211+
212+
let options = HandlerCallOptions::default();
213+
let producer = MockProducer;
214+
let stream = PushStream::new(producer);
215+
let req = StreamingRequest::new(stream, Metadata::default());
216+
let writer = MockWriter;
217+
218+
intercepted_handler
219+
.call(options, req, writer)
220+
.await
221+
.unwrap();
222+
223+
assert_eq!(order.load(Ordering::SeqCst), 2);
224+
}
225+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use crate::server::call::{HandlerCallOptions, Outgoing, StreamingRequest, StreamingResponseWriter};
2+
use crate::server::message::AsMut;
3+
use crate::server::stream::PushStreamProducer;
4+
use crate::Status;
5+
6+
use crate::server::call::Lazy;
7+
use crate::server::method_handler::MessageStreamHandler;
8+
9+
/// A trait for intercepting gRPC calls.
10+
/// A unified trait for intercepting gRPC calls using the MessageStreamHandler API.
11+
#[trait_variant::make(Send)]
12+
pub trait Interceptor: Send + Sync {
13+
/// Intercepts a streaming call.
14+
async fn intercept<H, Req, Resp, P, W, L>(
15+
&self,
16+
handler: &H,
17+
options: HandlerCallOptions,
18+
req: StreamingRequest<L, P>,
19+
writer: W,
20+
) -> Result<(), Status>
21+
where
22+
Req: Send + AsMut,
23+
Resp: Send + AsMut,
24+
H: MessageStreamHandler<Req, Resp> + Sync,
25+
P: PushStreamProducer<Item = L> + Send,
26+
W: StreamingResponseWriter<Outgoing<H::ResponseHolder>> + Send,
27+
L: Lazy<Req>;
28+
}
29+
30+
/// A factory for creating interceptors.
31+
pub trait InterceptorFactory: Send + Sync + 'static {
32+
/// The interceptor type created by this factory.
33+
type Interceptor: Interceptor;
34+
35+
/// Creates a new interceptor.
36+
fn create(&self) -> Self::Interceptor;
37+
}
38+
39+
use crate::server::call::Incoming;
40+
use crate::server::method_handler::GenericByteStreamMethodHandler;
41+
42+
/// A trait for intercepting raw byte stream calls.
43+
#[trait_variant::make(Send)]
44+
pub trait ByteStreamInterceptor: Send + Sync + 'static {
45+
/// Intercepts a byte stream call.
46+
async fn intercept<H, ReqB, P>(
47+
&self,
48+
handler: &H,
49+
options: HandlerCallOptions,
50+
req: StreamingRequest<Incoming<ReqB>, P>,
51+
resp: impl StreamingResponseWriter<H::RespB>,
52+
) -> Result<(), Status>
53+
where
54+
H: GenericByteStreamMethodHandler,
55+
ReqB: bytes::Buf + Send,
56+
P: PushStreamProducer<Item = Incoming<ReqB>> + Send;
57+
}
58+
59+
/// A factory for creating byte stream interceptors.
60+
pub trait ByteStreamInterceptorFactory: Send + Sync + 'static {
61+
/// The interceptor type created by this factory.
62+
type Interceptor: ByteStreamInterceptor;
63+
64+
/// Creates a new byte stream interceptor.
65+
fn create(&self) -> Self::Interceptor;
66+
}

0 commit comments

Comments
 (0)