use super::async_proxy::ProxyFailure;
use super::method::RequestMethod;
use super::request::Request;
use super::response::{
HeaderPair, Response, ResponseProvenance, UnrepresentableResponse, validate_status,
};
use crate::RuntimeError;
use arrayvec::ArrayString;
use bytes::Bytes;
use http_body_util::Full;
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use std::net::IpAddr;
use std::panic::AssertUnwindSafe;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, LazyLock};
const REQUEST_ID_DIGITS: usize = 32;
const HEX_DIGITS: [char; 16] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
static PROCESS_NONCE: LazyLock<u64> = LazyLock::new(crate::prng::next_u64);
static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(0);
const REQUEST_ID_HEADER: &str = "X-Request-Id";
const INTERNAL_ERROR_BODY: &str = "internal server error";
const FALLBACK_STATUS_CODE: hyper::StatusCode = hyper::StatusCode::INTERNAL_SERVER_ERROR;
fn fallback_status() -> u16 {
FALLBACK_STATUS_CODE.as_u16()
}
const SERVICE_UNAVAILABLE_BODY: &str = "service unavailable";
const UNPARSEABLE_BODY: &str = "malformed request body";
const INVALID_MULTIPART_BODY: &str = "invalid multipart body";
const ALLOW_HEADER: &str = "Allow";
#[cfg(feature = "ws")]
const WS_VERSION_HEADER: &str = "Sec-WebSocket-Version";
#[cfg(feature = "ws")]
const WS_VERSION: &str = "13";
const BAD_GATEWAY_BODY: &str = "bad gateway";
const GATEWAY_TIMEOUT_BODY: &str = "gateway timeout";
const CONNECTION_HEADER: &str = "Connection";
const CONNECTION_SPECIFIC_HEADERS: [&str; 5] = [
CONNECTION_HEADER,
"Keep-Alive",
"Proxy-Connection",
"Transfer-Encoding",
"Upgrade",
];
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RequestId {
digits: ArrayString<REQUEST_ID_DIGITS>,
}
impl RequestId {
pub(super) fn generate() -> Self {
let nonce = u128::from(*PROCESS_NONCE) << 64;
let counter = u128::from(REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed));
Self {
digits: render_hex(nonce | counter),
}
}
pub fn as_str(&self) -> &str {
self.digits.as_str()
}
}
impl fmt::Display for RequestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for RequestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("RequestId").field(&self.as_str()).finish()
}
}
const _: () = assert!(REQUEST_ID_DIGITS * 4 == u128::BITS as usize);
fn render_hex(value: u128) -> ArrayString<REQUEST_ID_DIGITS> {
let mut digits = ArrayString::new();
for position in (0..REQUEST_ID_DIGITS).rev() {
let nibble = (value >> (position * 4)) & 0xf;
digits.push(HEX_DIGITS[nibble as usize]);
}
digits
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RejectionKind {
Routing,
MethodSelection,
BodyLimit,
BodyAdmission,
BodyUnreadable,
BodyTimeout,
MalformedBody,
Multipart,
InvalidHeader,
Application,
Middleware,
WebSocketHandshake,
Proxy,
InternalService,
}
impl RejectionKind {
#[doc(hidden)]
pub const ALL: [Self; 14] = [
Self::Routing,
Self::MethodSelection,
Self::BodyLimit,
Self::BodyAdmission,
Self::BodyUnreadable,
Self::BodyTimeout,
Self::MalformedBody,
Self::Multipart,
Self::InvalidHeader,
Self::Application,
Self::Middleware,
Self::WebSocketHandshake,
Self::Proxy,
Self::InternalService,
];
pub(super) fn label(self) -> &'static str {
match self {
Self::Routing => "routing",
Self::MethodSelection => "method_selection",
Self::BodyLimit => "body_limit",
Self::BodyAdmission => "body_admission",
Self::BodyUnreadable => "body_unreadable",
Self::BodyTimeout => "body_timeout",
Self::MalformedBody => "malformed_body",
Self::Multipart => "multipart",
Self::InvalidHeader => "invalid_header",
Self::Application => "application",
Self::Middleware => "middleware",
Self::WebSocketHandshake => "websocket_handshake",
Self::Proxy => "proxy",
Self::InternalService => "internal_service",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Rejection {
kind: RejectionKind,
status: u16,
message: Cow<'static, str>,
headers: Box<[HeaderPair]>,
}
impl Rejection {
pub fn new(
kind: RejectionKind,
status: u16,
message: impl Into<Cow<'static, str>>,
) -> Result<Self, RuntimeError> {
validate_status(status)?;
Ok(Self::raw(kind, status, message))
}
pub(super) fn raw(
kind: RejectionKind,
status: u16,
message: impl Into<Cow<'static, str>>,
) -> Self {
Self {
kind,
status,
message: message.into(),
headers: Box::new([]),
}
}
#[must_use]
pub fn with_header(self, name: &str, value: &str) -> Self {
self.pushing((Cow::Owned(name.to_owned()), Cow::Owned(value.to_owned())))
}
#[must_use]
pub(super) fn with_static_header(
self,
name: &'static str,
value: impl Into<Cow<'static, str>>,
) -> Self {
self.pushing((Cow::Borrowed(name), value.into()))
}
fn pushing(self, header: HeaderPair) -> Self {
let mut headers = self.headers.into_vec();
headers.push(header);
Self {
headers: headers.into_boxed_slice(),
..self
}
}
pub fn kind(&self) -> RejectionKind {
self.kind
}
pub fn status(&self) -> u16 {
self.status
}
pub fn message(&self) -> &str {
&self.message
}
pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
self.headers
.iter()
.map(|(name, value)| (name.as_ref(), value.as_ref()))
}
pub(super) fn header_pairs(&self) -> &[HeaderPair] {
&self.headers
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RejectionProtocol {
OrdinaryHttp,
StreamingHttp,
ServerSentEvents,
WebSocket,
Grpc,
Proxy,
}
impl RejectionProtocol {
fn label(self) -> &'static str {
match self {
Self::OrdinaryHttp => "ordinary_http",
Self::StreamingHttp => "streaming_http",
Self::ServerSentEvents => "server_sent_events",
Self::WebSocket => "websocket",
Self::Grpc => "grpc",
Self::Proxy => "proxy",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NegotiatedResponseMetadata {
protocol: RejectionProtocol,
content_type: Option<Box<str>>,
subprotocol: Option<Box<str>>,
}
impl NegotiatedResponseMetadata {
pub fn new(protocol: RejectionProtocol) -> Self {
Self {
protocol,
content_type: None,
subprotocol: None,
}
}
#[must_use]
pub fn with_content_type(self, content_type: &str) -> Self {
Self {
content_type: Some(content_type.into()),
..self
}
}
#[must_use]
pub fn with_subprotocol(self, subprotocol: &str) -> Self {
Self {
subprotocol: Some(subprotocol.into()),
..self
}
}
pub fn protocol(&self) -> RejectionProtocol {
self.protocol
}
pub fn content_type(&self) -> Option<&str> {
self.content_type.as_deref()
}
pub fn subprotocol(&self) -> Option<&str> {
self.subprotocol.as_deref()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RejectionContext {
request_id: RequestId,
method: Cow<'static, str>,
raw_path: Cow<'static, str>,
remote_addr: Option<IpAddr>,
route: Option<Box<str>>,
negotiated: Option<NegotiatedResponseMetadata>,
}
impl RejectionContext {
pub fn new(
request_id: RequestId,
method: impl Into<Cow<'static, str>>,
raw_path: impl Into<Cow<'static, str>>,
) -> Self {
Self {
request_id,
method: method.into(),
raw_path: raw_path.into(),
remote_addr: None,
route: None,
negotiated: None,
}
}
#[must_use]
pub fn with_remote_addr(self, remote_addr: IpAddr) -> Self {
Self {
remote_addr: Some(remote_addr),
..self
}
}
#[must_use]
pub fn with_route(self, route: &str) -> Self {
Self {
route: Some(route.into()),
..self
}
}
#[must_use]
pub fn with_negotiated(self, negotiated: NegotiatedResponseMetadata) -> Self {
Self {
negotiated: Some(negotiated),
..self
}
}
pub fn request_id(&self) -> &RequestId {
&self.request_id
}
pub fn method(&self) -> &str {
&self.method
}
pub fn raw_path(&self) -> &str {
&self.raw_path
}
pub fn remote_addr(&self) -> Option<IpAddr> {
self.remote_addr
}
pub fn route(&self) -> Option<&str> {
self.route.as_deref()
}
pub fn negotiated(&self) -> Option<&NegotiatedResponseMetadata> {
self.negotiated.as_ref()
}
}
pub(super) type RejectionMapper =
dyn Fn(&Rejection, &RejectionContext) -> Result<Response, RuntimeError> + Send + Sync;
pub(super) fn shared_mapper<F>(mapper: F) -> Arc<RejectionMapper>
where
F: Fn(&Rejection, &RejectionContext) -> Result<Response, RuntimeError> + Send + Sync + 'static,
{
Arc::new(mapper)
}
pub(super) type Diagnostic = Arc<dyn Error + Send + Sync>;
#[derive(Debug)]
struct RefusalDetail(Cow<'static, str>);
impl RefusalDetail {
fn diagnostic(detail: impl Into<Cow<'static, str>>) -> Diagnostic {
Arc::new(Self(detail.into()))
}
}
impl fmt::Display for RefusalDetail {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Error for RefusalDetail {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Disposition {
Reusable,
Close,
}
struct ProtectedHeader {
status: u16,
name: &'static str,
value: Cow<'static, str>,
}
pub(super) struct Rejected {
rejection: Rejection,
diagnostic: Diagnostic,
disposition: Disposition,
protected: Option<ProtectedHeader>,
}
impl Rejected {
fn plain(rejection: Rejection, diagnostic: Diagnostic) -> Self {
Self {
rejection,
diagnostic,
disposition: Disposition::Reusable,
protected: None,
}
}
fn decided(rejection: Rejection, detail: impl Into<Cow<'static, str>>) -> Self {
Self::plain(rejection, RefusalDetail::diagnostic(detail))
}
fn closing(rejection: Rejection, detail: impl Into<Cow<'static, str>>) -> Self {
Self::decided(rejection, detail).forcing_close()
}
fn declared(kind: RejectionKind, message: Box<str>) -> Self {
let detail = format!("the producer declared this message client-safe: {message}");
Self::decided(Rejection::raw(kind, 400, String::from(message)), detail)
}
fn unavailable(error: RuntimeError) -> Self {
Self::plain(
Rejection::raw(
RejectionKind::InternalService,
503,
SERVICE_UNAVAILABLE_BODY,
),
Arc::new(error),
)
}
fn faulted(kind: RejectionKind, diagnostic: Diagnostic) -> Self {
Self::plain(Rejection::raw(kind, 500, INTERNAL_ERROR_BODY), diagnostic)
}
fn unparseable(kind: RejectionKind, safe: &'static str, error: RuntimeError) -> Self {
Self::plain(Rejection::raw(kind, 400, safe), Arc::new(error))
}
pub(super) fn unrepresentable(error: hyper::http::Error) -> Self {
Self::faulted(RejectionKind::InvalidHeader, Arc::new(error))
}
pub(super) fn body_too_large(limit: usize) -> Self {
Self::closing(
Rejection::raw(RejectionKind::BodyLimit, 413, "request body too large"),
format!("body exceeds the {limit}-byte limit"),
)
}
pub(super) fn body_unreadable(error: Box<dyn Error + Send + Sync>) -> Self {
Self::plain(
Rejection::raw(
RejectionKind::BodyUnreadable,
400,
"request body could not be read",
),
Arc::from(error),
)
.forcing_close()
}
pub(super) fn body_timeout(deadline: std::time::Duration) -> Self {
Self::closing(
Rejection::raw(RejectionKind::BodyTimeout, 408, "request body timed out"),
format!("body did not arrive within {deadline:?}"),
)
}
pub(super) fn not_found(detail: &'static str) -> Self {
Self::decided(
Rejection::raw(RejectionKind::Routing, 404, "not found"),
detail,
)
}
pub(super) fn no_route() -> Self {
Self::not_found("no route claims this path")
}
pub(super) fn invalid_host(received: &str) -> Self {
Self::closing(
Rejection::raw(RejectionKind::Routing, 400, "invalid host header"),
format!("unparseable Host value {received:?}"),
)
}
pub(super) fn uri_too_deep(limit: usize) -> Self {
Self::decided(
Rejection::raw(RejectionKind::Routing, 414, "URI path too deep"),
format!("path exceeds the {limit}-segment match limit"),
)
}
pub(super) fn method_not_allowed(received: &str, allow: &str) -> Self {
let detail = format!("received {received}, frozen allowed set is {allow}");
let allowed = allow.to_owned();
Self::decided(
Rejection::raw(RejectionKind::MethodSelection, 405, "method not allowed")
.with_static_header(ALLOW_HEADER, allowed.clone()),
detail,
)
.protecting(ALLOW_HEADER, allowed)
}
#[cfg(feature = "ws")]
pub(super) fn ws_bad_handshake() -> Self {
Self::decided(
Rejection::raw(
RejectionKind::WebSocketHandshake,
400,
"invalid WebSocket upgrade headers",
),
"handshake method, version, key, or subprotocol syntax is not acceptable",
)
}
#[cfg(feature = "ws")]
pub(super) fn ws_unsupported_version() -> Self {
Self::decided(
Rejection::raw(
RejectionKind::WebSocketHandshake,
426,
"unsupported WebSocket version",
)
.with_static_header(WS_VERSION_HEADER, WS_VERSION),
format!("handshake requested a version other than {WS_VERSION}"),
)
.protecting(WS_VERSION_HEADER, WS_VERSION)
}
#[cfg(feature = "ws")]
pub(super) fn ws_origin_rejected(detail: &'static str) -> Self {
Self::decided(
Rejection::raw(
RejectionKind::WebSocketHandshake,
403,
"WebSocket origin rejected",
),
detail,
)
}
#[cfg(feature = "ws")]
pub(super) fn ws_upgrade_unbuildable(error: hyper::http::Error) -> Self {
Self::uncommitted_upgrade(500, INTERNAL_ERROR_BODY, Arc::new(error))
}
#[cfg(feature = "ws")]
pub(super) fn upgrade_registration_refused() -> Self {
Self::uncommitted_upgrade(
503,
SERVICE_UNAVAILABLE_BODY,
RefusalDetail::diagnostic("the supervisor refused to own this upgrade"),
)
}
#[cfg(feature = "ws")]
pub(super) fn upgrade_registration_unavailable() -> Self {
Self::uncommitted_upgrade(
500,
INTERNAL_ERROR_BODY,
RefusalDetail::diagnostic("no supervisor could take ownership of this upgrade"),
)
}
#[cfg(feature = "ws")]
fn uncommitted_upgrade(status: u16, safe: &'static str, diagnostic: Diagnostic) -> Self {
Self::plain(
Rejection::raw(RejectionKind::InternalService, status, safe),
diagnostic,
)
.forcing_close()
}
pub(super) fn from_proxy_failure(failure: ProxyFailure) -> Self {
let (status, safe) = proxy_failure_status(&failure);
let rejection = Rejection::raw(RejectionKind::Proxy, status, safe);
match failure {
ProxyFailure::UnbuildableTarget(detail) => Self::decided(rejection, detail),
ProxyFailure::Unsendable(diagnostic) => Self::plain(rejection, diagnostic),
ProxyFailure::Upstream(error) => Self::plain(rejection, Arc::new(error)),
}
}
pub(super) fn no_admissible_backend() -> Self {
Self::decided(
Rejection::raw(RejectionKind::Proxy, 503, SERVICE_UNAVAILABLE_BODY),
"every backend for this route is failing its health check",
)
}
pub(super) fn gate_unrunnable() -> Self {
Self::decided(
Rejection::raw(RejectionKind::InternalService, 500, INTERNAL_ERROR_BODY),
"a dispatch class owing a middleware gate resolved no router to run it",
)
}
#[must_use]
fn protecting(self, name: &'static str, value: impl Into<Cow<'static, str>>) -> Self {
let status = self.rejection.status();
Self {
protected: Some(ProtectedHeader {
status,
name,
value: value.into(),
}),
..self
}
}
#[must_use]
fn forcing_close(self) -> Self {
Self {
disposition: Disposition::Close,
..self
}
}
}
pub(super) fn proxy_failure_status(failure: &ProxyFailure) -> (u16, &'static str) {
match failure {
ProxyFailure::Upstream(RuntimeError::Timeout) => (504, GATEWAY_TIMEOUT_BODY),
_ => (502, BAD_GATEWAY_BODY),
}
}
#[derive(Clone, Copy)]
pub(super) struct ProducerKinds {
declared: RejectionKind,
faulted: RejectionKind,
}
pub(super) const HANDLER: ProducerKinds = ProducerKinds {
declared: RejectionKind::Application,
faulted: RejectionKind::InternalService,
};
pub(super) const MIDDLEWARE: ProducerKinds = ProducerKinds {
declared: RejectionKind::Middleware,
faulted: RejectionKind::Middleware,
};
fn classify(error: RuntimeError, kinds: ProducerKinds) -> Rejected {
match error {
RuntimeError::BadRequest(message) => Rejected::declared(kinds.declared, message),
RuntimeError::ScopeClosed => Rejected::unavailable(RuntimeError::ScopeClosed),
parsed @ RuntimeError::MalformedBody(_) => {
Rejected::unparseable(RejectionKind::MalformedBody, UNPARSEABLE_BODY, parsed)
}
parsed @ RuntimeError::Multipart(_) => {
Rejected::unparseable(RejectionKind::Multipart, INVALID_MULTIPART_BODY, parsed)
}
other => Rejected::faulted(kinds.faulted, Arc::new(other)),
}
}
#[derive(Clone)]
pub(super) struct RequestIdentity {
request_id: RequestId,
method: RequestMethod,
uri: hyper::Uri,
remote_addr: Option<IpAddr>,
version: hyper::Version,
route: Option<Arc<str>>,
protocol: Option<RejectionProtocol>,
content_type: Option<Box<str>>,
subprotocol: Option<Box<str>>,
}
impl RequestIdentity {
pub(super) fn from_head(
origin: &super::request::RequestOrigin<'_>,
method: &hyper::Method,
uri: &hyper::Uri,
) -> Self {
Self {
request_id: origin.request_id,
method: RequestMethod::from_hyper(method),
uri: uri.clone(),
remote_addr: origin.remote_addr,
version: origin.version,
route: None,
protocol: None,
content_type: None,
subprotocol: None,
}
}
pub(super) fn from_request(req: &Request) -> Self {
Self {
request_id: req.request_id(),
method: req.request_method().clone(),
uri: req.uri_owned(),
remote_addr: req.remote_addr(),
version: req.http_version(),
route: None,
protocol: None,
content_type: None,
subprotocol: None,
}
}
#[must_use]
pub(super) fn with_route(self, route: Arc<str>) -> Self {
Self {
route: Some(route),
..self
}
}
#[must_use]
pub(super) fn with_protocol(self, protocol: RejectionProtocol) -> Self {
Self {
protocol: Some(protocol),
..self
}
}
#[must_use]
fn with_content_type(self, content_type: &str) -> Self {
Self {
content_type: Some(content_type.into()),
..self
}
}
#[must_use]
#[cfg(feature = "ws")]
fn with_subprotocol(self, subprotocol: &str) -> Self {
Self {
subprotocol: Some(subprotocol.into()),
..self
}
}
fn method_label(&self) -> &'static str {
self.method.label()
}
fn is_head(&self) -> bool {
self.method
.routable()
.is_some_and(super::request::method_is_head)
}
fn materialize(&self) -> RejectionContext {
let base = RejectionContext::new(
self.request_id,
self.method.to_cow(),
self.uri.path().to_owned(),
);
let addressed = match self.remote_addr {
Some(remote_addr) => base.with_remote_addr(remote_addr),
None => base,
};
let routed = match &self.route {
Some(route) => addressed.with_route(route),
None => addressed,
};
match self.protocol {
Some(protocol) => routed.with_negotiated(self.negotiated(protocol)),
None => routed,
}
}
fn negotiated(&self, protocol: RejectionProtocol) -> NegotiatedResponseMetadata {
let base = NegotiatedResponseMetadata::new(protocol);
let typed = match &self.content_type {
Some(content_type) => base.with_content_type(content_type),
None => base,
};
match &self.subprotocol {
Some(subprotocol) => typed.with_subprotocol(subprotocol),
None => typed,
}
}
}
pub(super) struct Finalized {
pub(super) response: hyper::Response<Full<Bytes>>,
pub(super) refused: Option<RejectionKind>,
}
#[derive(Clone)]
pub(super) struct RejectionScope {
mapper: Option<Arc<RejectionMapper>>,
identity: Arc<RequestIdentity>,
}
impl RejectionScope {
pub(super) fn new(mapper: Option<Arc<RejectionMapper>>, identity: RequestIdentity) -> Self {
Self {
mapper,
identity: Arc::new(identity),
}
}
fn transitioned(self, established: impl FnOnce(RequestIdentity) -> RequestIdentity) -> Self {
Self {
mapper: self.mapper,
identity: Arc::new(established(own_identity(self.identity))),
}
}
pub(super) fn method_label(&self) -> &'static str {
self.identity.method_label()
}
pub(super) fn path(&self) -> &str {
self.identity.uri.path()
}
pub(super) fn request_id(&self) -> RequestId {
self.identity.request_id
}
pub(super) fn is_head(&self) -> bool {
self.identity.is_head()
}
#[must_use]
pub(super) fn established(self, route: Arc<str>, protocol: RejectionProtocol) -> Self {
self.transitioned(|identity| identity.with_route(route).with_protocol(protocol))
}
#[must_use]
#[cfg(feature = "ws")]
pub(super) fn reclassified(self, protocol: RejectionProtocol) -> Self {
self.transitioned(|identity| identity.with_protocol(protocol))
}
#[must_use]
#[cfg(feature = "ws")]
pub(super) fn negotiated_subprotocol(self, subprotocol: &str) -> Self {
self.transitioned(|identity| identity.with_subprotocol(subprotocol))
}
pub(super) fn resolve(
&self,
outcome: Result<Response, RuntimeError>,
kinds: ProducerKinds,
) -> Response {
match outcome {
Ok(response) => response,
Err(error) => self.map(classify(error, kinds)),
}
}
pub(super) fn finalize(&self, response: Response) -> Finalized {
let (provenance, converted) = self.convert(response);
match converted {
Ok(converted) => sent(converted, provenance),
Err(refused) => self.recover(provenance, refused),
}
}
fn convert(
&self,
response: Response,
) -> (
ResponseProvenance,
Result<hyper::Response<Full<Bytes>>, UnrepresentableResponse>,
) {
match self.identity.is_head() {
true => response.strip_body().into_wire(),
false => response.into_wire(),
}
}
fn recover(
&self,
provenance: ResponseProvenance,
refused: UnrepresentableResponse,
) -> Finalized {
match provenance.into_refusal() {
Some(mut mapped) => self.displace_mapped(&mut mapped),
None => self.map_unrepresentable(refused),
}
}
fn displace_mapped(&self, mapped: &mut MappedRefusal) -> Finalized {
mapped.record(fallback_status());
tracing::error!(
request_id = self.identity.request_id.as_str(),
kind = mapped.kind().label(),
mapped_status = mapped.mapped_status(),
status = fallback_status(),
"mapped rejection response could not be represented"
);
Finalized {
response: fallback_hyper(self.identity.request_id),
refused: Some(mapped.kind()),
}
}
fn map_unrepresentable(&self, refused: UnrepresentableResponse) -> Finalized {
let scope = self.after_head(refused.content_type());
let mapped = scope.map(Rejected::unrepresentable(refused.into_error()));
scope.finalize(mapped)
}
fn after_head(&self, content_type: Option<&str>) -> Self {
match content_type {
Some(content_type) => self
.clone()
.transitioned(|identity| identity.with_content_type(content_type)),
None => self.clone(),
}
}
pub(super) fn map(&self, mut rejected: Rejected) -> Response {
let context = self.identity.materialize();
let protected = rejected.protected.take();
let answered = self
.invoke(&rejected.rejection, &context)
.and_then(|response| {
without_informational_status(response, &rejected.rejection, &context)
})
.unwrap_or_else(|| fallback_response(*context.request_id()));
let final_response = self.enforce_protocol(answered, protected, rejected.disposition);
let mapped_status = final_response.status();
final_response.mark_mapped(MappedRefusal::new(rejected, context, mapped_status))
}
fn invoke(&self, rejection: &Rejection, context: &RejectionContext) -> Option<Response> {
match &self.mapper {
Some(mapper) => catch_mapper(mapper, rejection, context),
None => Some(built_in_response(rejection, context)),
}
}
fn enforce_protocol(
&self,
response: Response,
protected: Option<ProtectedHeader>,
disposition: Disposition,
) -> Response {
let required = match protected {
Some(header) if header.status == response.status() => {
response.with_replaced_header(header.name, header.value)
}
Some(_) | None => response,
};
self.enforce_disposition(required, disposition)
}
fn enforce_disposition(&self, response: Response, disposition: Disposition) -> Response {
match (self.identity.version, disposition) {
(hyper::Version::HTTP_2, _) => response.without_headers(&CONNECTION_SPECIFIC_HEADERS),
(_, Disposition::Close) => {
response.with_replaced_header(CONNECTION_HEADER, Cow::Borrowed("close"))
}
(_, Disposition::Reusable) => response,
}
}
}
fn catch_mapper(
mapper: &Arc<RejectionMapper>,
rejection: &Rejection,
context: &RejectionContext,
) -> Option<Response> {
match std::panic::catch_unwind(AssertUnwindSafe(|| mapper(rejection, context))) {
Ok(Ok(response)) => Some(response),
Ok(Err(error)) => {
tracing::error!(
request_id = context.request_id().as_str(),
kind = rejection.kind().label(),
cause = %SourceChain(&error),
"rejection mapper failed"
);
None
}
Err(payload) => {
tracing::error!(
request_id = context.request_id().as_str(),
kind = rejection.kind().label(),
panic = crate::task::panic_message(payload.as_ref()),
"rejection mapper panicked"
);
None
}
}
}
fn without_informational_status(
response: Response,
rejection: &Rejection,
context: &RejectionContext,
) -> Option<Response> {
match response.status() < 200 {
true => {
tracing::error!(
request_id = context.request_id().as_str(),
kind = rejection.kind().label(),
status = response.status(),
"rejection mapper returned an informational status"
);
None
}
false => Some(response),
}
}
fn built_in_response(rejection: &Rejection, context: &RejectionContext) -> Response {
let base = Response::text_raw(rejection.status(), rejection.message());
let with_defaults = rejection
.header_pairs()
.iter()
.fold(base, |response, (name, value)| {
response.with_pair(name.clone(), value.clone())
});
with_request_id(with_defaults, *context.request_id())
}
fn with_request_id(response: Response, request_id: RequestId) -> Response {
response.with_static_header(
REQUEST_ID_HEADER,
Cow::Owned(request_id.as_str().to_owned()),
)
}
fn fallback_response(request_id: RequestId) -> Response {
with_request_id(
Response::text_raw(fallback_status(), INTERNAL_ERROR_BODY),
request_id,
)
}
fn fallback_hyper(request_id: RequestId) -> hyper::Response<Full<Bytes>> {
let (_, converted) = fallback_response(request_id).into_wire();
match converted {
Ok(response) => response,
Err(refused) => {
tracing::error!(
request_id = request_id.as_str(),
cause = %SourceChain(&refused.into_error()),
"fixed fallback response could not be represented"
);
unnamed_fallback()
}
}
}
fn unnamed_fallback() -> hyper::Response<Full<Bytes>> {
let body = Full::new(Bytes::from_static(INTERNAL_ERROR_BODY.as_bytes()));
let mut response = hyper::Response::new(body);
*response.status_mut() = FALLBACK_STATUS_CODE;
response
}
pub(super) struct MappedRefusal {
rejected: Rejected,
context: RejectionContext,
mapped_status: u16,
recorded: bool,
}
enum Delivery {
Sent(u16),
Discarded,
}
impl Delivery {
fn sent_status(&self) -> Option<u16> {
match self {
Self::Sent(status) => Some(*status),
Self::Discarded => None,
}
}
fn unsent_status(&self, mapped_status: u16) -> Option<u16> {
match self {
Self::Sent(_) => None,
Self::Discarded => Some(mapped_status),
}
}
fn condition(&self) -> &'static str {
match self {
Self::Sent(_) => "request rejected",
Self::Discarded => "rejection response was not sent",
}
}
}
impl MappedRefusal {
fn new(rejected: Rejected, context: RejectionContext, mapped_status: u16) -> Self {
Self {
rejected,
context,
mapped_status,
recorded: false,
}
}
pub(super) fn kind(&self) -> RejectionKind {
self.rejected.rejection.kind()
}
fn mapped_status(&self) -> u16 {
self.mapped_status
}
fn record(&mut self, status: u16) {
self.recorded = true;
self.emit(Delivery::Sent(status));
}
fn emit(&self, delivery: Delivery) {
let context = &self.context;
let negotiated = context.negotiated();
tracing::error!(
request_id = context.request_id().as_str(),
kind = self.kind().label(),
status = delivery.sent_status(),
mapped_status = delivery.unsent_status(self.mapped_status),
default_status = self.rejected.rejection.status(),
method = context.method(),
raw_path = context.raw_path(),
route = context.route(),
protocol = negotiated.map(|negotiated| negotiated.protocol().label()),
content_type = negotiated.and_then(NegotiatedResponseMetadata::content_type),
subprotocol = negotiated.and_then(NegotiatedResponseMetadata::subprotocol),
remote_addr = context.remote_addr().map(tracing::field::display),
cause = %SourceChain(self.rejected.diagnostic.as_ref()),
"{}",
delivery.condition()
);
}
}
impl Drop for MappedRefusal {
fn drop(&mut self) {
match self.recorded {
true => {}
false => self.emit(Delivery::Discarded),
}
}
}
fn sent(response: hyper::Response<Full<Bytes>>, provenance: ResponseProvenance) -> Finalized {
let refused = provenance.into_refusal().map(|mut mapped| {
mapped.record(response.status().as_u16());
mapped.kind()
});
Finalized { response, refused }
}
fn own_identity(identity: Arc<RequestIdentity>) -> RequestIdentity {
Arc::try_unwrap(identity).unwrap_or_else(|shared| (*shared).clone())
}
pub(super) struct SourceChain<'a>(pub(super) &'a (dyn Error + 'static));
impl fmt::Display for SourceChain<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)?;
let mut source = self.0.source();
while let Some(cause) = source {
write!(f, ": {cause}")?;
source = cause.source();
}
Ok(())
}
}