rocket_assets_fairing/
lib.rs1use normpath::PathExt;
30use rocket::{
31 error,
32 fairing::{self, Fairing, Info, Kind},
33 fs::NamedFile,
34 info, info_,
35 outcome::IntoOutcome,
36 request::{self, FromRequest, Request},
37 response::{self, Responder, Response},
38 Build, Orbit, Rocket,
39};
40use std::io;
41use std::path::{Path, PathBuf};
42
43pub struct Assets {
45 path: PathBuf,
46 cache_max_age: i32,
47}
48
49impl Assets {
50 pub fn fairing() -> impl Fairing {
52 AssetsFairing
53 }
54 pub async fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<Asset> {
56 let mut asset_path = self.path.clone();
57 asset_path.push(path);
58 let file = NamedFile::open(Path::new(&asset_path)).await?;
59 let cache_max_age = self.cache_max_age;
60 Ok(Asset {
61 file,
62 cache_max_age,
63 })
64 }
65}
66#[rocket::async_trait]
67impl<'r> FromRequest<'r> for &'r Assets {
68 type Error = ();
69 async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
70 req.rocket().state::<Assets>().or_forward(())
71 }
72}
73
74pub struct Asset {
76 file: NamedFile,
77 cache_max_age: i32,
78}
79impl<'r> Responder<'r, 'static> for Asset {
80 fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
81 let cache_control = format!("max-age={}", self.cache_max_age);
82 Response::build_from(self.file.respond_to(req)?)
83 .raw_header("Cache-control", cache_control)
84 .ok()
85 }
86}
87
88struct AssetsFairing;
89#[rocket::async_trait]
90impl Fairing for AssetsFairing {
91 fn info(&self) -> Info {
92 let kind = Kind::Response | Kind::Ignite | Kind::Liftoff;
93 Info {
94 kind,
95 name: "Static Assets",
96 }
97 }
98
99 async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
100 use rocket::figment::value::magic::RelativePathBuf;
101
102 let configured_dir = rocket
103 .figment()
104 .extract_inner::<RelativePathBuf>("assets_dir")
105 .map(|path| path.relative());
106
107 let relative_path = match configured_dir {
108 Ok(dir) => dir,
109 Err(e) if e.missing() => "assets/".into(),
110 Err(e) => {
111 rocket::config::pretty_print_error(e);
112 return Err(rocket);
113 }
114 };
115
116 let path = match relative_path.normalize() {
117 Ok(path) => path.into_path_buf(),
118 Err(e) => {
119 error!(
120 "Invalid assets directory '{}': {}.",
121 relative_path.display(),
122 e
123 );
124 return Err(rocket);
125 }
126 };
127
128 let cache_max_age = rocket
129 .figment()
130 .extract_inner::<i32>("assets_max_age")
131 .unwrap_or(86400);
132
133 Ok(rocket.manage(Assets {
134 path,
135 cache_max_age,
136 }))
137 }
138
139 async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
140 use rocket::{figment::Source, log::PaintExt, yansi::Paint};
141
142 let state = rocket
143 .state::<Assets>()
144 .expect("Template AssetsContext registered in on_ignite");
145
146 info!("{}{}:", Paint::emoji("📐 "), Paint::magenta("Assets"));
147 info_!("directory: {}", Paint::white(Source::from(&*state.path)));
148 info_!("cache max age: {}", Paint::white(state.cache_max_age));
149 }
150}