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
150
151
|
mod error;
mod fs;
mod settings;
mod templated;
use std::{os::unix::fs::MetadataExt, str::FromStr};
use axum::{
body::Body, extract::Path, http::header, response::Response, routing::get, Extension, Router,
};
use bempline::{Document, Options};
use camino::Utf8PathBuf;
pub use error::RuntimeError;
use fs::Filesystem;
use settings::Settings;
use tokio_util::io::ReaderStream;
use crate::templated::Templated;
#[tokio::main]
async fn main() {
let fs = Filesystem::new("../inf/served");
let settings = Settings {
template_dir: Utf8PathBuf::from("../inf/templates"),
};
let app = Router::new()
.route("/", get(index_handler))
.route("/*path", get(handler))
.layer(Extension(fs))
.layer(Extension(settings));
let listener = tokio::net::TcpListener::bind("0.0.0.0:2560").await.unwrap();
axum::serve(listener, app).await.unwrap()
}
async fn index_handler(fse: Extension<Filesystem>, se: Extension<Settings>) -> Response {
handler(fse, se, Path(String::from("/"))).await
}
async fn handler(
Extension(fs): Extension<Filesystem>,
Extension(settings): Extension<Settings>,
Path(path): Path<String>,
) -> Response {
match falible_handler(fs, settings, path).await {
Ok(resp) => resp,
Err(re) => Response::builder()
.body(Body::from(re.to_string()))
.unwrap(),
}
}
async fn falible_handler(
fs: Filesystem,
settings: Settings,
path: String,
) -> Result<Response, RuntimeError> {
println!("raw = {path}");
let path = path.parse()?;
println!("path = {path}");
let filepath = fs.resolve(&path)?;
let ext = filepath.extension().unwrap_or_default();
if ext != "html" {
send_file(filepath).await
} else {
let content = Filesystem::read_to_string(&filepath).await?;
match Templated::from_str(&content) {
Ok(templated) => send_template(templated, filepath, settings).await,
Err(_) => Ok(Response::builder()
.header(header::CONTENT_TYPE, "text/html")
.body(Body::from(content))
.unwrap()),
}
}
}
// 20 megabytes
const STREAM_AFTER: u64 = 20 * 1024 * 1024;
async fn send_file(filepath: Utf8PathBuf) -> Result<Response, RuntimeError> {
let ext = filepath.extension().unwrap_or_default();
let mime = match ext {
// Text
"css" => "text/css",
"html" => "text/html",
"js" => "text/javascript",
"txt" => "txt/plain",
// Multimedia
"gif" => "image/gif",
"jpg" | "jpeg" => "image/jpeg",
"mp4" => "video/mp4",
"png" => "image/png",
_ => "",
};
let mut response = Response::builder();
if !mime.is_empty() {
response = response.header(header::CONTENT_TYPE, mime);
}
let metadata = Filesystem::metadata(&filepath)?;
if metadata.size() > STREAM_AFTER {
let file = Filesystem::open(filepath).await?;
let stream = ReaderStream::new(file);
Ok(response.body(Body::from_stream(stream)).unwrap())
} else {
let content = Filesystem::read(filepath).await?;
Ok(response.body(Body::from(content)).unwrap())
}
}
async fn send_template(
templated: Templated,
path: Utf8PathBuf,
settings: Settings,
) -> Result<Response, RuntimeError> {
let template_stem = templated.frontmatter.get("template").expect("no template");
let template_name = Utf8PathBuf::from(format!("{template_stem}.html"));
let template_path = settings.template_dir.join(template_name);
let filename = path.file_name().expect("template has no filename");
let mut template = Document::from_file(
template_path,
Options::default().include_path(bempline::options::IncludeMethod::Path(
settings.template_dir.as_std_path().to_owned(),
)),
)
.unwrap();
template.set(
"title",
templated.frontmatter.get("title").unwrap_or(filename),
);
template.set("main", templated.content);
Ok(Response::builder()
.header(header::CONTENT_TYPE, "text/html")
.body(Body::from(template.compile()))
.unwrap())
}
|