use super::body::HyperResponseBody;
use super::disconnect::DisconnectSignal;
use super::dispatch::{AsyncDispatch, FrozenRouter, RouteClass, Routed};
#[cfg(feature = "profiling")]
use super::internal_routes::match_profiling_route;
use super::internal_routes::{
build_internal_handler, invoke_internal_route, match_internal_route_from_path,
};
use super::record::record_request;
use super::request::{RequestHead, RequestOrigin, method_is_head};
use super::router::{DispatchResult, GateCheck, ServerDispatch, gate_result};
use super::server_lifecycle::ConnectionLifecycle;
use super::streaming::{
dispatch_streaming_proxy, handle_proxy_stream_response, handle_sse, handle_stream_response,
};
#[cfg(feature = "ws")]
use super::ws_proxy::{self, WsUpgrade};
use super::{BufferConfig, Request, Response};
use crate::resource::HealthState;
use crate::runtime_state::RuntimeInner;
use std::sync::Arc;
use std::time::Duration;
const REQUEST_BODY_TIMEOUT: Duration = Duration::from_secs(30);
#[cfg(feature = "grpc")]
use super::grpc_support::is_grpc_request;
pub(super) struct ConnCtx {
pub(super) tracing_enabled: bool,
pub(super) metrics_handle: Option<metrics_exporter_prometheus::PrometheusHandle>,
#[cfg(feature = "profiling")]
pub(super) profiling_enabled: bool,
pub(super) max_request_body: usize,
pub(super) sse_buffer_size: usize,
#[cfg(feature = "ws")]
pub(super) ws_buffer_size: usize,
pub(super) health_state: Option<HealthState>,
pub(super) is_tls: bool,
}
impl ConnCtx {
pub(super) fn from_runtime(
rt: &Arc<RuntimeInner>,
buffers: BufferConfig,
is_tls: bool,
) -> Self {
Self {
tracing_enabled: rt.config.tracing_enabled,
metrics_handle: rt.metrics_handle.clone(),
#[cfg(feature = "profiling")]
profiling_enabled: rt.config.profiling_enabled,
max_request_body: buffers.max_request_body,
sse_buffer_size: buffers.sse_buffer_size,
#[cfg(feature = "ws")]
ws_buffer_size: buffers.ws_buffer_size,
health_state: rt.health_state.clone(),
is_tls,
}
}
}
struct Refused {
refusal: Response,
method: Option<super::method::Method>,
uri: hyper::Uri,
}
impl Refused {
fn unnameable(uri: hyper::Uri) -> Box<Self> {
Box::new(Self {
refusal: method_not_allowed_response(),
method: None,
uri,
})
}
}
type Building = Result<DispatchInput, Box<Refused>>;
async fn collect_body_limited(
hyper_req: hyper::Request<hyper::body::Incoming>,
max_body: usize,
origin: RequestOrigin<'_>,
lifecycle_script: Option<&super::mock::LifecycleScript>,
method: super::method::Method,
) -> Result<Request, Box<Refused>> {
let (parts, body) = hyper_req.into_parts();
let body_bytes = match collect_body(body, max_body, lifecycle_script).await {
Ok(bytes) => bytes,
Err(refusal) => {
return Err(Box::new(Refused {
refusal,
method: Some(method),
uri: parts.uri,
}));
}
};
Ok(Request::from_hyper(parts, body_bytes, origin, method))
}
async fn collect_body(
body: hyper::body::Incoming,
max_body: usize,
lifecycle_script: Option<&super::mock::LifecycleScript>,
) -> Result<bytes::Bytes, Response> {
use http_body_util::BodyExt;
super::mock::LifecycleScript::pause_at(
lifecycle_script,
super::mock::LifecycleCheckpoint::RequestBodyLimitConfigured(max_body),
)
.await;
let limited = http_body_util::Limited::new(body, max_body);
match tokio::time::timeout(REQUEST_BODY_TIMEOUT, limited.collect()).await {
Ok(Ok(collected)) => Ok(collected.to_bytes()),
Ok(Err(_)) => Err(Response::text_raw(413, "request body too large")),
Err(_) => Err(Response::text_raw(408, "request body timeout")),
}
}
#[cfg(feature = "ws")]
type DispatchInput = (Request, WsUpgrade);
#[cfg(not(feature = "ws"))]
type DispatchInput = Request;
#[cfg(feature = "ws")]
fn build_head_only_request(
mut hyper_req: hyper::Request<hyper::body::Incoming>,
origin: RequestOrigin<'_>,
) -> Building {
let ws = ws_proxy::extract_ws_upgrade(&mut hyper_req);
let head = RequestHead::from_hyper_request(&hyper_req, origin)
.ok_or_else(|| Refused::unnameable(hyper_req.uri().clone()))?;
Ok((head.to_request(None), ws))
}
#[cfg(not(feature = "ws"))]
fn build_head_only_request(
hyper_req: hyper::Request<hyper::body::Incoming>,
origin: RequestOrigin<'_>,
) -> Building {
let head = RequestHead::from_hyper_request(&hyper_req, origin)
.ok_or_else(|| Refused::unnameable(hyper_req.uri().clone()))?;
Ok(head.to_request(None))
}
#[cfg(feature = "ws")]
async fn collect_request(
hyper_req: hyper::Request<hyper::body::Incoming>,
max_body: usize,
origin: RequestOrigin<'_>,
lifecycle_script: Option<&super::mock::LifecycleScript>,
method: super::method::Method,
) -> Building {
let mut r = hyper_req;
let ws_upgrade = ws_proxy::extract_ws_upgrade(&mut r);
let req = collect_body_limited(r, max_body, origin, lifecycle_script, method).await?;
Ok((req, ws_upgrade))
}
#[cfg(not(feature = "ws"))]
async fn collect_request(
hyper_req: hyper::Request<hyper::body::Incoming>,
max_body: usize,
origin: RequestOrigin<'_>,
lifecycle_script: Option<&super::mock::LifecycleScript>,
method: super::method::Method,
) -> Building {
collect_body_limited(hyper_req, max_body, origin, lifecycle_script, method).await
}
async fn build_dispatch_input(
hyper_req: hyper::Request<hyper::body::Incoming>,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
lifecycle: &ConnectionLifecycle,
skip_body: bool,
method: super::method::Method,
) -> Building {
match skip_body {
true => build_head_only_request(hyper_req, origin),
false => {
let lifecycle_script = lifecycle.script();
collect_request(
hyper_req,
ctx.max_request_body,
origin,
lifecycle_script.as_deref(),
method,
)
.await
}
}
}
enum PreBodyRoute {
Internal(super::internal_routes::InternalRoute, super::method::Method),
Class(RouteClass, super::method::Method),
Unnameable,
}
fn classify_pre_body(
hyper_req: &hyper::Request<hyper::body::Incoming>,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
) -> PreBodyRoute {
let head = match RequestHead::from_hyper_request(hyper_req, origin) {
Some(head) => head,
None => return PreBodyRoute::Unnameable,
};
let internal = match_internal_route_from_path(head.path(), ctx);
#[cfg(feature = "profiling")]
let internal = internal.or_else(|| match_profiling_route(head.path(), head.raw_query(), ctx));
match internal {
Some(route) => PreBodyRoute::Internal(route, head.method()),
None => PreBodyRoute::Class(dispatch.classify_route(&head), head.method()),
}
}
fn pending_middleware_gate(
result: &DispatchResult,
router: Option<&FrozenRouter>,
) -> Option<GateCheck> {
match (result.needs_middleware_gate(), router) {
(true, Some(router)) => router.middleware_gate(result.request_ref()),
_ => None,
}
}
fn answer(
ctx: &ConnCtx,
method: &'static str,
path: &str,
is_head: bool,
resp: Response,
start: std::time::Instant,
) -> hyper::Response<HyperResponseBody> {
let converted = to_hyper_full(strip_body_if_head(is_head, resp));
record_request(ctx, method, path, converted.status().as_u16(), start);
converted
}
fn refuse(
ctx: &ConnCtx,
result: &DispatchResult,
refusal: Response,
start: std::time::Instant,
) -> hyper::Response<HyperResponseBody> {
let req = result.request_ref();
answer(ctx, req.method(), req.path(), req.is_head(), refusal, start)
}
const UNNAMEABLE_METHOD: &str = "UNKNOWN";
pub(super) fn refuse_head(
ctx: &ConnCtx,
method: Option<super::method::Method>,
path: &str,
refusal: Response,
start: std::time::Instant,
) -> hyper::Response<HyperResponseBody> {
let is_head = method.is_some_and(method_is_head);
let name = method.map_or(UNNAMEABLE_METHOD, super::method::Method::as_str);
answer(ctx, name, path, is_head, refusal, start)
}
struct RequestDispatch<'a> {
dispatch: &'a ServerDispatch,
ctx: &'a ConnCtx,
origin: RequestOrigin<'a>,
lifecycle: &'a ConnectionLifecycle,
start: std::time::Instant,
}
async fn dispatch_classified_route(
hyper_req: hyper::Request<hyper::body::Incoming>,
route_class: RouteClass,
method: super::method::Method,
request_dispatch: &RequestDispatch<'_>,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
let &RequestDispatch {
dispatch,
ctx,
origin,
lifecycle,
start,
} = request_dispatch;
#[cfg(feature = "ws")]
let is_ws_upgrade = ws_proxy::is_ws_upgrade_head(hyper_req.headers());
#[cfg(not(feature = "ws"))]
let is_ws_upgrade = false;
let skip_body_collection = matches!(route_class, RouteClass::HeadOnly | RouteClass::Unmatched)
|| (matches!(&route_class, RouteClass::StreamingProxy(_)) && is_ws_upgrade);
match route_class {
RouteClass::StreamingProxy(target) if !is_ws_upgrade => {
return dispatch_streaming_proxy(
hyper_req, dispatch, ctx, target, origin, method, start,
)
.await;
}
RouteClass::Refused(refusal) => {
let path = hyper_req.uri().path();
return Ok(refuse_head(ctx, Some(method), path, refusal, start));
}
RouteClass::HeadOnly
| RouteClass::Unmatched
| RouteClass::Buffered
| RouteClass::StreamingProxy(_) => {}
}
let input = match build_dispatch_input(
hyper_req,
ctx,
origin,
lifecycle,
skip_body_collection,
method,
)
.await
{
Ok(input) => input,
Err(refused) => {
let Refused {
refusal,
method: refused_method,
uri,
} = *refused;
return Ok(refuse_head(ctx, refused_method, uri.path(), refusal, start));
}
};
dispatch_built_request(input, request_dispatch).await
}
async fn dispatch_built_request(
input: DispatchInput,
request_dispatch: &RequestDispatch<'_>,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
let &RequestDispatch {
dispatch,
ctx,
lifecycle,
start,
..
} = request_dispatch;
#[cfg(feature = "ws")]
let (req, ws_upgrade) = input;
#[cfg(not(feature = "ws"))]
let req = input;
let Routed { result, router } = dispatch.dispatch(req);
let gate_blocked = match pending_middleware_gate(&result, router) {
None => None,
Some(GateCheck { reached, fut }) => gate_result(reached, fut.await),
};
if let Some(blocked) = gate_blocked {
return Ok(refuse(ctx, &result, blocked, start));
}
#[cfg(feature = "ws")]
if let Some(rejected) = result
.is_websocket()
.then(|| ws_proxy::check_ws_origin(result.request_ref()))
.flatten()
{
return Ok(refuse(ctx, &result, rejected, start));
}
match result {
DispatchResult::Async(fut, req) => finish_async(ctx, &req, fut.await, start),
DispatchResult::Stream(fut, req) => handle_stream_response(fut.await, req, ctx, start),
DispatchResult::Sse(handler, req) => {
record_request(ctx, req.method(), req.path(), 200, start);
handle_sse(handler, req, ctx.sse_buffer_size, lifecycle).await
}
#[cfg(feature = "ws")]
DispatchResult::WebSocket(handler, req) => {
record_upgrade(ctx, req, start, |req| {
ws_proxy::handle_ws_upgrade(ws_upgrade, handler, req, ctx.ws_buffer_size, lifecycle)
})
.await
}
#[cfg(feature = "ws")]
DispatchResult::ProxyWebSocket(req, backend, prefix) => {
record_upgrade(ctx, req, start, |req| {
ws_proxy::handle_proxy_ws(ws_upgrade, req, backend, prefix, lifecycle)
})
.await
}
DispatchResult::ProxyStream(req, backend, prefix) => {
handle_proxy_stream_response(req, &backend, &prefix, ctx, start).await
}
}
}
pub(super) async fn handle_request(
hyper_req: hyper::Request<hyper::body::Incoming>,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
remote_addr: Option<std::net::IpAddr>,
lifecycle: &ConnectionLifecycle,
disconnect: DisconnectSignal,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
let start = std::time::Instant::now();
let origin = RequestOrigin {
remote_addr,
is_tls: ctx.is_tls,
disconnect: &disconnect,
};
#[cfg(feature = "grpc")]
let hyper_req = match try_dispatch_grpc(hyper_req, dispatch, ctx, origin, start).await {
GrpcDispatch::Handled(resp) => return resp,
GrpcDispatch::NotGrpc(req) => req,
};
let (route_class, method) = match classify_pre_body(&hyper_req, dispatch, ctx, origin) {
PreBodyRoute::Internal(route, method) => {
return dispatch_internal_head_only(
&hyper_req, route, dispatch, ctx, origin, method, start,
)
.await;
}
PreBodyRoute::Unnameable => {
return Ok(refuse_head(
ctx,
None,
hyper_req.uri().path(),
method_not_allowed_response(),
start,
));
}
PreBodyRoute::Class(class, method) => (class, method),
};
let request_dispatch = RequestDispatch {
dispatch,
ctx,
origin,
lifecycle,
start,
};
dispatch_classified_route(hyper_req, route_class, method, &request_dispatch).await
}
fn finish_async(
ctx: &ConnCtx,
req: &Request,
resp: Response,
start: std::time::Instant,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
Ok(answer(
ctx,
req.method(),
req.path(),
req.is_head(),
resp,
start,
))
}
#[cfg(feature = "ws")]
async fn record_upgrade<F, Fut>(
ctx: &ConnCtx,
req: Request,
start: std::time::Instant,
upgrade: F,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible>
where
F: FnOnce(Request) -> Fut,
Fut: std::future::Future<Output = hyper::Response<HyperResponseBody>>,
{
let (method, uri) = (req.method(), req.uri_owned());
let resp = upgrade(req).await;
record_request(ctx, method, uri.path(), resp.status().as_u16(), start);
Ok(resp)
}
async fn dispatch_internal_head_only(
hyper_req: &hyper::Request<hyper::body::Incoming>,
route: super::internal_routes::InternalRoute,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
method: super::method::Method,
start: std::time::Instant,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
match dispatch.skip_middleware_for_internal() {
true => Ok(answer(
ctx,
method.as_str(),
hyper_req.uri().path(),
method_is_head(method),
invoke_internal_route(&route),
start,
)),
false => {
dispatch_internal_through_middleware(hyper_req, route, dispatch, ctx, origin, start)
.await
}
}
}
async fn dispatch_internal_through_middleware(
hyper_req: &hyper::Request<hyper::body::Incoming>,
route: super::internal_routes::InternalRoute,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
start: std::time::Instant,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
let head = match RequestHead::from_hyper_request(hyper_req, origin) {
Some(h) => h,
None => {
return Ok(refuse_head(
ctx,
None,
hyper_req.uri().path(),
method_not_allowed_response(),
start,
));
}
};
let req = head.to_request(None);
let handler = build_internal_handler(route);
let AsyncDispatch { fut, req } = dispatch.dispatch_with_handler(&handler, req);
finish_async(ctx, &req, fut.await, start)
}
pub(super) fn to_hyper_full(resp: Response) -> hyper::Response<HyperResponseBody> {
let (parts, body) = resp.into_hyper().into_parts();
hyper::Response::from_parts(parts, HyperResponseBody::Full(body))
}
fn strip_body_if_head(is_head: bool, resp: Response) -> Response {
match is_head {
true => resp.strip_body(),
false => resp,
}
}
#[cfg(feature = "grpc")]
enum GrpcDispatch {
Handled(Result<hyper::Response<HyperResponseBody>, std::convert::Infallible>),
NotGrpc(hyper::Request<hyper::body::Incoming>),
}
#[cfg(feature = "grpc")]
async fn try_dispatch_grpc(
hyper_req: hyper::Request<hyper::body::Incoming>,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
start: std::time::Instant,
) -> GrpcDispatch {
let is_grpc = dispatch.grpc_router().is_some() && is_grpc_request(&hyper_req);
match is_grpc {
false => GrpcDispatch::NotGrpc(hyper_req),
true => GrpcDispatch::Handled(
dispatch_grpc_inner(hyper_req, dispatch, ctx, origin, start).await,
),
}
}
#[cfg(feature = "grpc")]
async fn dispatch_grpc_inner(
hyper_req: hyper::Request<hyper::body::Incoming>,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
start: std::time::Instant,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
let method = super::method::Method::from_hyper(hyper_req.method());
let grpc_router = match dispatch.grpc_router() {
Some(r) => r,
None => {
return Ok(refuse_head(
ctx,
method,
hyper_req.uri().path(),
Response::text_raw(500, "grpc router missing"),
start,
));
}
};
match run_head_gate(&hyper_req, dispatch, origin, None).await {
Err(MethodNotAllowed) => Ok(refuse_head(
ctx,
method,
hyper_req.uri().path(),
method_not_allowed_response(),
start,
)),
Ok(Some(refusal)) => Ok(refuse_head(
ctx,
method,
hyper_req.uri().path(),
refusal,
start,
)),
Ok(None) => {
origin.disconnect.complete();
grpc_router.dispatch(hyper_req).await
}
}
}
pub(super) struct MethodNotAllowed;
pub(super) async fn run_head_gate(
hyper_req: &hyper::Request<hyper::body::Incoming>,
dispatch: &ServerDispatch,
origin: RequestOrigin<'_>,
params: Option<super::request::Params>,
) -> Result<Option<Response>, MethodNotAllowed> {
let head = match RequestHead::from_hyper_request(hyper_req, origin) {
Some(head) => head,
None => return Err(MethodNotAllowed),
};
let GateCheck { reached, fut } = match dispatch.middleware_gate_head(&head, params) {
Ok(Some(gate)) => gate,
Ok(None) => return Ok(None),
Err(refusal) => return Ok(Some(refusal)),
};
Ok(gate_result(reached, fut.await))
}
pub(super) fn method_not_allowed_response() -> Response {
Response::text_raw(405, "method not allowed")
}