use crate::app::App;
use crate::body::Body;
#[cfg(feature = "tls")]
use crate::tls::acceptor_from_pem;
use bytes::Bytes;
use futures_util::StreamExt;
use http_body_util::{
combinators::UnsyncBoxBody, BodyDataStream, BodyExt, Full, Limited, StreamBody,
};
use hyper::body::Frame;
use hyper::body::Incoming;
use hyper::service::service_fn;
use hyper::{Request as HyperRequest, Response as HyperResponse, StatusCode};
use hyper_util::rt::TokioIo;
use std::convert::Infallible;
use std::future::Future;
use std::net::SocketAddr;
fn into_boxed_body(body: Body) -> UnsyncBoxBody<Bytes, std::io::Error> {
match body {
Body::Bytes(bytes) => Full::new(bytes)
.map_err(|never| match never {})
.boxed_unsync(),
Body::Stream(stream) => {
let frames = stream.map(|chunk| {
chunk
.map(Frame::data)
.map_err(|e| std::io::Error::other(e.to_string()))
});
StreamBody::new(frames).boxed_unsync()
}
}
}
pub async fn serve<F>(app: App, addr: SocketAddr, shutdown: F) -> std::io::Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
let listener = bind_tcp(addr, app.config().backlog)?;
let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
serve_listener(app, listener, limits, shutdown).await
}
pub async fn serve_on<F>(
app: App,
listener: tokio::net::TcpListener,
shutdown: F,
) -> std::io::Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
serve_listener(app, listener, limits, shutdown).await
}
fn bind_tcp(addr: SocketAddr, backlog: u32) -> std::io::Result<tokio::net::TcpListener> {
let socket = match addr {
std::net::SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?,
std::net::SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?,
};
socket.set_reuseaddr(true)?;
socket.bind(addr)?;
socket.listen(backlog)
}
async fn serve_listener<F>(
app: App,
listener: tokio::net::TcpListener,
limits: std::sync::Arc<AcceptLimits>,
shutdown: F,
) -> std::io::Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
let conn_cfg = ConnSettings::from(app.config());
let shutdown_timeout_ms = app.config().shutdown_timeout_ms;
let drain = Drain::new();
let mut backoff = AcceptBackoff::default();
tokio::pin!(shutdown);
#[cfg(feature = "tls")]
let tls_acceptor = match &app.config().tls {
Some(t) => Some(acceptor_from_pem(&t.cert, &t.key)?),
None => None,
};
loop {
let acquire = async {
let slot = limits.connection_slot().await;
(slot, listener.accept().await)
};
tokio::select! {
biased;
_ = &mut shutdown => break,
(slot, accepted) = acquire => {
let (stream, peer) = match accepted {
Ok(s) => {
backoff.reset();
s
}
Err(e) => {
let pause = backoff.next_pause();
tracing::warn!(error = %e, pause_ms = pause.as_millis() as u64, "accept failed");
tokio::time::sleep(pause).await;
continue;
}
};
#[cfg(feature = "tls")]
{
if let Some(acceptor) = tls_acceptor.clone() {
let app = app.clone();
let handshakes = limits.handshakes.clone();
let handshake_timeout = limits.tls_handshake_timeout;
let token = drain.token();
let conn_builder_fut = async move {
let queue_and_shake = async {
let permit = match &handshakes {
Some(sem) => sem.clone().acquire_owned().await.ok(),
None => None,
};
(acceptor.accept(stream).await, permit)
};
let (accepted, handshake_permit) = match handshake_timeout {
Some(limit) => {
match tokio::time::timeout(limit, queue_and_shake).await {
Ok(pair) => pair,
Err(_) => {
tracing::debug!(%peer, "TLS handshake timed out");
return;
}
}
}
None => queue_and_shake.await,
};
match accepted {
Ok(tls_stream) => {
drop(handshake_permit);
serve_stream(app, tls_stream, conn_cfg, peer, token, slot).await;
}
Err(e) => tracing::debug!(%peer, error = %e, "TLS handshake failed"),
}
};
tokio::spawn(conn_builder_fut);
continue;
}
}
serve_stream(app.clone(), stream, conn_cfg, peer, drain.token(), slot).await;
}
}
}
drain.wait(shutdown_timeout_ms).await;
Ok(())
}
struct Drain {
signal: tokio::sync::watch::Sender<bool>,
guard: Option<tokio::sync::mpsc::Sender<()>>,
finished: tokio::sync::mpsc::Receiver<()>,
}
struct DrainToken {
signal: tokio::sync::watch::Receiver<bool>,
_guard: tokio::sync::mpsc::Sender<()>,
}
impl Drain {
fn new() -> Self {
let (signal, _) = tokio::sync::watch::channel(false);
let (guard, finished) = tokio::sync::mpsc::channel(1);
Self {
signal,
guard: Some(guard),
finished,
}
}
fn token(&self) -> DrainToken {
DrainToken {
signal: self.signal.subscribe(),
_guard: self.guard.clone().expect("token after wait"),
}
}
async fn wait(mut self, timeout_ms: u64) {
let _ = self.signal.send(true);
self.guard.take();
let drained = async move { while self.finished.recv().await.is_some() {} };
if timeout_ms == 0 {
drained.await;
} else {
let grace = std::time::Duration::from_millis(timeout_ms);
if tokio::time::timeout(grace, drained).await.is_err() {
tracing_drain_timeout(timeout_ms);
}
}
}
}
const GOAWAY_LINGER: std::time::Duration = std::time::Duration::from_millis(250);
const TEXT_PLAIN: &str = "text/plain; charset=utf-8";
type ConnSlot = Option<tokio::sync::OwnedSemaphorePermit>;
#[derive(Clone)]
pub(crate) struct ConnGuard(#[allow(dead_code)] std::sync::Arc<ConnGuardInner>);
pub(crate) struct ConnGuardInner {
_slot: ConnSlot,
_token: DrainToken,
}
impl ConnGuard {
fn new(slot: ConnSlot, token: DrainToken) -> Self {
Self(std::sync::Arc::new(ConnGuardInner {
_slot: slot,
_token: token,
}))
}
}
struct AcceptLimits {
connections: Option<std::sync::Arc<tokio::sync::Semaphore>>,
#[cfg(feature = "tls")]
handshakes: Option<std::sync::Arc<tokio::sync::Semaphore>>,
#[cfg(feature = "tls")]
tls_handshake_timeout: Option<std::time::Duration>,
}
impl AcceptLimits {
fn from(cfg: &crate::app::ServerConfig) -> Self {
let sem = |n: usize| (n > 0).then(|| std::sync::Arc::new(tokio::sync::Semaphore::new(n)));
Self {
connections: sem(cfg.max_connections),
#[cfg(feature = "tls")]
handshakes: sem(cfg.max_tls_handshakes),
#[cfg(feature = "tls")]
tls_handshake_timeout: (cfg.tls_handshake_timeout_ms > 0)
.then(|| std::time::Duration::from_millis(cfg.tls_handshake_timeout_ms)),
}
}
async fn connection_slot(&self) -> ConnSlot {
match &self.connections {
Some(sem) => sem.clone().acquire_owned().await.ok(),
None => None,
}
}
}
#[derive(Default)]
struct AcceptBackoff {
current_ms: u64,
}
impl AcceptBackoff {
const FIRST_MS: u64 = 5;
const MAX_MS: u64 = 1_000;
fn reset(&mut self) {
self.current_ms = 0;
}
fn next_pause(&mut self) -> std::time::Duration {
self.current_ms = match self.current_ms {
0 => Self::FIRST_MS,
n => (n * 2).min(Self::MAX_MS),
};
std::time::Duration::from_millis(self.current_ms)
}
}
struct ConnActivity {
in_flight: std::sync::atomic::AtomicUsize,
last_ms: std::sync::atomic::AtomicU64,
origin: tokio::time::Instant,
}
struct InFlight(std::sync::Arc<ConnActivity>);
impl InFlight {
fn new(activity: std::sync::Arc<ConnActivity>) -> Self {
activity
.in_flight
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Self(activity)
}
}
impl Drop for InFlight {
fn drop(&mut self) {
self.0.last_ms.store(
self.0.origin.elapsed().as_millis() as u64,
std::sync::atomic::Ordering::Relaxed,
);
self.0
.in_flight
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
}
struct GuardedBody {
inner: UnsyncBoxBody<Bytes, std::io::Error>,
_guard: InFlight,
}
impl hyper::body::Body for GuardedBody {
type Data = Bytes;
type Error = std::io::Error;
fn poll_frame(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<std::result::Result<Frame<Bytes>, std::io::Error>>> {
std::pin::Pin::new(&mut self.inner).poll_frame(cx)
}
fn size_hint(&self) -> hyper::body::SizeHint {
self.inner.size_hint()
}
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
fn attach_guard(
body: UnsyncBoxBody<Bytes, std::io::Error>,
guard: InFlight,
) -> UnsyncBoxBody<Bytes, std::io::Error> {
GuardedBody {
inner: body,
_guard: guard,
}
.boxed_unsync()
}
impl ConnActivity {
fn new() -> Self {
Self {
in_flight: std::sync::atomic::AtomicUsize::new(0),
last_ms: std::sync::atomic::AtomicU64::new(0),
origin: tokio::time::Instant::now(),
}
}
fn busy(&self) -> bool {
self.in_flight.load(std::sync::atomic::Ordering::Relaxed) > 0
}
fn idle_for(&self, keep_alive_ms: u64) -> Option<std::time::Duration> {
if self.in_flight.load(std::sync::atomic::Ordering::Relaxed) > 0 {
return Some(std::time::Duration::from_millis(keep_alive_ms));
}
let last = self.last_ms.load(std::sync::atomic::Ordering::Relaxed);
let idle_ms = (self.origin.elapsed().as_millis() as u64).saturating_sub(last);
match keep_alive_ms.checked_sub(idle_ms) {
Some(0) | None => None,
Some(remaining) => Some(std::time::Duration::from_millis(remaining)),
}
}
}
const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
#[derive(Default)]
struct Negotiated(std::sync::atomic::AtomicBool);
impl Negotiated {
fn get(&self) -> bool {
self.0.load(std::sync::atomic::Ordering::Relaxed)
}
fn set(&self) {
self.0.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
struct Sniffing<S> {
inner: S,
flag: std::sync::Arc<Negotiated>,
matched: usize,
}
impl<S> Sniffing<S> {
fn new(inner: S, flag: std::sync::Arc<Negotiated>) -> Self {
Self {
inner,
flag,
matched: 0,
}
}
fn observe(&mut self, fresh: &[u8]) {
if fresh.is_empty() {
self.flag.set();
return;
}
for &byte in fresh {
if byte != H2_PREFACE[self.matched] {
self.flag.set();
return;
}
self.matched += 1;
if self.matched == H2_PREFACE.len() {
self.flag.set();
return;
}
}
}
}
impl<S: tokio::io::AsyncRead + Unpin> tokio::io::AsyncRead for Sniffing<S> {
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
let this = self.get_mut();
let before = buf.filled().len();
let polled = std::pin::Pin::new(&mut this.inner).poll_read(cx, buf);
if matches!(polled, std::task::Poll::Ready(Ok(()))) && !this.flag.get() {
let fresh: Vec<u8> = buf.filled()[before..].to_vec();
this.observe(&fresh);
}
polled
}
}
impl<S: tokio::io::AsyncWrite + Unpin> tokio::io::AsyncWrite for Sniffing<S> {
fn poll_write(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
std::pin::Pin::new(&mut self.inner).poll_write(cx, buf)
}
fn poll_flush(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::pin::Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_shutdown(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::pin::Pin::new(&mut self.inner).poll_shutdown(cx)
}
fn poll_write_vectored(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> std::task::Poll<std::io::Result<usize>> {
std::pin::Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
}
#[derive(Clone, Copy)]
struct ConnSettings {
max_body: usize,
timeout_ms: u64,
max_headers: usize,
header_read_timeout_ms: u64,
keep_alive_ms: u64,
h2_max_header_list_size: u32,
h2_max_concurrent_streams: u32,
}
impl ConnSettings {
fn from(cfg: &crate::app::ServerConfig) -> Self {
Self {
max_body: cfg.max_body_bytes,
timeout_ms: cfg.request_timeout_ms,
max_headers: cfg.max_headers,
header_read_timeout_ms: cfg.header_read_timeout_ms,
keep_alive_ms: cfg.keep_alive_ms,
h2_max_header_list_size: cfg.h2_max_header_list_size,
h2_max_concurrent_streams: cfg.h2_max_concurrent_streams,
}
}
}
pub async fn serve_many<F>(app: App, addrs: Vec<SocketAddr>, shutdown: F) -> std::io::Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
if addrs.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"no addresses to bind",
));
}
let mut listeners = Vec::with_capacity(addrs.len());
for addr in addrs {
match bind_tcp(addr, app.config().backlog) {
Ok(l) => listeners.push(l),
Err(e) => {
tracing::error!(%addr, error = %e, "bind failed; not starting");
return Err(e);
}
}
}
let (tx, _) = tokio::sync::broadcast::channel::<()>(1);
let mut tasks = Vec::with_capacity(listeners.len());
let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
for listener in listeners {
let app = app.clone();
let limits = limits.clone();
let mut rx = tx.subscribe();
tasks.push(tokio::spawn(async move {
serve_listener(app, listener, limits, async move {
let _ = rx.recv().await;
})
.await
}));
}
shutdown.await;
let _ = tx.send(());
let mut first_err: Option<std::io::Error> = None;
for t in tasks {
let outcome = match t.await {
Ok(r) => r,
Err(e) => Err(std::io::Error::other(format!("listener task failed: {e}"))),
};
if let Err(e) = outcome {
first_err.get_or_insert(e);
}
}
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
#[cfg(unix)]
pub async fn serve_unix<F>(
app: App,
path: impl AsRef<std::path::Path>,
shutdown: F,
) -> std::io::Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
let path = path.as_ref();
unlink_if_stale(path).await?;
let listener = tokio::net::UnixListener::bind(path)?;
let bound = socket_identity(path);
let conn_cfg = ConnSettings::from(app.config());
let shutdown_timeout_ms = app.config().shutdown_timeout_ms;
let drain = Drain::new();
let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
let mut backoff = AcceptBackoff::default();
let mut shutdown = std::pin::pin!(shutdown);
let peer = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
loop {
let acquire = async {
let slot = limits.connection_slot().await;
(slot, listener.accept().await)
};
tokio::select! {
biased;
_ = &mut shutdown => break,
(slot, accepted) = acquire => {
let (stream, _) = match accepted {
Ok(s) => {
backoff.reset();
s
}
Err(e) => {
let pause = backoff.next_pause();
tracing::warn!(error = %e, pause_ms = pause.as_millis() as u64, "accept failed");
tokio::time::sleep(pause).await;
continue;
}
};
serve_stream(app.clone(), stream, conn_cfg, peer, drain.token(), slot).await;
}
}
}
drain.wait(shutdown_timeout_ms).await;
if bound.is_some() && bound == socket_identity(path) {
let _ = std::fs::remove_file(path);
}
Ok(())
}
#[cfg(unix)]
fn socket_identity(path: &std::path::Path) -> Option<(u64, u64)> {
use std::os::unix::fs::MetadataExt;
let md = std::fs::symlink_metadata(path).ok()?;
Some((md.dev(), md.ino()))
}
#[cfg(unix)]
async fn unlink_if_stale(path: &std::path::Path) -> std::io::Result<()> {
use std::os::unix::fs::FileTypeExt;
let Ok(md) = std::fs::symlink_metadata(path) else {
return Ok(()); };
if md.file_type().is_socket() && tokio::net::UnixStream::connect(path).await.is_ok() {
return Err(std::io::Error::new(
std::io::ErrorKind::AddrInUse,
format!(
"{} is already being served by another listener",
path.display()
),
));
}
match std::fs::remove_file(path) {
Err(e) if e.kind() != std::io::ErrorKind::NotFound => Err(e),
_ => Ok(()),
}
}
async fn serve_stream<S>(
app: App,
stream: S,
cfg: ConnSettings,
peer: std::net::SocketAddr,
token: DrainToken,
slot: ConnSlot,
) where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
{
let negotiated = std::sync::Arc::new(Negotiated::default());
let io = TokioIo::new(Sniffing::new(stream, negotiated.clone()));
let activity = std::sync::Arc::new(ConnActivity::new());
let mut signal = token.signal.clone();
let guard = ConnGuard::new(slot, token);
let svc = {
let activity = activity.clone();
let guard = guard.clone();
service_fn(move |req: HyperRequest<Incoming>| {
let app = app.clone();
let activity = activity.clone();
let conn_guard = guard.clone();
async move {
let in_flight = InFlight::new(activity);
let res = handle(app, req, cfg.max_body, cfg.timeout_ms, peer, conn_guard).await;
res.map(|r| r.map(|body| attach_guard(body, in_flight)))
}
})
};
let mut builder =
hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
builder.http1().max_headers(cfg.max_headers);
builder
.http2()
.max_header_list_size(cfg.h2_max_header_list_size);
builder.http2().max_concurrent_streams(
(cfg.h2_max_concurrent_streams > 0).then_some(cfg.h2_max_concurrent_streams),
);
builder.http1().keep_alive(cfg.keep_alive_ms > 0);
if cfg.header_read_timeout_ms > 0 {
let deadline = std::time::Duration::from_millis(cfg.header_read_timeout_ms);
builder.http1().timer(hyper_util::rt::TokioTimer::new());
builder.http1().header_read_timeout(deadline);
builder.http2().timer(hyper_util::rt::TokioTimer::new());
builder.http2().keep_alive_interval(deadline);
builder.http2().keep_alive_timeout(deadline);
}
tokio::spawn(async move {
let conn = builder.serve_connection_with_upgrades(io, svc);
let mut conn = std::pin::pin!(conn);
let mut winding_down = false;
let idle_enabled = cfg.keep_alive_ms > 0;
let idle = tokio::time::sleep(std::time::Duration::from_millis(if idle_enabled {
cfg.keep_alive_ms
} else {
u64::MAX / 2
}));
let mut idle = std::pin::pin!(idle);
let linger = tokio::time::sleep(std::time::Duration::from_secs(3_600));
let mut linger = std::pin::pin!(linger);
let mut lingering = false;
let mut negotiation_armed = cfg.header_read_timeout_ms > 0;
let negotiation =
tokio::time::sleep(std::time::Duration::from_millis(if negotiation_armed {
cfg.header_read_timeout_ms
} else {
u64::MAX / 2
}));
let mut negotiation = std::pin::pin!(negotiation);
loop {
tokio::select! {
biased;
_ = conn.as_mut() => break,
_ = signal.wait_for(|fired| *fired), if !winding_down => {
winding_down = true;
conn.as_mut().graceful_shutdown();
if !activity.busy() {
linger.as_mut().reset(
tokio::time::Instant::now() + GOAWAY_LINGER,
);
lingering = true;
}
}
_ = linger.as_mut(), if lingering => {
if !activity.busy() {
break;
}
linger
.as_mut()
.reset(tokio::time::Instant::now() + GOAWAY_LINGER);
}
_ = negotiation.as_mut(), if negotiation_armed && !winding_down => {
if negotiated.get() {
negotiation_armed = false;
} else {
tracing::debug!(
%peer,
deadline_ms = cfg.header_read_timeout_ms,
"no protocol chosen within the header deadline; closing"
);
winding_down = true;
conn.as_mut().graceful_shutdown();
linger
.as_mut()
.reset(tokio::time::Instant::now() + GOAWAY_LINGER);
lingering = true;
}
}
_ = idle.as_mut(), if idle_enabled && !winding_down => {
match activity.idle_for(cfg.keep_alive_ms) {
None => {
winding_down = true;
conn.as_mut().graceful_shutdown();
linger
.as_mut()
.reset(tokio::time::Instant::now() + GOAWAY_LINGER);
lingering = true;
}
Some(remaining) => idle
.as_mut()
.reset(tokio::time::Instant::now() + remaining),
}
}
}
}
drop(guard);
});
}
fn tracing_drain_timeout(ms: u64) {
tracing::warn!(
grace_ms = ms,
"graceful shutdown timed out; exiting with requests in flight"
);
}
async fn handle(
app: App,
req: HyperRequest<Incoming>,
max_body: usize,
timeout_ms: u64,
peer: std::net::SocketAddr,
conn_guard: ConnGuard,
) -> Result<HyperResponse<UnsyncBoxBody<Bytes, std::io::Error>>, Infallible> {
let mut res = respond(app.clone(), req, max_body, timeout_ms, peer, conn_guard).await?;
app.apply_security_headers(res.headers_mut(), false);
Ok(res)
}
async fn respond(
app: App,
req: HyperRequest<Incoming>,
max_body: usize,
timeout_ms: u64,
peer: std::net::SocketAddr,
conn_guard: ConnGuard,
) -> Result<HyperResponse<UnsyncBoxBody<Bytes, std::io::Error>>, Infallible> {
if let Some(declared) = req
.headers()
.get(http::header::CONTENT_LENGTH)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<u64>().ok())
{
if declared > max_body as u64 {
let res = HyperResponse::builder()
.status(StatusCode::PAYLOAD_TOO_LARGE)
.header(http::header::CONNECTION, "close")
.header(http::header::CONTENT_TYPE, TEXT_PLAIN)
.body(into_boxed_body(Body::Bytes(bytes::Bytes::from_static(
b"Payload Too Large",
))))
.expect("response build is infallible");
return Ok(res);
}
}
#[cfg(not(feature = "ws"))]
let _ = &conn_guard;
if req.headers().contains_key(http::header::TRANSFER_ENCODING)
&& req.headers().contains_key(http::header::CONTENT_LENGTH)
{
tracing::warn!(
path = %req.uri().path(),
"rejected a request with both Transfer-Encoding and Content-Length"
);
let res = HyperResponse::builder()
.status(StatusCode::BAD_REQUEST)
.header(http::header::CONNECTION, "close")
.header(http::header::CONTENT_TYPE, TEXT_PLAIN)
.body(into_boxed_body(Body::Bytes(bytes::Bytes::from_static(
b"Bad Request",
))))
.expect("response build is infallible");
return Ok(res);
}
#[cfg(feature = "ws")]
let ws_max_frame_bytes = app.config().ws_max_frame_bytes;
#[cfg(feature = "ws")]
let ws_max_message_bytes = app.config().ws_max_message_bytes;
#[cfg(feature = "ws")]
let ws_idle_timeout_ms = app.config().ws_idle_timeout_ms;
#[cfg(feature = "ws")]
let mut req = req;
#[cfg(feature = "ws")]
let on_upgrade = if crate::ws::is_upgrade_request(req.headers()) {
Some(hyper::upgrade::on(&mut req))
} else {
None
};
let (parts, body) = req.into_parts();
let limited = Limited::new(body, max_body);
let body_stream: crate::call::BodyStream = Box::pin(BodyDataStream::new(limited).map(|r| {
r.map_err(|e| {
if e.downcast_ref::<http_body_util::LengthLimitError>()
.is_some()
{
crate::Error::new(StatusCode::PAYLOAD_TOO_LARGE, "request body too large")
} else {
crate::Error::bad_request(format!("error reading request body: {e}"))
}
})
}));
#[cfg(feature = "ws")]
let process = {
let mut extensions = http::Extensions::new();
extensions.insert(crate::call::PeerAddr(peer));
if let Some(on_upgrade) = on_upgrade {
extensions.insert(crate::ws::OnUpgradeHandle::new(on_upgrade));
extensions.insert(conn_guard.clone());
extensions.insert(crate::ws::WsIdleTimeout(ws_idle_timeout_ms));
extensions.insert(crate::ws::WsLimits {
max_frame_bytes: ws_max_frame_bytes,
max_message_bytes: ws_max_message_bytes,
});
}
app.process_call(
crate::call::Call::new(parts.method, parts.uri, parts.headers, bytes::Bytes::new())
.with_body_stream(body_stream),
extensions,
)
};
#[cfg(not(feature = "ws"))]
let process = {
let mut extensions = http::Extensions::new();
extensions.insert(crate::call::PeerAddr(peer));
app.process_call(
crate::call::Call::new(parts.method, parts.uri, parts.headers, bytes::Bytes::new())
.with_body_stream(body_stream),
extensions,
)
};
let res = if timeout_ms == 0 {
process.await
} else {
match tokio::time::timeout(std::time::Duration::from_millis(timeout_ms), process).await {
Ok(res) => res,
Err(_) => crate::response::Response::text("Request Timeout")
.with_status(StatusCode::REQUEST_TIMEOUT),
}
};
let mut builder = HyperResponse::builder().status(res.status);
if let Some(headers) = builder.headers_mut() {
*headers = res.headers;
}
Ok(builder
.body(into_boxed_body(res.body))
.expect("response build is infallible"))
}