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