#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
unreachable_pub
)]
#![doc(html_root_url = "https://docs.rs/tonic-web/0.9.2")]
#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
pub use layer::GrpcWebLayer;
pub use service::{GrpcWebService, ResponseFuture};
mod call;
mod layer;
mod service;
use http::header::HeaderName;
use std::time::Duration;
use tonic::{body::BoxBody, server::NamedService};
use tower_http::cors::{AllowOrigin, CorsLayer};
use tower_layer::Layer;
use tower_service::Service;
const DEFAULT_MAX_AGE: Duration = Duration::from_secs(24 * 60 * 60);
const DEFAULT_EXPOSED_HEADERS: [&str; 3] =
["grpc-status", "grpc-message", "grpc-status-details-bin"];
const DEFAULT_ALLOW_HEADERS: [&str; 4] =
["x-grpc-web", "content-type", "x-user-agent", "grpc-timeout"];
type BoxError = Box<dyn std::error::Error + Send + Sync>;
pub fn enable<S>(service: S) -> CorsGrpcWeb<S>
where
S: Service<http::Request<hyper::Body>, Response = http::Response<BoxBody>>,
S: Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<BoxError> + Send,
{
let cors = CorsLayer::new()
.allow_origin(AllowOrigin::mirror_request())
.allow_credentials(true)
.max_age(DEFAULT_MAX_AGE)
.expose_headers(
DEFAULT_EXPOSED_HEADERS
.iter()
.cloned()
.map(HeaderName::from_static)
.collect::<Vec<HeaderName>>(),
)
.allow_headers(
DEFAULT_ALLOW_HEADERS
.iter()
.cloned()
.map(HeaderName::from_static)
.collect::<Vec<HeaderName>>(),
);
tower_layer::layer_fn(|s| CorsGrpcWeb(cors.layer(s))).layer(GrpcWebService::new(service))
}
#[derive(Debug, Clone)]
pub struct CorsGrpcWeb<S>(tower_http::cors::Cors<GrpcWebService<S>>);
impl<S> Service<http::Request<hyper::Body>> for CorsGrpcWeb<S>
where
S: Service<http::Request<hyper::Body>, Response = http::Response<BoxBody>>,
S: Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<BoxError> + Send,
{
type Response = S::Response;
type Error = S::Error;
type Future =
<tower_http::cors::Cors<GrpcWebService<S>> as Service<http::Request<hyper::Body>>>::Future;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, req: http::Request<hyper::Body>) -> Self::Future {
self.0.call(req)
}
}
impl<S> NamedService for CorsGrpcWeb<S>
where
S: NamedService,
{
const NAME: &'static str = S::NAME;
}
pub(crate) mod util {
pub(crate) mod base64 {
use base64::{
alphabet,
engine::{
general_purpose::{GeneralPurpose, GeneralPurposeConfig},
DecodePaddingMode,
},
};
pub(crate) const STANDARD: GeneralPurpose = GeneralPurpose::new(
&alphabet::STANDARD,
GeneralPurposeConfig::new()
.with_encode_padding(true)
.with_decode_padding_mode(DecodePaddingMode::Indifferent),
);
}
}