Skip to content

Commit

Permalink
fix serve_dir
Browse files Browse the repository at this point in the history
  • Loading branch information
scottlamb committed Dec 31, 2023
1 parent 40b8a36 commit 4d4bc01
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 25 deletions.
58 changes: 39 additions & 19 deletions examples/serve_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,26 @@
//! Test program which serves the current directory on `http://127.0.0.1:1337/`.
//! Note this requires `--features dir`.

use futures_util::future;
use http::header::{self, HeaderMap, HeaderValue};
use http_body_util::BodyExt;
use http_serve::dir;
use hyper::service::{make_service_fn, service_fn};
use hyper::Body;
use hyper_util::rt::TokioIo;
use std::fmt::Write;
use std::io::Write as RawWrite;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;

type BoxError = Box<dyn std::error::Error + Send + Sync>;
type Body = http_body_util::combinators::BoxBody<bytes::Bytes, BoxError>;

fn is_dir(dir: &nix::dir::Dir, ent: &nix::dir::Entry) -> Result<bool, nix::Error> {
// Many filesystems return file types in the directory entries.
if let Some(t) = ent.file_type() {
return Ok(t == nix::dir::Type::Directory);
}

// ...but some require an fstat call.
use nix::sys::stat::{fstatat, SFlag};
use std::os::unix::io::AsRawFd;
let stat = fstatat(
Expand All @@ -34,7 +41,7 @@ fn is_dir(dir: &nix::dir::Dir, ent: &nix::dir::Entry) -> Result<bool, nix::Error
}

fn reply(
req: http::Request<Body>,
req: http::Request<hyper::body::Incoming>,
node: dir::Node,
) -> Result<http::Response<Body>, ::std::io::Error> {
if node.metadata().is_dir() {
Expand All @@ -45,7 +52,7 @@ fn reply(
return Ok(http::Response::builder()
.status(http::StatusCode::MOVED_PERMANENTLY)
.header(http::header::LOCATION, loc)
.body(Body::empty())
.body(http_serve::Body::empty().boxed())
.unwrap());
}
let (mut resp, stream) = http_serve::streaming_body(&req).build();
Expand Down Expand Up @@ -83,7 +90,7 @@ fn reply(
}
resp.headers_mut()
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/html"));
return Ok(resp);
return Ok(resp.map(BodyExt::boxed));
}
let mut h = HeaderMap::new();
node.add_encoding_headers(&mut h);
Expand All @@ -94,21 +101,24 @@ fn reply(
}
}
let f = node.into_file_entity(h)?;
Ok(http_serve::serve(f, &req))
Ok(http_serve::serve(f, &req).map(BodyExt::boxed))
}

async fn serve(
fs_dir: &Arc<dir::FsDir>,
req: http::Request<Body>,
req: http::Request<hyper::body::Incoming>,
) -> Result<http::Response<Body>, ::std::io::Error> {
let p = if req.uri().path() == "/" {
"."
} else {
&req.uri().path()[1..]
};
// TODO: this should go through the same unwrapping.
let node = Arc::clone(&fs_dir).get(p, req.headers()).await?;
let e = match reply(req, node) {

let e = match Arc::clone(&fs_dir)
.get(p, req.headers())
.await
.and_then(|node| reply(req, node))
{
Ok(res) => return Ok(res),
Err(e) => e,
};
Expand All @@ -118,19 +128,29 @@ async fn serve(
};
Ok(http::Response::builder()
.status(status)
.body(format!("I/O error: {}", e).into())
.body(http_serve::Body::from(format!("I/O error: {}", e)).boxed())
.unwrap())
}

#[tokio::main]
async fn main() {
let dir: &'static Arc<dir::FsDir> =
Box::leak(Box::new(dir::FsDir::builder().for_path(".").unwrap()));
let addr = ([127, 0, 0, 1], 1337).into();
let make_svc = make_service_fn(move |_conn| {
future::ok::<_, std::convert::Infallible>(service_fn(move |req| serve(dir, req)))
});
let server = hyper::Server::bind(&addr).serve(make_svc);
println!("Serving . on http://{} with 1 thread.", server.local_addr());
server.await.unwrap();
let addr = SocketAddr::from(([127, 0, 0, 1], 1337));
let listener = TcpListener::bind(addr).await.unwrap();
println!(
"Serving . on http://{} with 1 thread.",
listener.local_addr().unwrap()
);
loop {
let (tcp, _) = listener.accept().await.unwrap();
tokio::spawn(async move {
tcp.set_nodelay(true).unwrap();
let io = TokioIo::new(tcp);
hyper::server::conn::http1::Builder::new()
.serve_connection(io, hyper::service::service_fn(move |req| serve(dir, req)))
.await
.unwrap();
});
}
}
18 changes: 12 additions & 6 deletions src/serving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,23 @@ fn parse_modified_hdrs(
pub struct Body<D = bytes::Bytes, E = BoxError>(InnerBody<D, E>);

impl<D, E> Body<D, E> {
fn empty() -> Self {
pub fn empty() -> Self {
Self(InnerBody::Once(None))
}
}

impl<D: From<&'static [u8]>, E> Body<D, E> {
fn from_str(value: &'static str) -> Self {
impl<D: From<&'static [u8]>, E> From<&'static str> for Body<D, E> {
fn from(value: &'static str) -> Self {
Self(InnerBody::Once(Some(value.as_bytes().into())))
}
}

impl<D: From<Vec<u8>>, E> From<String> for Body<D, E> {
fn from(value: String) -> Self {
Self(InnerBody::Once(Some(value.into_bytes().into())))
}
}

impl<D, E> Stream for Body<D, E>
where
D: Buf,
Expand Down Expand Up @@ -211,7 +217,7 @@ fn serve_inner<
Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.header(header::ALLOW, HeaderValue::from_static("get, head"))
.body(Body::from_str("This resource only supports GET and HEAD."))
.body(Body::from("This resource only supports GET and HEAD."))
.unwrap(),
);
}
Expand All @@ -225,7 +231,7 @@ fn serve_inner<
return ServeInner::Simple(
Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from_str(s))
.body(Body::from(s))
.unwrap(),
)
}
Expand Down Expand Up @@ -281,7 +287,7 @@ fn serve_inner<

if precondition_failed {
res = res.status(StatusCode::PRECONDITION_FAILED);
return ServeInner::Simple(res.body(Body::from_str("Precondition failed")).unwrap());
return ServeInner::Simple(res.body(Body::from("Precondition failed")).unwrap());
}

if not_modified {
Expand Down

0 comments on commit 4d4bc01

Please sign in to comment.