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
use http::{
header::{self, HeaderMap},
StatusCode,
};
use tide::{Request, Response, Result};
use async_std::{fs, io, task};
use std::path::{Component, Path, PathBuf};
pub trait StaticRootDir {
fn root_dir(&self) -> &Path;
}
impl<T: StaticRootDir> StaticRootDir for &T {
fn root_dir(&self) -> &Path {
(*self).root_dir()
}
}
fn stream_bytes(
root: impl StaticRootDir,
actual_path: &str,
headers: &HeaderMap,
) -> io::Result<Response> {
let path = &get_path(&root, actual_path);
let meta = task::block_on(fs::metadata(path)).ok();
let meta = match meta {
Some(m) => m,
None => {
return Ok(tide::Response::new(StatusCode::NOT_FOUND.as_u16())
.set_header(header::CONTENT_TYPE.as_str(), mime::TEXT_HTML.as_ref())
.body_string(format!("Couldn't locate file {:?}", actual_path)));
}
};
if !meta.is_file() {
if !actual_path.ends_with("/") {
return Ok(tide::Response::new(StatusCode::MOVED_PERMANENTLY.as_u16())
.set_header(header::LOCATION.as_str(), String::from(actual_path) + "/")
.body_string("".into()));
} else {
let index = Path::new(actual_path).join("index.html");
return stream_bytes(root, &*index.to_string_lossy(), headers);
}
}
let mime = mime_guess::from_path(path).first_or_octet_stream();
let size = format!("{}", meta.len());
let file = task::block_on(fs::File::open(PathBuf::from(path))).unwrap();
let reader = io::BufReader::new(file);
Ok(tide::Response::new(StatusCode::OK.as_u16())
.body(reader)
.set_header(header::CONTENT_LENGTH.as_str(), size)
.set_mime(mime))
}
fn get_path(root: impl StaticRootDir, path: &str) -> PathBuf {
let rel_path = Path::new(path)
.components()
.fold(PathBuf::new(), |mut result, p| {
match p {
Component::Normal(x) => result.push({
let s = x.to_str().unwrap_or("");
&*percent_encoding::percent_decode(s.as_bytes()).decode_utf8_lossy()
}),
Component::ParentDir => {
result.pop();
}
_ => (),
}
result
});
root.root_dir().join(rel_path)
}
pub async fn serve_static_files(ctx: Request<impl StaticRootDir>) -> Result {
let path: String = ctx.param("path").expect(
"`tide_naive_static_files::serve_static_files` requires a `*path` glob param at the end!",
);
let root = ctx.state();
let resp = stream_bytes(root, &path, ctx.headers());
match resp {
Err(e) => {
eprintln!("tide-naive-static-files internal error: {}", e);
let resp = tide::Response::new(StatusCode::INTERNAL_SERVER_ERROR.as_u16())
.set_header(header::CONTENT_TYPE.as_str(), mime::TEXT_HTML.as_ref())
.body_string("Internal server error!".into());
Ok(resp)
}
Ok(resp) => Ok(resp),
}
}