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 //! See the [`Config`] documentation for details. 38 //! 39 //! Alternatively, if you have a tls enabled server, you could skip setting `accept_http1` to `true`. 40 //! This works because the browser will handle `ALPN`. 41 //! 42 //! ```ignore 43 //! #[tokio::main] 44 //! async fn main() -> Result<(), Box<dyn std::error::Error>> { 45 //! let cert = tokio::fs::read("server.pem").await?; 46 //! let key = tokio::fs::read("server.key").await?; 47 //! let identity = Identity::from_pem(cert, key); 48 //! 49 //! let addr = "[::1]:50051".parse().unwrap(); 50 //! let greeter = GreeterServer::new(MyGreeter::default()); 51 //! 52 //! // No need to enable HTTP/1 53 //! Server::builder() 54 //! .tls_config(ServerTlsConfig::new().identity(identity))? 55 //! .add_service(tonic_web::enable(greeter)) 56 //! .serve(addr) 57 //! .await?; 58 //! 59 //! Ok(()) 60 //! } 61 //! ``` 62 //! 63 //! ## Limitations 64 //! 65 //! * `tonic_web` is designed to work with grpc-web-compliant clients only. It is not expected to 66 //! handle arbitrary HTTP/x.x requests or bespoke protocols. 67 //! * Similarly, the cors support implemented by this crate will *only* handle grpc-web and 68 //! grpc-web preflight requests. 69 //! * Currently, grpc-web clients can only perform `unary` and `server-streaming` calls. These 70 //! are the only requests this crate is designed to handle. Support for client and bi-directional 71 //! streaming will be officially supported when clients do. 72 //! * There is no support for web socket transports. 73 //! 74 //! 75 //! [`tonic`]: https://github.com/hyperium/tonic 76 //! [`tonic_web`]: https://github.com/hyperium/tonic 77 //! [grpc-web]: https://github.com/grpc/grpc-web 78 //! [tower]: https://github.com/tower-rs/tower 79 //! [`enable`]: crate::enable() 80 //! [`Config`]: crate::Config 81 #![warn( 82 missing_debug_implementations, 83 missing_docs, 84 rust_2018_idioms, 85 unreachable_pub 86 )] 87 88 pub use config::Config; 89 90 mod call; 91 mod config; 92 mod cors; 93 mod service; 94 95 use crate::service::GrpcWeb; 96 use std::future::Future; 97 use std::pin::Pin; 98 use tonic::body::BoxBody; 99 use tonic::transport::NamedService; 100 use tower_service::Service; 101 102 /// enable a tonic service to handle grpc-web requests with the default configuration. 103 /// 104 /// Shortcut for `tonic_web::config().enable(service)` 105 pub fn enable<S>(service: S) -> GrpcWeb<S> 106 where 107 S: Service<http::Request<hyper::Body>, Response = http::Response<BoxBody>>, 108 S: NamedService + Clone + Send + 'static, 109 S::Future: Send + 'static, 110 S::Error: Into<BoxError> + Send, 111 { 112 config().enable(service) 113 } 114 115 /// returns a default [`Config`] instance for configuring services. 116 /// 117 /// ## Example 118 /// 119 /// ``` 120 /// let config = tonic_web::config() 121 /// .allow_origins(vec!["http://foo.com"]) 122 /// .allow_credentials(false) 123 /// .expose_headers(vec!["x-request-id"]); 124 /// 125 /// // let greeter = config.enable(Greeter); 126 /// // let route_guide = config.enable(RouteGuide); 127 /// ``` 128 pub fn config() -> Config { 129 Config::default() 130 } 131 132 type BoxError = Box<dyn std::error::Error + Send + Sync>; 133 type BoxFuture<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>; 134