xref: /tonic/tonic-web/src/lib.rs (revision a562a3ce)
1 //! grpc-web protocol translation for [`tonic`] services.
2 //!
3 //! [`tonic_web`] enables tonic servers to handle requests from [grpc-web] clients directly,
4 //! without the need of an external proxy. It achieves this by wrapping individual tonic services
5 //! with a [tower] service that performs the translation between protocols and handles `cors`
6 //! requests.
7 //!
8 //! ## Getting Started
9 //!
10 //! ```toml
11 //! [dependencies]
12 //! tonic_web = "0.1"
13 //! ```
14 //!
15 //! ## Enabling tonic services
16 //!
17 //! The easiest way to get started, is to call the [`enable`] function with your tonic service
18 //! and allow the tonic server to accept HTTP/1.1 requests:
19 //!
20 //! ```ignore
21 //! #[tokio::main]
22 //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
23 //!     let addr = "[::1]:50051".parse().unwrap();
24 //!     let greeter = GreeterServer::new(MyGreeter::default());
25 //!
26 //!     Server::builder()
27 //!        .accept_http1(true)
28 //!        .add_service(tonic_web::enable(greeter))
29 //!        .serve(addr)
30 //!        .await?;
31 //!
32 //!    Ok(())
33 //! }
34 //!
35 //! ```
36 //! This will apply a default configuration that works well with grpc-web clients out of the box.
37 //!
38 //! You can customize the CORS configuration composing the [`GrpcWebLayer`] with the cors layer of your choice.
39 //!
40 //! Alternatively, if you have a tls enabled server, you could skip setting `accept_http1` to `true`.
41 //! This works because the browser will handle `ALPN`.
42 //!
43 //! ```ignore
44 //! #[tokio::main]
45 //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
46 //!     let cert = tokio::fs::read("server.pem").await?;
47 //!     let key = tokio::fs::read("server.key").await?;
48 //!     let identity = Identity::from_pem(cert, key);
49 //!
50 //!     let addr = "[::1]:50051".parse().unwrap();
51 //!     let greeter = GreeterServer::new(MyGreeter::default());
52 //!
53 //!     // No need to enable HTTP/1
54 //!     Server::builder()
55 //!        .tls_config(ServerTlsConfig::new().identity(identity))?
56 //!        .add_service(tonic_web::enable(greeter))
57 //!        .serve(addr)
58 //!        .await?;
59 //!
60 //!    Ok(())
61 //! }
62 //! ```
63 //!
64 //! ## Limitations
65 //!
66 //! * `tonic_web` is designed to work with grpc-web-compliant clients only. It is not expected to
67 //! handle arbitrary HTTP/x.x requests or bespoke protocols.
68 //! * Similarly, the cors support implemented  by this crate will *only* handle grpc-web and
69 //! grpc-web preflight requests.
70 //! * Currently, grpc-web clients can only perform `unary` and `server-streaming` calls. These
71 //! are the only requests this crate is designed to handle. Support for client and bi-directional
72 //! streaming will be officially supported when clients do.
73 //! * There is no support for web socket transports.
74 //!
75 //!
76 //! [`tonic`]: https://github.com/hyperium/tonic
77 //! [`tonic_web`]: https://github.com/hyperium/tonic
78 //! [grpc-web]: https://github.com/grpc/grpc-web
79 //! [tower]: https://github.com/tower-rs/tower
80 //! [`enable`]: crate::enable()
81 #![warn(
82     missing_debug_implementations,
83     missing_docs,
84     rust_2018_idioms,
85     unreachable_pub
86 )]
87 #![doc(html_root_url = "https://docs.rs/tonic-web/0.5.0")]
88 #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
89 
90 pub use layer::GrpcWebLayer;
91 pub use service::{GrpcWebService, ResponseFuture};
92 
93 mod call;
94 mod layer;
95 mod service;
96 
97 use http::header::HeaderName;
98 use std::time::Duration;
99 use tonic::body::BoxBody;
100 use tower_http::cors::{AllowOrigin, Cors, CorsLayer};
101 use tower_layer::Layer;
102 use tower_service::Service;
103 
104 const DEFAULT_MAX_AGE: Duration = Duration::from_secs(24 * 60 * 60);
105 const DEFAULT_EXPOSED_HEADERS: [&str; 3] =
106     ["grpc-status", "grpc-message", "grpc-status-details-bin"];
107 const DEFAULT_ALLOW_HEADERS: [&str; 4] =
108     ["x-grpc-web", "content-type", "x-user-agent", "grpc-timeout"];
109 
110 type BoxError = Box<dyn std::error::Error + Send + Sync>;
111 
112 /// Enable a tonic service to handle grpc-web requests with the default configuration.
113 ///
114 /// You can customize the CORS configuration composing the [`GrpcWebLayer`] with the cors layer of your choice.
115 pub fn enable<S>(service: S) -> Cors<GrpcWebService<S>>
116 where
117     S: Service<http::Request<hyper::Body>, Response = http::Response<BoxBody>>,
118     S: Clone + Send + 'static,
119     S::Future: Send + 'static,
120     S::Error: Into<BoxError> + Send,
121 {
122     CorsLayer::new()
123         .allow_origin(AllowOrigin::mirror_request())
124         .allow_credentials(true)
125         .max_age(DEFAULT_MAX_AGE)
126         .expose_headers(
127             DEFAULT_EXPOSED_HEADERS
128                 .iter()
129                 .cloned()
130                 .map(HeaderName::from_static)
131                 .collect::<Vec<HeaderName>>(),
132         )
133         .allow_headers(
134             DEFAULT_ALLOW_HEADERS
135                 .iter()
136                 .cloned()
137                 .map(HeaderName::from_static)
138                 .collect::<Vec<HeaderName>>(),
139         )
140         .layer(GrpcWebService::new(service))
141 }
142