use super::host_router::FrozenHostRouter;
use super::middleware::{MiddlewareFn, Next, Terminal};
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, RouteHandler, split_path_segments};
use super::{Request, Response};
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, LazyLock};
pub(super) struct StreamingProxyTarget {
pub(super) backend: Arc<str>,
pub(super) prefix: Arc<str>,
pub(super) params: RequestParams,
}
pub(super) enum RouteClass {
Buffered,
StreamingProxy(StreamingProxyTarget),
HeadOnly,
Unmatched,
Refused(Response),
}
#[cfg(feature = "grpc")]
pub use super::grpc_support::GrpcRouter;
static NOT_FOUND_HANDLER: LazyLock<Handler> = LazyLock::new(|| {
Box::new(|_req: &Request| {
Box::pin(async { not_found() }) as Pin<Box<dyn Future<Output = Response> + Send>>
})
});
fn not_found() -> Response {
Response::text_raw(404, "not found")
}
pub(super) fn service_unavailable() -> Response {
Response::text_raw(503, "service unavailable")
}
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,
#[cfg(feature = "grpc")]
pub(super) grpc_router: Option<GrpcRouter>,
}
pub(super) enum DispatchResult {
Async(Pin<Box<dyn Future<Output = Response> + Send>>, 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: Pin<Box<dyn Future<Output = Response> + Send>>,
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) struct GateCheck {
pub(super) reached: Arc<AtomicBool>,
pub(super) fut: Pin<Box<dyn Future<Output = Response> + Send>>,
}
impl FrozenRouter {
pub(super) fn classify_route(&self, head: &RequestHead<'_>) -> RouteClass {
let path = head.path();
let segments = match split_path_segments(path) {
Some(s) => s,
None => return RouteClass::Refused(Response::text_raw(414, "URI too long")),
};
match self.root.lookup(head.method(), path, &segments) {
Some((RouteHandler::Proxy { healthy, .. }, _))
| Some((RouteHandler::ProxyStream { healthy, .. }, _))
if upstream_unhealthy(healthy) =>
{
RouteClass::Refused(service_unavailable())
}
Some((
RouteHandler::ProxyStream {
backend, prefix, ..
},
params,
)) => RouteClass::StreamingProxy(StreamingProxyTarget {
backend: Arc::clone(backend),
prefix: Arc::clone(prefix),
params,
}),
Some((RouteHandler::Sse(_), _)) => RouteClass::HeadOnly,
#[cfg(feature = "ws")]
Some((RouteHandler::WebSocket(_), _)) => RouteClass::HeadOnly,
Some(_) => RouteClass::Buffered,
None => RouteClass::Unmatched,
}
}
pub(super) fn dispatch(&self, mut req: Request) -> DispatchResult {
let method = req.method_enum();
let path_owned: Box<str> = req.path().into();
let result = {
let segments = match split_path_segments(&path_owned) {
Some(s) => s,
None => {
let fut = Box::pin(async { Response::text_raw(414, "URI too long") });
return DispatchResult::Async(fut, req);
}
};
self.root.lookup(method, &path_owned, &segments)
};
match result {
Some((RouteHandler::Async(handler), params)) => {
req.set_params(params);
self.dispatch_async(handler, req).into()
}
Some((RouteHandler::Stream(handler), params)) => {
req.set_params(params);
let fut = handler(&req);
DispatchResult::Stream(fut, req)
}
Some((RouteHandler::Sse(handler), params)) => {
req.set_params(params);
DispatchResult::Sse(Arc::clone(handler), req)
}
#[cfg(feature = "ws")]
Some((RouteHandler::WebSocket(handler), params)) => {
req.set_params(params);
DispatchResult::WebSocket(Arc::clone(handler), req)
}
Some((RouteHandler::Proxy { healthy, .. }, _))
| Some((RouteHandler::ProxyStream { healthy, .. }, _))
if upstream_unhealthy(healthy) =>
{
let fut = Box::pin(async { service_unavailable() });
DispatchResult::Async(fut, req)
}
Some((
RouteHandler::Proxy {
backend, prefix, ..
},
params,
)) => {
req.set_params(params);
self.dispatch_proxy(ProxyKind::Buffered, req, backend, prefix)
}
Some((
RouteHandler::ProxyStream {
backend, prefix, ..
},
params,
)) => {
req.set_params(params);
self.dispatch_proxy(ProxyKind::Streaming, req, backend, prefix)
}
None => self.dispatch_async(&NOT_FOUND_HANDLER, req).into(),
}
}
fn dispatch_proxy(
&self,
kind: ProxyKind,
req: Request,
backend: &Arc<str>,
prefix: &Arc<str>,
) -> 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).into()
}
ProxyKind::Streaming => {
DispatchResult::ProxyStream(req, Arc::clone(backend), Arc::clone(prefix))
}
}
}
pub(super) fn dispatch_async(&self, handler: &Handler, req: Request) -> AsyncDispatch {
let terminal = Terminal::Handler(handler);
let next = Next::new(&self.middleware, terminal);
let fut = next.call(&req);
AsyncDispatch { fut, req }
}
pub(super) fn middleware_gate(&self, req: &Request) -> Option<GateCheck> {
match self.middleware.is_empty() {
true => None,
false => {
let reached = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&reached);
let terminal = Terminal::Gate(flag);
let next = Next::new(&self.middleware, terminal);
let fut = next.call(req);
Some(GateCheck { reached, fut })
}
}
}
pub(super) fn middleware_gate_head(
&self,
head: &RequestHead<'_>,
params: Option<RequestParams>,
) -> Option<GateCheck> {
match self.middleware.is_empty() {
true => None,
false => {
let gate_req = head.to_request(params);
self.middleware_gate(&gate_req)
}
}
}
}
enum ProxyKind {
Buffered,
Streaming,
}
fn dispatch_proxy_through_middleware(
router: &FrozenRouter,
req: Request,
backend: &Arc<str>,
prefix: &Arc<str>,
) -> AsyncDispatch {
let terminal = Terminal::Proxy {
backend: Arc::clone(backend),
prefix: Arc::clone(prefix),
};
let next = Next::new(&router.middleware, terminal);
let fut = next.call(&req);
AsyncDispatch { fut, req }
}
pub(super) fn gate_result(reached: Arc<AtomicBool>, resp: Response) -> Option<Response> {
match reached.load(Ordering::Acquire) {
true => None,
false => Some(resp),
}
}
pub(super) struct Routed<'a> {
pub(super) result: DispatchResult,
pub(super) router: Option<&'a FrozenRouter>,
}
pub(super) enum ServerDispatch {
Single(FrozenRouter),
Host(FrozenHostRouter),
}
impl ServerDispatch {
pub(super) fn classify_route(&self, head: &RequestHead<'_>) -> RouteClass {
match self.resolve_from_head(head) {
Ok(Some(router)) => router.classify_route(head),
Ok(None) => RouteClass::Unmatched,
Err(refusal) => RouteClass::Refused(refusal),
}
}
fn resolve_from_head(&self, head: &RequestHead<'_>) -> Result<Option<&FrozenRouter>, Response> {
match self {
Self::Single(router) => Ok(Some(router)),
Self::Host(host_router) => host_router.resolve_from_head(head),
}
}
fn resolve(&self, req: &Request) -> Result<Option<&FrozenRouter>, Response> {
match self {
Self::Single(router) => Ok(Some(router)),
Self::Host(host_router) => host_router.resolve(req),
}
}
fn fallback(error_resp: Option<Response>, req: Request) -> AsyncDispatch {
let fut: Pin<Box<dyn Future<Output = Response> + Send>> = match error_resp {
None => Box::pin(async { not_found() }),
Some(resp) => Box::pin(async move { resp }),
};
AsyncDispatch { fut, req }
}
pub(super) fn dispatch(&self, req: Request) -> Routed<'_> {
match self.resolve(&req) {
Ok(Some(router)) => Routed {
result: router.dispatch(req),
router: Some(router),
},
Ok(None) => Routed {
result: Self::fallback(None, req).into(),
router: None,
},
Err(resp) => Routed {
result: Self::fallback(Some(resp), req).into(),
router: None,
},
}
}
pub(super) fn middleware_gate_head(
&self,
head: &RequestHead<'_>,
params: Option<RequestParams>,
) -> Result<Option<GateCheck>, Response> {
match self.resolve_from_head(head)? {
Some(router) => Ok(router.middleware_gate_head(head, params)),
None => Ok(None),
}
}
pub(super) fn dispatch_with_handler(&self, handler: &Handler, req: Request) -> AsyncDispatch {
match self.resolve(&req) {
Ok(Some(router)) => router.dispatch_async(handler, req),
Ok(None) => Self::fallback(None, req),
Err(resp) => Self::fallback(Some(resp), 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,
}
}
}