1use core::fmt;
26use std::future::Future;
27use std::pin::Pin;
28use std::task::{Context, Poll, ready};
29
30use http::{HeaderMap, HeaderValue, Method, Request, Response, StatusCode, Version, header};
31use pin_project::pin_project;
32use tonic::metadata::GRPC_CONTENT_TYPE;
33use tonic::{body::Body, server::NamedService};
34use tower_service::Service;
35use tracing::{debug, trace};
36
37use crate::call::content_types::is_grpc_web;
38use crate::call::{Encoding, GrpcWebCall};
39
40#[derive(Debug, Clone)]
42pub struct GrpcWebService<S> {
43 inner: S,
44}
45
46#[derive(Debug, PartialEq)]
47enum RequestKind<'a> {
48 GrpcWeb {
56 method: &'a Method,
57 encoding: Encoding,
58 accept: Encoding,
59 },
60 Other(http::Version),
62}
63
64impl<S> GrpcWebService<S> {
65 pub(crate) fn new(inner: S) -> Self {
66 GrpcWebService { inner }
67 }
68}
69
70impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for GrpcWebService<S>
71where
72 S: Service<Request<Body>, Response = Response<ResBody>>,
73 ReqBody: http_body::Body<Data = bytes::Bytes> + Send + 'static,
74 ReqBody::Error: Into<crate::BoxError> + fmt::Display,
75 ResBody: http_body::Body<Data = bytes::Bytes> + Send + 'static,
76 ResBody::Error: Into<crate::BoxError> + fmt::Display,
77{
78 type Response = Response<Body>;
79 type Error = S::Error;
80 type Future = ResponseFuture<S::Future>;
81
82 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
83 self.inner.poll_ready(cx)
84 }
85
86 fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
87 match RequestKind::new(req.headers(), req.method(), req.version()) {
88 RequestKind::GrpcWeb {
98 method: &Method::POST,
99 encoding,
100 accept,
101 } => {
102 trace!(kind = "simple", path = ?req.uri().path(), ?encoding, ?accept);
103
104 ResponseFuture {
105 case: Case::GrpcWeb {
106 future: self.inner.call(coerce_request(req, encoding)),
107 accept,
108 },
109 }
110 }
111
112 RequestKind::GrpcWeb { .. } => {
116 debug!(kind = "simple", error="method not allowed", method = ?req.method());
117
118 ResponseFuture {
119 case: Case::immediate(StatusCode::METHOD_NOT_ALLOWED),
120 }
121 }
122
123 RequestKind::Other(Version::HTTP_2) => {
126 debug!(kind = "other h2", content_type = ?req.headers().get(header::CONTENT_TYPE));
127 ResponseFuture {
128 case: Case::Other {
129 future: self.inner.call(req.map(Body::new)),
130 },
131 }
132 }
133
134 RequestKind::Other(_) => {
136 debug!(kind = "other h1", content_type = ?req.headers().get(header::CONTENT_TYPE));
137
138 ResponseFuture {
139 case: Case::immediate(StatusCode::BAD_REQUEST),
140 }
141 }
142 }
143 }
144}
145
146#[pin_project]
148#[must_use = "futures do nothing unless polled"]
149pub struct ResponseFuture<F> {
150 #[pin]
151 case: Case<F>,
152}
153
154#[pin_project(project = CaseProj)]
155enum Case<F> {
156 GrpcWeb {
157 #[pin]
158 future: F,
159 accept: Encoding,
160 },
161 Other {
162 #[pin]
163 future: F,
164 },
165 ImmediateResponse {
166 res: Option<http::response::Parts>,
167 },
168}
169
170impl<F> Case<F> {
171 fn immediate(status: StatusCode) -> Self {
172 let (res, ()) = Response::builder()
173 .status(status)
174 .body(())
175 .unwrap()
176 .into_parts();
177 Self::ImmediateResponse { res: Some(res) }
178 }
179}
180
181impl<F, B, E> Future for ResponseFuture<F>
182where
183 F: Future<Output = Result<Response<B>, E>>,
184 B: http_body::Body<Data = bytes::Bytes> + Send + 'static,
185 B::Error: Into<crate::BoxError> + fmt::Display,
186{
187 type Output = Result<Response<Body>, E>;
188
189 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
190 let this = self.project();
191
192 match this.case.project() {
193 CaseProj::GrpcWeb { future, accept } => {
194 let res = ready!(future.poll(cx))?;
195
196 Poll::Ready(Ok(coerce_response(res, *accept)))
197 }
198 CaseProj::Other { future } => future.poll(cx).map_ok(|res| res.map(Body::new)),
199 CaseProj::ImmediateResponse { res } => {
200 let res = Response::from_parts(res.take().unwrap(), Body::empty());
201 Poll::Ready(Ok(res))
202 }
203 }
204 }
205}
206
207impl<S: NamedService> NamedService for GrpcWebService<S> {
208 const NAME: &'static str = S::NAME;
209}
210
211impl<F> fmt::Debug for ResponseFuture<F> {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("ResponseFuture").finish()
214 }
215}
216
217impl<'a> RequestKind<'a> {
218 fn new(headers: &'a HeaderMap, method: &'a Method, version: Version) -> Self {
219 if is_grpc_web(headers) {
220 return RequestKind::GrpcWeb {
221 method,
222 encoding: Encoding::from_content_type(headers),
223 accept: Encoding::from_accept(headers),
224 };
225 }
226
227 RequestKind::Other(version)
228 }
229}
230
231fn coerce_request<B>(mut req: Request<B>, encoding: Encoding) -> Request<Body>
235where
236 B: http_body::Body<Data = bytes::Bytes> + Send + 'static,
237 B::Error: Into<crate::BoxError> + fmt::Display,
238{
239 req.headers_mut().remove(header::CONTENT_LENGTH);
240
241 req.headers_mut()
242 .insert(header::CONTENT_TYPE, GRPC_CONTENT_TYPE);
243
244 req.headers_mut()
245 .insert(header::TE, HeaderValue::from_static("trailers"));
246
247 req.headers_mut().insert(
248 header::ACCEPT_ENCODING,
249 HeaderValue::from_static("identity,deflate,gzip"),
250 );
251
252 req.map(|b| Body::new(GrpcWebCall::request(b, encoding)))
253}
254
255fn coerce_response<B>(res: Response<B>, encoding: Encoding) -> Response<Body>
256where
257 B: http_body::Body<Data = bytes::Bytes> + Send + 'static,
258 B::Error: Into<crate::BoxError> + fmt::Display,
259{
260 let mut res = res
261 .map(|b| GrpcWebCall::response(b, encoding))
262 .map(Body::new);
263
264 res.headers_mut().insert(
265 header::CONTENT_TYPE,
266 HeaderValue::from_static(encoding.to_content_type()),
267 );
268
269 res
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::call::content_types::*;
276 use http::header::{
277 ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, CONTENT_TYPE, ORIGIN,
278 };
279 use tower_layer::Layer as _;
280
281 type BoxFuture<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;
282
283 #[derive(Debug, Clone)]
284 struct Svc;
285
286 impl<B> tower_service::Service<Request<B>> for Svc {
287 type Response = Response<Body>;
288 type Error = std::convert::Infallible;
289 type Future = BoxFuture<Self::Response, Self::Error>;
290
291 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
292 Poll::Ready(Ok(()))
293 }
294
295 fn call(&mut self, _: Request<B>) -> Self::Future {
296 Box::pin(async { Ok(Response::new(Body::default())) })
297 }
298 }
299
300 impl NamedService for Svc {
301 const NAME: &'static str = "test";
302 }
303
304 fn enable<S>(service: S) -> tower_http::cors::Cors<GrpcWebService<S>>
305 where
306 S: Service<http::Request<Body>, Response = http::Response<Body>>,
307 {
308 tower_layer::Stack::new(
309 crate::GrpcWebLayer::new(),
310 tower_http::cors::CorsLayer::new(),
311 )
312 .layer(service)
313 }
314
315 mod grpc_web {
316 use super::*;
317 use tower_layer::Layer;
318
319 fn request() -> Request<Body> {
320 Request::builder()
321 .method(Method::POST)
322 .header(CONTENT_TYPE, GRPC_WEB)
323 .header(ORIGIN, "http://example.com")
324 .body(Body::default())
325 .unwrap()
326 }
327
328 #[tokio::test]
329 async fn default_cors_config() {
330 let mut svc = enable(Svc);
331 let res = svc.call(request()).await.unwrap();
332
333 assert_eq!(res.status(), StatusCode::OK);
334 }
335
336 #[tokio::test]
337 async fn web_layer() {
338 let mut svc = crate::GrpcWebLayer::new().layer(Svc);
339 let res = svc.call(request()).await.unwrap();
340
341 assert_eq!(res.status(), StatusCode::OK);
342 }
343
344 #[tokio::test]
345 async fn web_layer_with_axum() {
346 let mut svc = axum::routing::Router::new()
347 .route("/", axum::routing::post_service(Svc))
348 .layer(crate::GrpcWebLayer::new());
349
350 let res = svc.call(request()).await.unwrap();
351
352 assert_eq!(res.status(), StatusCode::OK);
353 }
354
355 #[tokio::test]
356 async fn without_origin() {
357 let mut svc = enable(Svc);
358
359 let mut req = request();
360 req.headers_mut().remove(ORIGIN);
361
362 let res = svc.call(req).await.unwrap();
363
364 assert_eq!(res.status(), StatusCode::OK);
365 }
366
367 #[tokio::test]
368 async fn only_post_and_options_allowed() {
369 let mut svc = enable(Svc);
370
371 for method in &[
372 Method::GET,
373 Method::PUT,
374 Method::DELETE,
375 Method::HEAD,
376 Method::PATCH,
377 ] {
378 let mut req = request();
379 *req.method_mut() = method.clone();
380
381 let res = svc.call(req).await.unwrap();
382
383 assert_eq!(
384 res.status(),
385 StatusCode::METHOD_NOT_ALLOWED,
386 "{method} should not be allowed"
387 );
388 }
389 }
390
391 #[tokio::test]
392 async fn grpc_web_content_types() {
393 let mut svc = enable(Svc);
394
395 for ct in &[GRPC_WEB_TEXT, GRPC_WEB_PROTO, GRPC_WEB_TEXT_PROTO, GRPC_WEB] {
396 let mut req = request();
397 req.headers_mut()
398 .insert(CONTENT_TYPE, HeaderValue::from_static(ct));
399
400 let res = svc.call(req).await.unwrap();
401
402 assert_eq!(res.status(), StatusCode::OK);
403 }
404 }
405 }
406
407 mod options {
408 use super::*;
409
410 fn request() -> Request<Body> {
411 Request::builder()
412 .method(Method::OPTIONS)
413 .header(ORIGIN, "http://example.com")
414 .header(ACCESS_CONTROL_REQUEST_HEADERS, "x-grpc-web")
415 .header(ACCESS_CONTROL_REQUEST_METHOD, "POST")
416 .body(Body::default())
417 .unwrap()
418 }
419
420 #[tokio::test]
421 async fn valid_grpc_web_preflight() {
422 let mut svc = enable(Svc);
423 let res = svc.call(request()).await.unwrap();
424
425 assert_eq!(res.status(), StatusCode::OK);
426 }
427 }
428
429 mod grpc {
430 use super::*;
431
432 fn request() -> Request<Body> {
433 Request::builder()
434 .version(Version::HTTP_2)
435 .header(CONTENT_TYPE, GRPC_CONTENT_TYPE)
436 .body(Body::default())
437 .unwrap()
438 }
439
440 #[tokio::test]
441 async fn h2_is_ok() {
442 let mut svc = enable(Svc);
443
444 let req = request();
445 let res = svc.call(req).await.unwrap();
446
447 assert_eq!(res.status(), StatusCode::OK)
448 }
449
450 #[tokio::test]
451 async fn h1_is_err() {
452 let mut svc = enable(Svc);
453
454 let req = Request::builder()
455 .header(CONTENT_TYPE, GRPC_CONTENT_TYPE)
456 .body(Body::default())
457 .unwrap();
458
459 let res = svc.call(req).await.unwrap();
460 assert_eq!(res.status(), StatusCode::BAD_REQUEST)
461 }
462
463 #[tokio::test]
464 async fn content_type_variants() {
465 let mut svc = enable(Svc);
466
467 for variant in &["grpc", "grpc+proto", "grpc+thrift", "grpc+foo"] {
468 let mut req = request();
469 req.headers_mut().insert(
470 CONTENT_TYPE,
471 HeaderValue::from_maybe_shared(format!("application/{variant}")).unwrap(),
472 );
473
474 let res = svc.call(req).await.unwrap();
475
476 assert_eq!(res.status(), StatusCode::OK)
477 }
478 }
479 }
480
481 mod other {
482 use super::*;
483
484 fn request() -> Request<Body> {
485 Request::builder()
486 .header(CONTENT_TYPE, "application/text")
487 .body(Body::default())
488 .unwrap()
489 }
490
491 #[tokio::test]
492 async fn h1_is_err() {
493 let mut svc = enable(Svc);
494 let res = svc.call(request()).await.unwrap();
495
496 assert_eq!(res.status(), StatusCode::BAD_REQUEST)
497 }
498
499 #[tokio::test]
500 async fn h2_is_ok() {
501 let mut svc = enable(Svc);
502 let mut req = request();
503 *req.version_mut() = Version::HTTP_2;
504
505 let res = svc.call(req).await.unwrap();
506 assert_eq!(res.status(), StatusCode::OK)
507 }
508 }
509}