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
                }
            }
        }
    }
}