1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
#![forbid(unsafe_code)] #![deny( missing_copy_implementations, missing_crate_level_docs, missing_debug_implementations, missing_docs, nonstandard_style, unused_qualifications )] /*! Serves static file assets from the file system. ## stability note Please note that this crate is fairly incomplete, while functional. It does not include any notion of range requests or cache headers. It serves all files from disk every time, with no in-memory caching. This may also merge with the [static file handler](https://docs.trillium.rs/trillium_static/) ``` use trillium_static_compiled::{include_dir, StaticCompiledHandler}; let handler = StaticCompiledHandler::new(include_dir!("examples/files")) .with_index_file("index.html"); // given the following directory layout // // examples/files // ├── index.html // ├── subdir // │ └── index.html // └── subdir_with_no_index // └── plaintext.txt // use trillium_testing::prelude::*; assert_ok!( get("/").on(&handler), "<h1>hello world</h1>", "content-type" => "text/html" ); assert_not_handled!(get("/file_that_does_not_exist.txt").on(&handler)); assert_ok!(get("/index.html").on(&handler)); assert_ok!(get("/subdir/index.html").on(&handler), "subdir index.html"); assert_ok!(get("/subdir").on(&handler), "subdir index.html"); assert_not_handled!(get("/subdir_with_no_index").on(&handler)); assert_ok!( get("/subdir_with_no_index/plaintext.txt").on(&handler), "plaintext file", "content-type" => "text/plain" ); // with a different index file let plaintext_index = StaticCompiledHandler::new(include_dir!("examples/files")) .with_index_file("plaintext.txt"); assert_not_handled!(get("/").on(&plaintext_index)); assert_not_handled!(get("/subdir").on(&plaintext_index)); assert_ok!( get("/subdir_with_no_index").on(&plaintext_index), "plaintext file", "content-type" => "text/plain" ); // with no index file let no_index = StaticCompiledHandler::new(include_dir!("examples/files")); assert_not_handled!(get("/").on(&no_index)); assert_not_handled!(get("/subdir").on(&no_index)); assert_not_handled!(get("/subdir_with_no_index").on(&no_index)); ``` */ pub use include_dir::include_dir; use include_dir::{Dir, DirEntry, File}; use trillium::http_types::content::ContentType; use trillium::{async_trait, Conn, Handler}; /** The static compiled handler which contains the compile-time loaded assets */ #[derive(Debug, Clone, Copy)] pub struct StaticCompiledHandler { dir: Dir<'static>, index_file: Option<&'static str>, } impl StaticCompiledHandler { /// Constructs a new StaticCompiledHandler. This must be used in /// conjunction with [`include_dir!`]. See crate-level docs for /// example usage. pub fn new(dir: Dir<'static>) -> Self { Self { dir, index_file: None, } } /// Configures the optional index file for this /// StaticCompiledHandler. See the crate-level docs for example /// usage. pub fn with_index_file(mut self, file: &'static str) -> Self { self.index_file = Some(file); self } fn serve_file(&self, mut conn: Conn, file: File) -> Conn { if let Some(mime) = mime_db::lookup(file.path().to_string_lossy().as_ref()) { conn.headers_mut().apply(ContentType::new(mime)); } conn.ok(file.contents()) } fn get_item(&self, path: &str) -> Option<DirEntry> { if path.is_empty() { Some(DirEntry::Dir(self.dir)) } else { self.dir .get_dir(path) .map(DirEntry::Dir) .or_else(|| self.dir.get_file(path).map(DirEntry::File)) } } } #[async_trait] impl Handler for StaticCompiledHandler { async fn run(&self, conn: Conn) -> Conn { match ( self.get_item(conn.path().trim_start_matches('/')), self.index_file, ) { (None, _) => conn, (Some(DirEntry::File(file)), _) => self.serve_file(conn, file), (Some(DirEntry::Dir(_)), None) => conn, (Some(DirEntry::Dir(dir)), Some(index_file)) => { if let Some(file) = dir.get_file(dir.path().join(index_file)) { self.serve_file(conn, file) } else { conn } } } } }