#![deny(rust_2018_idioms, missing_docs)]
#[macro_use]
mod macros;
mod serve_dir;
mod serve_file;
#[doc(hidden)]
pub mod private {
pub use {http, mime, mime_guess};
}
use bytes::Bytes;
use http_body::{Body, Frame};
use pin_project::pin_project;
use std::{
io,
pin::Pin,
task::{Context, Poll},
};
use tokio::io::AsyncRead;
use futures_util::Stream;
use tokio_util::io::ReaderStream;
const DEFAULT_CAPACITY: usize = 65536;
pub use self::{
serve_dir::{
ResponseBody as ServeDirResponseBody, ResponseFuture as ServeDirResponseFuture, ServeDir,
},
serve_file::{
File, ResponseBody as ServeFileResponseBody, ResponseFuture as ServeFileResponseFuture,
ServeFile,
},
};
#[pin_project]
#[derive(Debug)]
pub struct AsyncReadBody<T> {
#[pin]
reader: ReaderStream<T>,
}
impl<T> AsyncReadBody<T>
where
T: AsyncRead,
{
fn with_capacity(read: T, capacity: usize) -> Self {
Self {
reader: ReaderStream::with_capacity(read, capacity),
}
}
}
impl<T> Body for AsyncReadBody<T>
where
T: AsyncRead,
{
type Data = Bytes;
type Error = io::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
self.project().reader.poll_next(cx).map(|res| match res {
Some(Ok(buf)) => Some(Ok(Frame::data(buf))),
Some(Err(err)) => Some(Err(err)),
None => None,
})
}
}
#[cfg(feature = "metadata")]
fn unmodified_since_request_condition<T>(
file: &include_dir::File<'_>,
req: &http::Request<T>,
) -> bool {
use http::{header, Method};
use httpdate::HttpDate;
let Some(metadata) = file.metadata() else {
return false;
};
match req.method() {
&Method::GET | &Method::HEAD => (),
_ => return false,
}
let Some(since) = req
.headers()
.get(header::IF_MODIFIED_SINCE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<HttpDate>().ok())
else {
return false;
};
metadata.modified() <= since.into()
}