grpc_webnext/backend.rs
1//! The gRPC dispatch target.
2//!
3//! Everything else in this crate is inbound protocol translation (Fetch + WebSocket ⇄
4//! gRPC); the only thing that varies between "wrap an in-process service" and "proxy an
5//! upstream" is where the translated gRPC call lands. Both destinations are the same
6//! interface — `tower::Service<http::Request, Response = http::Response>` reduced to
7//! `.oneshot(req)` — so the difference is one enum.
8
9use http::{Request, Response};
10use http_body_util::BodyExt;
11use tonic::body::Body as TonicBody;
12use tonic::service::Routes;
13use tonic::transport::Channel;
14use tower::ServiceExt;
15
16use crate::{BoxError, ResBody};
17
18/// Where a translated gRPC request is dispatched.
19#[derive(Clone)]
20pub enum Backend {
21 /// In-process: call the local tonic `Routes` (wrap a service you own).
22 InProcess(Routes),
23 /// Upstream: forward to a remote gRPC server over an HTTP/2 `Channel` (proxy mode).
24 Upstream(Channel),
25}
26
27impl Backend {
28 /// Dispatch a gRPC-framed HTTP request and return the HTTP response. The two backends'
29 /// response bodies differ in type; both are boxed into one `ResBody` here, so every
30 /// caller downstream is monomorphic. `InProcess` is infallible (its `Service::Error`
31 /// is `Infallible`); `Upstream` surfaces the transport error for the caller to map to a
32 /// gRPC status (`UNAVAILABLE` / `BAD_GATEWAY`).
33 pub async fn call(&self, req: Request<TonicBody>) -> Result<Response<ResBody>, BoxError> {
34 match self {
35 Backend::InProcess(routes) => {
36 let resp = routes.clone().oneshot(req).await.unwrap_or_else(|e| match e {});
37 Ok(resp.map(|b| b.map_err(Into::into).boxed_unsync()))
38 }
39 Backend::Upstream(channel) => {
40 let resp = channel.clone().oneshot(req).await.map_err(|e| Box::new(e) as BoxError)?;
41 Ok(resp.map(|b| b.map_err(Into::into).boxed_unsync()))
42 }
43 }
44 }
45
46 /// Whether this backend forwards to a remote upstream (proxy mode). Used to pick
47 /// policies that only make sense across the network — enforcing the client deadline
48 /// locally, forwarding `grpc-timeout` with grace, capping concurrent streams.
49 pub fn is_upstream(&self) -> bool {
50 matches!(self, Backend::Upstream(_))
51 }
52}