Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ohkami/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ hmac = { version = "0.12", default-features = false }
sha2 = { version = "0.10", default-features = false }

# optional
mime_guess = { version = "2.0", optional = true }
ctrlc = { version = "3.4", optional = true }
num_cpus = { version = "1.16", optional = true }
futures-util = { version = "0.3", optional = true, default-features = false }
Expand Down Expand Up @@ -91,7 +92,7 @@ ws = ["ohkami_lib/stream", "dep:mews"]

##### internal #####
__rt__ = []
__rt_native__ = ["__rt__", "dep:ctrlc"]
__rt_native__ = ["__rt__", "dep:mime_guess", "dep:ctrlc"]

##### DEBUG #####
DEBUG = ["tokio?/rt-multi-thread", "tokio?/macros"]
Expand Down
129 changes: 129 additions & 0 deletions ohkami/src/ohkami/dir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#![cfg(feature="__rt_native__")]

use crate::handler::{Handler, IntoHandler};
use crate::response::Content;
use std::fs::File;
use std::path::{PathBuf, Path};

pub struct Dir {
pub(crate) route: &'static str,
pub(crate) files: Vec<(PathBuf, File)>,

/*=== config ===*/
pub(crate) serve_dotfiles: bool,
pub(crate) omit_extensions: &'static [&'static str],
}

impl Dir {
pub(super) fn new(route: &'static str, dir_path: std::path::PathBuf) -> std::io::Result<Self> {
let dir_path = dir_path.canonicalize()?;

if !dir_path.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{} is not directory", dir_path.display()))
)
}

let mut files = Vec::<(PathBuf, File)>::new(); {
fn fetch_entries(
dir: std::path::PathBuf
) -> std::io::Result<Vec<std::path::PathBuf>> {
dir.read_dir()?
.map(|de| de.map(|de| de.path()))
.collect()
}

let mut entries = fetch_entries(dir_path.clone())?;
while let Some(entry) = entries.pop() {
if entry.is_file() {
files.push((
entry.clone(),
std::fs::File::open(entry)?
));

} else if entry.is_dir() {
entries.append(&mut fetch_entries(entry)?)

} else {
continue
}
}
}

Ok(Self {
route,
files,
serve_dotfiles: false,
omit_extensions: &[],
})
}

/// Whether to serve dotfiles like `.abc` (default: false)
///
/// When `false`, files of name starting with `.` will be ignored.
pub fn serve_dotfiles(mut self, yes: bool) -> Self {
self.serve_dotfiles = yes;
self
}

/// File extensions (leading `.` trimmed) that should not be appeared in server path.
/// For example, if you omit `[".html"]`, `/abc.html` will be served at `/abc` instead of `/abc.html`.
///
/// As a special case, `index.html` will be served at `/` when `".html"` is omitted.
pub fn omit_extensions(mut self, extensions_to_omit: &'static [&'static str]) -> Self {
self.omit_extensions = extensions_to_omit;
self
}
}

#[derive(Clone)]
pub(super) struct StaticFileHandler {
mime: &'static str,
content: std::sync::Arc<Vec<u8>>,
}

impl StaticFileHandler {
pub(super) fn new(path: &Path, file: std::fs::File) -> std::io::Result<Self> {
let mime = ::mime_guess::from_path(path)
.first_raw()
.unwrap_or("application/octet-stream");

let mut content = vec![
u8::default();
file.metadata().unwrap().len() as usize
]; {use std::io::Read;
let mut file = file;
file.read_exact(&mut content)?;
}

Ok(Self { mime, content:std::sync::Arc::new(content) })
}
}

impl IntoHandler<File> for StaticFileHandler {
fn n_params(&self) -> usize {0}

fn into_handler(self) -> Handler {
let this: &'static StaticFileHandler
= Box::leak(Box::new(self));

Handler::new(|_| Box::pin(async {
let mut res = crate::Response::OK();
{
res.headers.set().ContentType(this.mime);
res.content = Content::Payload({
let content: &'static [u8] = &this.content;
content.into()
});
}
res
}), #[cfg(feature="openapi")] {use crate::openapi;
openapi::Operation::with(openapi::Responses::new([(
200,
openapi::Response::when("OK")
.content(this.mime, openapi::string().format("binary"))
)]))
})
}
}
6 changes: 4 additions & 2 deletions ohkami/src/ohkami/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#[cfg(test)]
mod _test;

#[cfg(feature="__rt_native__")]
mod dir;

pub(crate) mod routing;
pub use routing::{Route, Routing};

Expand Down Expand Up @@ -382,8 +385,7 @@ impl Ohkami {
///
/// ### static directory serving
///
/// `.Dir` mounts a directory and generates handlers
/// for serving each static file in it.
/// `.Dir` mounts a directory and serves all files in it/its sub directories.
///
/// This doesn't work on `rt_worker` ( of course because there Ohkami can't
/// touch your local file system ). Consider using `asset` of wrangler.{toml/json}
Expand Down
Loading