use super::host_router::FrozenHostRouter;
use super::method::Method;
use super::middleware::{MiddlewareFn, Next, ResponseFuture, Terminal};
use super::rejection::{
Rejected, RejectionMapper, RejectionProtocol, RejectionScope, RequestIdentity,
};
use super::request::{Params as RequestParams, RequestHead};
use super::stream::StreamResponse;
pub(super) use super::trie::Handler;
pub(super) use super::trie::SseHandler;
#[cfg(feature = "ws")]
pub(super) use super::trie::WsHandler;
use super::trie::{
FrozenNode, PATH_SEGMENT_LIMIT, RouteHandler, RouteLookup, Selected, split_path_segments,
};
use super::{Request, Response};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
pub(super) struct StreamingProxyTarget {
pub(super) backend: Arc<str>,
pub(super) prefix: Arc<str>,
pub(super) params: RequestParams,
pub(super) method: Method,
}
struct Established {
route: Arc<str>,
protocol: RejectionProtocol,
}
pub(super) struct PreBodyScope {
mapper: Option<Arc<RejectionMapper>>,
established: Option<Established>,
}
impl PreBodyScope {
fn unrouted(mapper: Option<Arc<RejectionMapper>>) -> Self {
Self {
mapper,
established: None,
}
}
pub(super) fn scope(&self, identity: RequestIdentity) -> RejectionScope {
let identity = match &self.established {
Some(established) => identity
.with_route(Arc::clone(&established.route))
.with_protocol(established.protocol),
None => identity,
};
RejectionScope::new(self.mapper.clone(), identity)
}
}
pub(super) struct Classified<'a> {
pub(super) class: RouteClass,
pub(super) scope: PreBodyScope,
pub(super) router: Option<&'a FrozenRouter>,
}
#[derive(Clone, Copy)]
pub(super) enum HeadUpgrade {
None,
WebSocket,
}
impl HeadUpgrade {
pub(super) fn of(is_websocket: bool) -> Self {
match is_websocket {
true => Self::WebSocket,
false => Self::None,
}
}
fn is_websocket(self) -> bool {
matches!(self, Self::WebSocket)
}
}
pub(super) enum RouteClass {
Buffered(Method),
StreamingProxy(StreamingProxyTarget),
HeadOnly,
Terminal,
Refused(Rejected),
}
#[cfg(feature = "grpc")]
pub use super::grpc_support::GrpcRouter;
fn protocol_of(handler: &RouteHandler) -> RejectionProtocol {
match handler {
RouteHandler::Async(_) => RejectionProtocol::OrdinaryHttp,
RouteHandler::Stream(_) => RejectionProtocol::StreamingHttp,
RouteHandler::Sse(_) => RejectionProtocol::ServerSentEvents,
#[cfg(feature = "ws")]
RouteHandler::WebSocket(_) => RejectionProtocol::WebSocket,
RouteHandler::Proxy { .. } | RouteHandler::ProxyStream { .. } => RejectionProtocol::Proxy,
}
}
fn upstream_unhealthy(healthy: &Option<Arc<AtomicBool>>) -> bool {
healthy
.as_ref()
.is_some_and(|flag| !flag.load(Ordering::Relaxed))
}
pub(super) struct FrozenRouter {
pub(super) root: FrozenNode,
pub(super) middleware: Box<[MiddlewareFn]>,
pub(super) skip_middleware_for_internal: bool,
pub(super) mapper: Option<Arc<RejectionMapper>>,
#[cfg(feature = "grpc")]
pub(super) grpc_router: Option<GrpcRouter>,
}
pub(super) enum DispatchResult {
Async(ResponseFuture, Request),
Stream(
Pin<Box<dyn Future<Output = StreamResponse> + Send>>,
Request,
),
Sse(SseHandler, Request),
#[cfg(feature = "ws")]
WebSocket(WsHandler, Request),
#[cfg(feature = "ws")]
ProxyWebSocket(Request, Arc<str>, Arc<str>),
ProxyStream(Request, Arc<str>, Arc<str>),
}
pub(super) struct AsyncDispatch {
pub(super) fut: ResponseFuture,
pub(super) req: Request,
}
impl From<AsyncDispatch> for DispatchResult {
fn from(dispatch: AsyncDispatch) -> Self {
Self::Async(dispatch.fut, dispatch.req)
}
}
impl DispatchResult {
pub(super) fn needs_middleware_gate(&self) -> bool {
match self {
Self::Stream(..) | Self::Sse(..) | Self::ProxyStream(..) => true,
#[cfg(feature = "ws")]
Self::WebSocket(..) | Self::ProxyWebSocket(..) => true,
Self::Async(..) => false,
}
}
#[cfg(feature = "ws")]
pub(super) fn is_websocket(&self) -> bool {
matches!(self, Self::WebSocket(..) | Self::ProxyWebSocket(..))
}
pub(super) fn request_ref(&self) -> &Request {
match self {
Self::Async(_, req)
| Self::Stream(_, req)
| Self::Sse(_, req)
| Self::ProxyStream(req, _, _) => req,
#[cfg(feature = "ws")]
Self::WebSocket(_, req) | Self::ProxyWebSocket(req, _, _) => req,
}
}
}
pub(super) type GateCheck = ResponseFuture;
impl FrozenRouter {
fn classify_route(
&self,
head: &RequestHead<'_>,
upgrade: HeadUpgrade,
) -> (RouteClass, Option<Established>) {
let path = head.path();
let selected = match (head.routable_method(), split_path_segments(path)) {
(Some(method), Some(segments)) => self
.root
.select(method, path, &segments)
.map(|selected| (method, selected)),
_ => None,
};
match selected {
Some((method, selected)) => {
let established = Established {
route: Arc::clone(selected.route),
protocol: protocol_of(selected.handler),
};
(
self.classify_matched(selected, method, upgrade),
Some(established),
)
}
None => (RouteClass::Terminal, None),
}
}
fn classify_matched(
&self,
selected: Selected<'_, '_>,
method: Method,
upgrade: HeadUpgrade,
) -> RouteClass {
let handler = selected.handler;
match handler {
RouteHandler::Proxy { healthy, .. } | RouteHandler::ProxyStream { healthy, .. }
if upstream_unhealthy(healthy) =>
{
RouteClass::Refused(Rejected::no_admissible_backend())
}
RouteHandler::Proxy { .. } | RouteHandler::ProxyStream { .. }
if upgrade.is_websocket() =>
{
RouteClass::HeadOnly
}
RouteHandler::ProxyStream {
backend, prefix, ..
} => RouteClass::StreamingProxy(StreamingProxyTarget {
backend: Arc::clone(backend),
prefix: Arc::clone(prefix),
params: self.gate_params(selected),
method,
}),
RouteHandler::Sse(_) => RouteClass::HeadOnly,
#[cfg(feature = "ws")]
RouteHandler::WebSocket(_) => RouteClass::HeadOnly,
RouteHandler::Async(_) | RouteHandler::Stream(_) | RouteHandler::Proxy { .. } => {
RouteClass::Buffered(method)
}
}
}
fn gate_params(&self, selected: Selected<'_, '_>) -> RequestParams {
match self.middleware.is_empty() {
true => RequestParams::default(),
false => selected.bind_params(),
}
}
pub(super) fn dispatch(
&self,
mut req: Request,
mapper: Option<Arc<RejectionMapper>>,
) -> (DispatchResult, RejectionScope) {
let identity = RequestIdentity::from_request(&req);
let path: Box<str> = req.path().into();
let lookup = match split_path_segments(&path) {
Some(segments) => self.root.lookup(req.method_enum(), &path, &segments),
None => {
let refusal = Rejected::uri_too_deep(PATH_SEGMENT_LIMIT);
return self.terminal(req, mapper, identity, refusal);
}
};
match lookup {
RouteLookup::Matched(selected) => {
let route = Arc::clone(selected.route);
let handler = selected.handler;
req.set_params(selected.bind_params());
let scope = RejectionScope::new(
mapper,
identity
.with_route(route)
.with_protocol(protocol_of(handler)),
);
(self.dispatch_matched(handler, req, &scope), scope)
}
RouteLookup::MethodMismatch { route, allow } => {
let refusal = Rejected::method_not_allowed(req.method(), &allow);
self.terminal(req, mapper, identity.with_route(route), refusal)
}
RouteLookup::Unmatched => {
let refusal = Rejected::no_route();
self.terminal(req, mapper, identity, refusal)
}
}
}
fn dispatch_matched(
&self,
handler: &RouteHandler,
req: Request,
scope: &RejectionScope,
) -> DispatchResult {
match handler {
RouteHandler::Async(handler) => self.dispatch_async(handler, req, scope.clone()).into(),
RouteHandler::Stream(handler) => {
let fut = handler(&req);
DispatchResult::Stream(fut, req)
}
RouteHandler::Sse(handler) => DispatchResult::Sse(Arc::clone(handler), req),
#[cfg(feature = "ws")]
RouteHandler::WebSocket(handler) => DispatchResult::WebSocket(Arc::clone(handler), req),
RouteHandler::Proxy {
backend,
prefix,
healthy,
} => {
self.dispatch_proxy_route(ProxyKind::Buffered, req, backend, prefix, healthy, scope)
}
RouteHandler::ProxyStream {
backend,
prefix,
healthy,
} => self.dispatch_proxy_route(
ProxyKind::Streaming,
req,
backend,
prefix,
healthy,
scope,
),
}
}
fn dispatch_proxy_route(
&self,
kind: ProxyKind,
req: Request,
backend: &Arc<str>,
prefix: &Arc<str>,
healthy: &Option<Arc<AtomicBool>>,
scope: &RejectionScope,
) -> DispatchResult {
match upstream_unhealthy(healthy) {
true => {
let refusal = scope.clone();
DispatchResult::Async(
Box::pin(async move { refusal.map(Rejected::no_admissible_backend()) }),
req,
)
}
false => self.dispatch_proxy(kind, req, backend, prefix, scope),
}
}
fn terminal(
&self,
req: Request,
mapper: Option<Arc<RejectionMapper>>,
identity: RequestIdentity,
rejected: Rejected,
) -> (DispatchResult, RejectionScope) {
let scope = RejectionScope::new(mapper, identity);
let next = Next::new(
&self.middleware,
Terminal::Rejected(rejected),
scope.clone(),
);
let fut = next.call(&req);
(DispatchResult::Async(fut, req), scope)
}
fn dispatch_proxy(
&self,
kind: ProxyKind,
req: Request,
backend: &Arc<str>,
prefix: &Arc<str>,
scope: &RejectionScope,
) -> DispatchResult {
#[cfg(feature = "ws")]
if super::ws_proxy::is_ws_upgrade_request(&req) {
return DispatchResult::ProxyWebSocket(req, Arc::clone(backend), Arc::clone(prefix));
}
match kind {
ProxyKind::Buffered => {
dispatch_proxy_through_middleware(self, req, backend, prefix, scope).into()
}
ProxyKind::Streaming => {
DispatchResult::ProxyStream(req, Arc::clone(backend), Arc::clone(prefix))
}
}
}
pub(super) fn dispatch_async(
&self,
handler: &Handler,
req: Request,
scope: RejectionScope,
) -> AsyncDispatch {
let next = Next::new(&self.middleware, Terminal::Handler(handler), scope);
let fut = next.call(&req);
AsyncDispatch { fut, req }
}
pub(super) fn middleware_gate(
&self,
req: &Request,
scope: &RejectionScope,
) -> Option<GateCheck> {
match self.middleware.is_empty() {
true => None,
false => Some(self.gate_chain(req, scope)),
}
}
pub(super) fn middleware_gate_head(
&self,
head: &RequestHead<'_>,
params: Option<RequestParams>,
scope: &RejectionScope,
) -> Option<GateCheck> {
match self.middleware.is_empty() {
true => None,
false => {
let gate_req = head.to_request(params);
Some(self.gate_chain(&gate_req, scope))
}
}
}
fn gate_chain(&self, req: &Request, scope: &RejectionScope) -> GateCheck {
let next = Next::new(&self.middleware, Terminal::Gate, scope.clone());
next.call(req)
}
}
enum ProxyKind {
Buffered,
Streaming,
}
fn dispatch_proxy_through_middleware(
router: &FrozenRouter,
req: Request,
backend: &Arc<str>,
prefix: &Arc<str>,
scope: &RejectionScope,
) -> AsyncDispatch {
let terminal = Terminal::Proxy {
backend: Arc::clone(backend),
prefix: Arc::clone(prefix),
};
let next = Next::new(&router.middleware, terminal, scope.clone());
let fut = next.call(&req);
AsyncDispatch { fut, req }
}
pub(super) fn gate_result(resp: Response) -> Option<Response> {
match resp.provenance().is_gate_passthrough() {
true => None,
false => Some(resp),
}
}
pub(super) struct Routed<'a> {
pub(super) result: DispatchResult,
pub(super) router: Option<&'a FrozenRouter>,
pub(super) scope: RejectionScope,
}
pub(super) type Resolution<'a> = Result<Option<&'a FrozenRouter>, Rejected>;
pub(super) enum ServerDispatch {
Single(FrozenRouter),
Host(FrozenHostRouter),
}
impl ServerDispatch {
pub(super) fn classify_route<'a>(
&'a self,
head: &RequestHead<'_>,
upgrade: HeadUpgrade,
) -> Classified<'a> {
match self.resolve_from_head(head) {
Ok(Some(router)) => {
let (class, established) = router.classify_route(head, upgrade);
Classified {
class,
scope: PreBodyScope {
mapper: self.select_mapper(Some(router)),
established,
},
router: Some(router),
}
}
Ok(None) => Classified {
class: RouteClass::Terminal,
scope: PreBodyScope::unrouted(self.select_mapper(None)),
router: None,
},
Err(rejected) => Classified {
class: RouteClass::Refused(rejected),
scope: PreBodyScope::unrouted(self.select_mapper(None)),
router: None,
},
}
}
pub(super) fn resolve_from_head(&self, head: &RequestHead<'_>) -> Resolution<'_> {
match self {
Self::Single(router) => Ok(Some(router)),
Self::Host(host_router) => host_router.resolve_from_head(head),
}
}
pub(super) fn resolve(&self, req: &Request) -> Resolution<'_> {
match self {
Self::Single(router) => Ok(Some(router)),
Self::Host(host_router) => host_router.resolve(req),
}
}
fn router_for(&self, authority: &str) -> Option<&FrozenRouter> {
match self {
Self::Single(router) => Some(router),
Self::Host(host_router) => host_router.router_for(authority),
}
}
fn select_mapper(&self, router: Option<&FrozenRouter>) -> Option<Arc<RejectionMapper>> {
let child = router.and_then(|router| router.mapper.clone());
match self {
Self::Single(_) => child,
Self::Host(hosts) => child.or_else(|| hosts.mapper()),
}
}
pub(super) fn host_scope(&self, identity: RequestIdentity) -> RejectionScope {
RejectionScope::new(self.select_mapper(None), identity)
}
pub(super) fn head_scope(
&self,
head: &RequestHead<'_>,
identity: RequestIdentity,
) -> RejectionScope {
RejectionScope::new(
self.select_mapper(self.router_for(head.authority())),
identity,
)
}
pub(super) fn resolved_head_scope(
&self,
resolved: &Resolution<'_>,
identity: RequestIdentity,
) -> RejectionScope {
RejectionScope::new(
self.select_mapper(resolved.as_ref().ok().copied().flatten()),
identity,
)
}
pub(super) fn resolved_scope(
&self,
resolved: &Resolution<'_>,
req: &Request,
) -> RejectionScope {
self.resolved_head_scope(resolved, RequestIdentity::from_request(req))
}
pub(super) fn dispatch_resolved<'a>(
&'a self,
req: Request,
router: Option<&'a FrozenRouter>,
) -> Routed<'a> {
match router {
Some(router) => {
let (result, scope) = router.dispatch(req, self.select_mapper(Some(router)));
Routed {
result,
router: Some(router),
scope,
}
}
None => self.host_terminal(req, Rejected::not_found("no router claims this authority")),
}
}
fn host_terminal(&self, req: Request, rejected: Rejected) -> Routed<'_> {
let scope = self.host_scope(RequestIdentity::from_request(&req));
let mapping = scope.clone();
let fut: ResponseFuture = Box::pin(async move { mapping.map(rejected) });
Routed {
result: DispatchResult::Async(fut, req),
router: None,
scope,
}
}
pub(super) fn dispatch_with_handler(
resolved: Resolution<'_>,
handler: &Handler,
req: Request,
scope: RejectionScope,
) -> AsyncDispatch {
match resolved {
Ok(Some(router)) => router.dispatch_async(handler, req, scope),
Ok(None) => Self::refuse(req, scope, Rejected::no_route()),
Err(rejected) => Self::refuse(req, scope, rejected),
}
}
fn refuse(req: Request, scope: RejectionScope, rejected: Rejected) -> AsyncDispatch {
let fut: ResponseFuture = Box::pin(async move { scope.map(rejected) });
AsyncDispatch { fut, req }
}
pub(super) fn skip_middleware_for_internal(&self) -> bool {
match self {
Self::Single(router) => router.skip_middleware_for_internal,
Self::Host(_) => false,
}
}
#[cfg(feature = "grpc")]
pub(super) fn grpc_router(&self) -> Option<&super::grpc_support::GrpcRouter> {
match self {
Self::Single(router) => router.grpc_router.as_ref(),
Self::Host(_) => None,
}
}
}