use super::body::HyperResponseBody;
use super::disconnect::DisconnectSignal;
use super::dispatch::{
AsyncDispatch, Classified, FrozenRouter, HeadUpgrade, PreBodyScope, 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::{count_rejection, record_scoped};
use super::rejection::{
HANDLER, Rejected, RejectionProtocol, RejectionScope, RequestId, RequestIdentity,
};
use super::request::{RequestHead, RequestOrigin};
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 {
rejected: Rejected,
method: hyper::Method,
uri: hyper::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(rejected) => {
return Err(Box::new(Refused {
rejected,
method: parts.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, Rejected> {
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(error)) => Err(exceeded_or_unreadable(error, max_body)),
Err(_) => Err(Rejected::body_timeout(REQUEST_BODY_TIMEOUT)),
}
}
fn exceeded_or_unreadable(
error: Box<dyn std::error::Error + Send + Sync>,
max_body: usize,
) -> Rejected {
match error.downcast_ref::<http_body_util::LengthLimitError>() {
Some(_) => Rejected::body_too_large(max_body),
None => Rejected::body_unreadable(error),
}
}
#[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<'_>,
) -> DispatchInput {
let ws = ws_proxy::extract_ws_upgrade(&mut hyper_req);
let head = RequestHead::from_hyper_request(&hyper_req, origin);
(head.to_request(None), ws)
}
#[cfg(not(feature = "ws"))]
fn build_head_only_request(
hyper_req: hyper::Request<hyper::body::Incoming>,
origin: RequestOrigin<'_>,
) -> DispatchInput {
RequestHead::from_hyper_request(&hyper_req, origin).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
}
enum WireRead {
HeadOnly,
Body(super::method::Method),
}
async fn build_dispatch_input(
hyper_req: hyper::Request<hyper::body::Incoming>,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
lifecycle: &ConnectionLifecycle,
read: WireRead,
) -> Building {
match read {
WireRead::HeadOnly => Ok(build_head_only_request(hyper_req, origin)),
WireRead::Body(method) => {
let lifecycle_script = lifecycle.script();
collect_request(
hyper_req,
ctx.max_request_body,
origin,
lifecycle_script.as_deref(),
method,
)
.await
}
}
}
enum PreBodyRoute<'a> {
Internal(super::internal_routes::InternalRoute),
Class(Classified<'a>),
}
fn classify_pre_body<'a>(
hyper_req: &hyper::Request<hyper::body::Incoming>,
dispatch: &'a ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
) -> PreBodyRoute<'a> {
let head = RequestHead::from_hyper_request(hyper_req, origin);
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));
#[cfg(feature = "ws")]
let asks_websocket = ws_proxy::is_ws_upgrade_head(hyper_req.headers());
#[cfg(not(feature = "ws"))]
let asks_websocket = false;
match internal {
Some(route) => PreBodyRoute::Internal(route),
None => {
PreBodyRoute::Class(dispatch.classify_route(&head, HeadUpgrade::of(asks_websocket)))
}
}
}
fn pending_middleware_gate(
result: &DispatchResult,
router: Option<&FrozenRouter>,
scope: &RejectionScope,
) -> Option<GateCheck> {
match (result.needs_middleware_gate(), router) {
(false, _) => None,
(true, Some(router)) => router.middleware_gate(result.request_ref(), scope),
(true, None) => Some(unrunnable_gate(scope)),
}
}
fn unrunnable_gate(scope: &RejectionScope) -> GateCheck {
let scope = scope.clone();
Box::pin(async move { scope.map(Rejected::gate_unrunnable()) })
}
pub(super) fn answer(
ctx: &ConnCtx,
resp: Response,
start: std::time::Instant,
scope: &RejectionScope,
) -> hyper::Response<HyperResponseBody> {
let finalized = scope.finalize(resp);
let status = finalized.response.status().as_u16();
if let Some(kind) = finalized.refused {
count_rejection(ctx, kind, status);
}
record_scoped(ctx, scope, status, start);
let (parts, body) = finalized.response.into_parts();
hyper::Response::from_parts(parts, HyperResponseBody::Full(body))
}
pub(super) fn answer_rejected(
ctx: &ConnCtx,
scope: &RejectionScope,
rejected: Rejected,
start: std::time::Instant,
) -> hyper::Response<HyperResponseBody> {
let response = scope.map(rejected);
answer(ctx, response, start, scope)
}
struct RequestDispatch<'a> {
dispatch: &'a ServerDispatch,
ctx: &'a ConnCtx,
origin: RequestOrigin<'a>,
lifecycle: &'a ConnectionLifecycle,
start: std::time::Instant,
}
fn wire_read(route_class: &RouteClass) -> WireRead {
match route_class {
RouteClass::Buffered(method) => WireRead::Body(*method),
RouteClass::HeadOnly
| RouteClass::Terminal
| RouteClass::Refused(_)
| RouteClass::StreamingProxy(_) => WireRead::HeadOnly,
}
}
async fn dispatch_classified_route<'a>(
hyper_req: hyper::Request<hyper::body::Incoming>,
classified: Classified<'a>,
request_dispatch: &RequestDispatch<'a>,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
let &RequestDispatch {
ctx,
origin,
lifecycle,
start,
..
} = request_dispatch;
let Classified {
class,
scope,
router,
} = classified;
let class = match class {
RouteClass::StreamingProxy(target) => {
return dispatch_streaming_proxy(hyper_req, ctx, target, origin, router, &scope, start)
.await;
}
RouteClass::Refused(rejected) => {
let scope = scope.scope(RequestIdentity::from_head(
&origin,
hyper_req.method(),
hyper_req.uri(),
));
return Ok(answer_rejected(ctx, &scope, rejected, start));
}
read_from_wire
@ (RouteClass::HeadOnly | RouteClass::Terminal | RouteClass::Buffered(_)) => read_from_wire,
};
let read = wire_read(&class);
let input = match build_dispatch_input(hyper_req, ctx, origin, lifecycle, read).await {
Ok(input) => input,
Err(refused) => {
return Ok(refuse_body(ctx, origin, &scope, *refused, start));
}
};
dispatch_built_request(input, router, request_dispatch).await
}
fn refuse_body(
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
pre_body: &PreBodyScope,
refused: Refused,
start: std::time::Instant,
) -> hyper::Response<HyperResponseBody> {
let Refused {
rejected,
method,
uri,
} = refused;
let scope = pre_body.scope(RequestIdentity::from_head(&origin, &method, &uri));
answer_rejected(ctx, &scope, rejected, start)
}
async fn dispatch_built_request<'a>(
input: DispatchInput,
resolved: Option<&'a FrozenRouter>,
request_dispatch: &RequestDispatch<'a>,
) -> 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,
scope,
} = dispatch.dispatch_resolved(req, resolved);
#[cfg(feature = "ws")]
let scope = match result.is_websocket() {
true => scope.reclassified(RejectionProtocol::WebSocket),
false => scope,
};
let gate_blocked = match pending_middleware_gate(&result, router, &scope) {
None => None,
Some(gate) => gate_result(gate.await),
};
if let Some(blocked) = gate_blocked {
return Ok(answer(ctx, blocked, start, &scope));
}
#[cfg(feature = "ws")]
if let Some(rejected) = result
.is_websocket()
.then(|| ws_proxy::check_ws_origin(result.request_ref()))
.flatten()
{
return Ok(answer_rejected(ctx, &scope, rejected, start));
}
match result {
DispatchResult::Async(fut, held_request) => {
let answered = finish_async(ctx, fut.await, start, &scope);
drop(held_request);
answered
}
DispatchResult::Stream(fut, req) => {
handle_stream_response(fut.await, req, ctx, &scope, start)
}
DispatchResult::Sse(handler, req) => {
record_scoped(ctx, &scope, 200, start);
handle_sse(handler, req, ctx.sse_buffer_size, lifecycle).await
}
#[cfg(feature = "ws")]
DispatchResult::WebSocket(handler, req) => {
record_upgrade(ctx, req, start, &scope, |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, &scope, |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, &scope, 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,
request_id: RequestId::generate(),
version: hyper_req.version(),
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 classified = match classify_pre_body(&hyper_req, dispatch, ctx, origin) {
PreBodyRoute::Internal(route) => {
return dispatch_internal_head_only(&hyper_req, route, dispatch, ctx, origin, start)
.await;
}
PreBodyRoute::Class(classified) => classified,
};
let request_dispatch = RequestDispatch {
dispatch,
ctx,
origin,
lifecycle,
start,
};
dispatch_classified_route(hyper_req, classified, &request_dispatch).await
}
fn finish_async(
ctx: &ConnCtx,
resp: Response,
start: std::time::Instant,
scope: &RejectionScope,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
Ok(answer(ctx, resp, start, scope))
}
#[cfg(feature = "ws")]
async fn record_upgrade<F, Fut>(
ctx: &ConnCtx,
req: Request,
start: std::time::Instant,
scope: &RejectionScope,
upgrade: F,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible>
where
F: FnOnce(Request) -> Fut,
Fut: std::future::Future<
Output = Result<hyper::Response<HyperResponseBody>, ws_proxy::WsRefusal>,
>,
{
match upgrade(req).await {
Ok(resp) => {
record_scoped(ctx, scope, resp.status().as_u16(), start);
Ok(resp)
}
Err(refusal) => Ok(answer_rejected(
ctx,
&refused_upgrade_scope(scope, refusal.subprotocol.as_deref()),
refusal.rejected,
start,
)),
}
}
#[cfg(feature = "ws")]
fn refused_upgrade_scope(scope: &RejectionScope, subprotocol: Option<&str>) -> RejectionScope {
match subprotocol {
Some(subprotocol) => scope.clone().negotiated_subprotocol(subprotocol),
None => scope.clone(),
}
}
async fn dispatch_internal_head_only(
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> {
match dispatch.skip_middleware_for_internal() {
true => Ok(answer_internal_directly(hyper_req, &route, dispatch, ctx, origin, start).await),
false => {
dispatch_internal_through_middleware(hyper_req, route, dispatch, ctx, origin, start)
.await
}
}
}
async fn answer_internal_directly(
hyper_req: &hyper::Request<hyper::body::Incoming>,
route: &super::internal_routes::InternalRoute,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
start: std::time::Instant,
) -> hyper::Response<HyperResponseBody> {
let head = RequestHead::from_hyper_request(hyper_req, origin);
let identity = RequestIdentity::from_head(&origin, hyper_req.method(), hyper_req.uri());
let scope = internal_scope(dispatch.head_scope(&head, identity), route);
let response = scope.resolve(invoke_internal_route(route).await, HANDLER);
answer(ctx, response, start, &scope)
}
fn internal_scope(
scope: RejectionScope,
route: &super::internal_routes::InternalRoute,
) -> RejectionScope {
scope.established(route.route(), RejectionProtocol::OrdinaryHttp)
}
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 req = RequestHead::from_hyper_request(hyper_req, origin).to_request(None);
let resolved = dispatch.resolve(&req);
let scope = internal_scope(dispatch.resolved_scope(&resolved, &req), &route);
let handler = build_internal_handler(route);
let AsyncDispatch {
fut,
req: held_request,
} = ServerDispatch::dispatch_with_handler(resolved, &handler, req, scope.clone());
let answered = finish_async(ctx, fut.await, start, &scope);
drop(held_request);
answered
}
#[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 {
match dispatch.grpc_router() {
Some(grpc_router) if is_grpc_request(&hyper_req) => GrpcDispatch::Handled(
dispatch_grpc_inner(hyper_req, grpc_router, dispatch, ctx, origin, start).await,
),
_ => GrpcDispatch::NotGrpc(hyper_req),
}
}
#[cfg(feature = "grpc")]
async fn dispatch_grpc_inner(
hyper_req: hyper::Request<hyper::body::Incoming>,
grpc_router: &super::grpc_support::GrpcRouter,
dispatch: &ServerDispatch,
ctx: &ConnCtx,
origin: RequestOrigin<'_>,
start: std::time::Instant,
) -> Result<hyper::Response<HyperResponseBody>, std::convert::Infallible> {
let head = RequestHead::from_hyper_request(&hyper_req, origin);
let resolved = dispatch.resolve_from_head(&head);
let scope = dispatch
.resolved_head_scope(
&resolved,
RequestIdentity::from_head(&origin, hyper_req.method(), hyper_req.uri()),
)
.established(super::grpc_support::grpc_route(), RejectionProtocol::Grpc);
let router = match resolved {
Ok(router) => router,
Err(rejected) => return Ok(answer_rejected(ctx, &scope, rejected, start)),
};
match run_head_gate(&head, router, None, &scope).await {
Some(refusal) => Ok(answer(ctx, refusal, start, &scope)),
None => {
origin.disconnect.complete();
let response = grpc_router.dispatch(hyper_req).await?;
record_scoped(ctx, &scope, response.status().as_u16(), start);
Ok(response)
}
}
}
pub(super) async fn run_head_gate(
head: &RequestHead<'_>,
router: Option<&FrozenRouter>,
params: Option<super::request::Params>,
scope: &RejectionScope,
) -> Option<Response> {
let gate = match router.and_then(|router| router.middleware_gate_head(head, params, scope)) {
Some(gate) => gate,
None => return None,
};
gate_result(gate.await)
}