Skip to main content

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}