xref: /tonic/tonic/src/service/interceptor.rs (revision 48284aaf)
1 //! gRPC interceptors which are a kind of middleware.
2 //!
3 //! See [`Interceptor`] for more details.
4 
5 use crate::{request::SanitizeHeaders, Status};
6 use pin_project::pin_project;
7 use std::{
8     fmt,
9     future::Future,
10     pin::Pin,
11     task::{Context, Poll},
12 };
13 use tower_layer::Layer;
14 use tower_service::Service;
15 
16 /// A gRPC interceptor.
17 ///
18 /// gRPC interceptors are similar to middleware but have less flexibility. An interceptor allows
19 /// you to do two main things, one is to add/remove/check items in the `MetadataMap` of each
20 /// request. Two, cancel a request with a `Status`.
21 ///
22 /// Any function that satisfies the bound `FnMut(Request<()>) -> Result<Request<()>, Status>` can be
23 /// used as an `Interceptor`.
24 ///
25 /// An interceptor can be used on both the server and client side through the `tonic-build` crate's
26 /// generated structs.
27 ///
28 /// See the [interceptor example][example] for more details.
29 ///
30 /// If you need more powerful middleware, [tower] is the recommended approach. You can find
31 /// examples of how to use tower with tonic [here][tower-example].
32 ///
33 /// Additionally, interceptors is not the recommended way to add logging to your service. For that
34 /// a [tower] middleware is more appropriate since it can also act on the response. For example
35 /// tower-http's [`Trace`](https://docs.rs/tower-http/latest/tower_http/trace/index.html)
36 /// middleware supports gRPC out of the box.
37 ///
38 /// [tower]: https://crates.io/crates/tower
39 /// [example]: https://github.com/hyperium/tonic/tree/master/examples/src/interceptor
40 /// [tower-example]: https://github.com/hyperium/tonic/tree/master/examples/src/tower
41 pub trait Interceptor {
42     /// Intercept a request before it is sent, optionally cancelling it.
call(&mut self, request: crate::Request<()>) -> Result<crate::Request<()>, Status>43     fn call(&mut self, request: crate::Request<()>) -> Result<crate::Request<()>, Status>;
44 }
45 
46 impl<F> Interceptor for F
47 where
48     F: FnMut(crate::Request<()>) -> Result<crate::Request<()>, Status>,
49 {
call(&mut self, request: crate::Request<()>) -> Result<crate::Request<()>, Status>50     fn call(&mut self, request: crate::Request<()>) -> Result<crate::Request<()>, Status> {
51         self(request)
52     }
53 }
54 
55 /// A gRPC interceptor that can be used as a [`Layer`],
56 ///
57 /// See [`Interceptor`] for more details.
58 #[derive(Debug, Clone, Copy)]
59 pub struct InterceptorLayer<I> {
60     interceptor: I,
61 }
62 
63 impl<I> InterceptorLayer<I> {
64     /// Create a new interceptor layer.
65     ///
66     /// See [`Interceptor`] for more details.
new(interceptor: I) -> Self67     pub fn new(interceptor: I) -> Self {
68         Self { interceptor }
69     }
70 }
71 
72 impl<S, I> Layer<S> for InterceptorLayer<I>
73 where
74     I: Clone,
75 {
76     type Service = InterceptedService<S, I>;
77 
layer(&self, service: S) -> Self::Service78     fn layer(&self, service: S) -> Self::Service {
79         InterceptedService::new(service, self.interceptor.clone())
80     }
81 }
82 
83 /// A service wrapped in an interceptor middleware.
84 ///
85 /// See [`Interceptor`] for more details.
86 #[derive(Clone, Copy)]
87 pub struct InterceptedService<S, I> {
88     inner: S,
89     interceptor: I,
90 }
91 
92 impl<S, I> InterceptedService<S, I> {
93     /// Create a new `InterceptedService` that wraps `S` and intercepts each request with the
94     /// function `F`.
new(service: S, interceptor: I) -> Self95     pub fn new(service: S, interceptor: I) -> Self {
96         Self {
97             inner: service,
98             interceptor,
99         }
100     }
101 }
102 
103 impl<S, I> fmt::Debug for InterceptedService<S, I>
104 where
105     S: fmt::Debug,
106 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result107     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108         f.debug_struct("InterceptedService")
109             .field("inner", &self.inner)
110             .field("f", &format_args!("{}", std::any::type_name::<I>()))
111             .finish()
112     }
113 }
114 
115 impl<S, I, ReqBody, ResBody> Service<http::Request<ReqBody>> for InterceptedService<S, I>
116 where
117     S: Service<http::Request<ReqBody>, Response = http::Response<ResBody>>,
118     I: Interceptor,
119 {
120     type Response = http::Response<ResponseBody<ResBody>>;
121     type Error = S::Error;
122     type Future = ResponseFuture<S::Future>;
123 
124     #[inline]
poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>125     fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
126         self.inner.poll_ready(cx)
127     }
128 
call(&mut self, req: http::Request<ReqBody>) -> Self::Future129     fn call(&mut self, req: http::Request<ReqBody>) -> Self::Future {
130         // It is bad practice to modify the body (i.e. Message) of the request via an interceptor.
131         // To avoid exposing the body of the request to the interceptor function, we first remove it
132         // here, allow the interceptor to modify the metadata and extensions, and then recreate the
133         // HTTP request with the body. Tonic requests do not preserve the URI, HTTP version, and
134         // HTTP method of the HTTP request, so we extract them here and then add them back in below.
135         let uri = req.uri().clone();
136         let method = req.method().clone();
137         let version = req.version();
138         let req = crate::Request::from_http(req);
139         let (metadata, extensions, msg) = req.into_parts();
140 
141         match self
142             .interceptor
143             .call(crate::Request::from_parts(metadata, extensions, ()))
144         {
145             Ok(req) => {
146                 let (metadata, extensions, _) = req.into_parts();
147                 let req = crate::Request::from_parts(metadata, extensions, msg);
148                 let req = req.into_http(uri, method, version, SanitizeHeaders::No);
149                 ResponseFuture::future(self.inner.call(req))
150             }
151             Err(status) => ResponseFuture::status(status),
152         }
153     }
154 }
155 
156 // required to use `InterceptedService` with `Router`
157 impl<S, I> crate::server::NamedService for InterceptedService<S, I>
158 where
159     S: crate::server::NamedService,
160 {
161     const NAME: &'static str = S::NAME;
162 }
163 
164 /// Response future for [`InterceptedService`].
165 #[pin_project]
166 #[derive(Debug)]
167 pub struct ResponseFuture<F> {
168     #[pin]
169     kind: Kind<F>,
170 }
171 
172 impl<F> ResponseFuture<F> {
future(future: F) -> Self173     fn future(future: F) -> Self {
174         Self {
175             kind: Kind::Future(future),
176         }
177     }
178 
status(status: Status) -> Self179     fn status(status: Status) -> Self {
180         Self {
181             kind: Kind::Status(Some(status)),
182         }
183     }
184 }
185 
186 #[pin_project(project = KindProj)]
187 #[derive(Debug)]
188 enum Kind<F> {
189     Future(#[pin] F),
190     Status(Option<Status>),
191 }
192 
193 impl<F, E, B> Future for ResponseFuture<F>
194 where
195     F: Future<Output = Result<http::Response<B>, E>>,
196 {
197     type Output = Result<http::Response<ResponseBody<B>>, E>;
198 
poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>199     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
200         match self.project().kind.project() {
201             KindProj::Future(future) => future.poll(cx).map_ok(|res| res.map(ResponseBody::wrap)),
202             KindProj::Status(status) => {
203                 let (parts, ()) = status.take().unwrap().into_http::<()>().into_parts();
204                 let response = http::Response::from_parts(parts, ResponseBody::<B>::empty());
205                 Poll::Ready(Ok(response))
206             }
207         }
208     }
209 }
210 
211 /// Response body for [`InterceptedService`].
212 #[pin_project]
213 #[derive(Debug)]
214 pub struct ResponseBody<B> {
215     #[pin]
216     kind: ResponseBodyKind<B>,
217 }
218 
219 #[pin_project(project = ResponseBodyKindProj)]
220 #[derive(Debug)]
221 enum ResponseBodyKind<B> {
222     Empty,
223     Wrap(#[pin] B),
224 }
225 
226 impl<B> ResponseBody<B> {
new(kind: ResponseBodyKind<B>) -> Self227     fn new(kind: ResponseBodyKind<B>) -> Self {
228         Self { kind }
229     }
230 
empty() -> Self231     fn empty() -> Self {
232         Self::new(ResponseBodyKind::Empty)
233     }
234 
wrap(body: B) -> Self235     fn wrap(body: B) -> Self {
236         Self::new(ResponseBodyKind::Wrap(body))
237     }
238 }
239 
240 impl<B: http_body::Body> http_body::Body for ResponseBody<B> {
241     type Data = B::Data;
242     type Error = B::Error;
243 
poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>>244     fn poll_frame(
245         self: Pin<&mut Self>,
246         cx: &mut Context<'_>,
247     ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
248         match self.project().kind.project() {
249             ResponseBodyKindProj::Empty => Poll::Ready(None),
250             ResponseBodyKindProj::Wrap(body) => body.poll_frame(cx),
251         }
252     }
253 
size_hint(&self) -> http_body::SizeHint254     fn size_hint(&self) -> http_body::SizeHint {
255         match &self.kind {
256             ResponseBodyKind::Empty => http_body::SizeHint::with_exact(0),
257             ResponseBodyKind::Wrap(body) => body.size_hint(),
258         }
259     }
260 
is_end_stream(&self) -> bool261     fn is_end_stream(&self) -> bool {
262         match &self.kind {
263             ResponseBodyKind::Empty => true,
264             ResponseBodyKind::Wrap(body) => body.is_end_stream(),
265         }
266     }
267 }
268 
269 #[cfg(test)]
270 mod tests {
271     use super::*;
272     use tower::ServiceExt;
273 
274     #[tokio::test]
doesnt_remove_headers_from_requests()275     async fn doesnt_remove_headers_from_requests() {
276         let svc = tower::service_fn(|request: http::Request<()>| async move {
277             assert_eq!(
278                 request
279                     .headers()
280                     .get("user-agent")
281                     .expect("missing in leaf service"),
282                 "test-tonic"
283             );
284 
285             Ok::<_, Status>(http::Response::new(()))
286         });
287 
288         let svc = InterceptedService::new(svc, |request: crate::Request<()>| {
289             assert_eq!(
290                 request
291                     .metadata()
292                     .get("user-agent")
293                     .expect("missing in interceptor"),
294                 "test-tonic"
295             );
296 
297             Ok(request)
298         });
299 
300         let request = http::Request::builder()
301             .header("user-agent", "test-tonic")
302             .body(())
303             .unwrap();
304 
305         svc.oneshot(request).await.unwrap();
306     }
307 
308     #[tokio::test]
handles_intercepted_status_as_response()309     async fn handles_intercepted_status_as_response() {
310         let message = "Blocked by the interceptor";
311         let expected = Status::permission_denied(message).into_http::<()>();
312 
313         let svc = tower::service_fn(|_: http::Request<()>| async {
314             Ok::<_, Status>(http::Response::new(()))
315         });
316 
317         let svc = InterceptedService::new(svc, |_: crate::Request<()>| {
318             Err(Status::permission_denied(message))
319         });
320 
321         let request = http::Request::builder().body(()).unwrap();
322         let response = svc.oneshot(request).await.unwrap();
323 
324         assert_eq!(expected.status(), response.status());
325         assert_eq!(expected.version(), response.version());
326         assert_eq!(expected.headers(), response.headers());
327     }
328 
329     #[tokio::test]
doesnt_change_http_method()330     async fn doesnt_change_http_method() {
331         let svc = tower::service_fn(|request: http::Request<()>| async move {
332             assert_eq!(request.method(), http::Method::OPTIONS);
333 
334             Ok::<_, hyper::Error>(hyper::Response::new(()))
335         });
336 
337         let svc = InterceptedService::new(svc, Ok);
338 
339         let request = http::Request::builder()
340             .method(http::Method::OPTIONS)
341             .body(())
342             .unwrap();
343 
344         svc.oneshot(request).await.unwrap();
345     }
346 }
347