1use super::{with_file, with_partial_file};
2use super::{Caching, IntoPathBuf, Range};
3
4use crate::error::ClientErrorKind;
5use crate::header::{Method, StatusCode};
6use crate::into::{IntoResponse, IntoRoute};
7use crate::routes::{ParamsNames, PathParams, Route, RoutePath};
8use crate::util::PinnedFuture;
9use crate::{Error, Request, Resources, Response};
10
11use std::borrow::Cow;
12use std::io;
13use std::path::Path;
14use std::time::Duration;
15
16pub async fn serve_file(
18 path: impl AsRef<Path>,
19 req: &Request,
20 caching: Option<Caching>,
21) -> io::Result<Response> {
22 if matches!(&caching, Some(c) if c.if_none_match(req.header())) {
24 return Ok(caching.unwrap().into_response());
25 }
26
27 let range = Range::parse(req.header());
28
29 let mut res = match range {
30 Some(range) => with_partial_file(path, range).await?.into_response(),
31 None => with_file(path).await?.into_response(),
32 };
33
34 if let Some(caching) = caching {
36 if matches!(
37 res.header.status_code,
38 StatusCode::OK | StatusCode::NOT_FOUND
39 ) {
40 caching.complete_header(&mut res.header);
41 }
42 }
43
44 Ok(res)
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub(super) enum CachingBuilder {
49 None,
50 Default,
51 MaxAge(Duration),
52}
53
54impl From<CachingBuilder> for Option<Caching> {
55 fn from(b: CachingBuilder) -> Self {
56 match b {
57 CachingBuilder::None => None,
58 CachingBuilder::Default => Some(Caching::default()),
59 CachingBuilder::MaxAge(age) => Some(Caching::new(age)),
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct StaticFiles {
81 uri: &'static str,
82 path: &'static str,
83 caching: CachingBuilder,
84}
85
86impl StaticFiles {
87 pub const fn new(uri: &'static str, path: &'static str) -> Self {
89 Self {
90 uri,
91 path,
92 caching: CachingBuilder::Default,
93 }
94 }
95
96 pub const fn no_cache(uri: &'static str, path: &'static str) -> Self {
97 Self {
98 uri,
99 path,
100 caching: CachingBuilder::None,
101 }
102 }
103
104 pub const fn cache_with_age(
105 uri: &'static str,
106 path: &'static str,
107 max_age: Duration,
108 ) -> Self {
109 Self {
110 uri,
111 path,
112 caching: CachingBuilder::MaxAge(max_age),
113 }
114 }
115}
116
117impl IntoRoute for StaticFiles {
118 type IntoRoute = StaticFilesRoute;
119
120 fn into_route(self) -> StaticFilesRoute {
121 StaticFilesRoute {
122 uri: self.uri.into(),
123 path: self.path.into(),
124 caching: self.caching.into(),
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct StaticFilesOwned {
146 uri: String,
147 path: String,
148 caching: CachingBuilder,
149}
150
151impl StaticFilesOwned {
152 pub fn new(uri: String, path: String) -> Self {
154 Self {
155 uri,
156 path,
157 caching: CachingBuilder::Default,
158 }
159 }
160
161 pub fn no_cache(uri: String, path: String) -> Self {
162 Self {
163 uri,
164 path,
165 caching: CachingBuilder::None,
166 }
167 }
168
169 pub fn cache_with_age(
170 uri: String,
171 path: String,
172 max_age: Duration,
173 ) -> Self {
174 Self {
175 uri,
176 path,
177 caching: CachingBuilder::MaxAge(max_age),
178 }
179 }
180}
181
182impl IntoRoute for StaticFilesOwned {
183 type IntoRoute = StaticFilesRoute;
184
185 fn into_route(self) -> StaticFilesRoute {
186 StaticFilesRoute {
187 uri: self.uri.trim_end_matches('/').to_string().into(),
188 path: self.path.into(),
189 caching: self.caching.into(),
190 }
191 }
192}
193
194#[doc(hidden)]
195pub struct StaticFilesRoute {
196 uri: Cow<'static, str>,
198 path: Cow<'static, str>,
199 caching: Option<Caching>,
200}
201
202impl Route for StaticFilesRoute {
203 fn validate_requirements(&self, _params: &ParamsNames, _data: &Resources) {}
204
205 fn path(&self) -> RoutePath {
206 RoutePath {
207 method: Some(Method::GET),
208 path: format!("{}/{{*rem}}", self.uri).into(),
209 }
210 }
211
212 fn call<'a>(
213 &'a self,
214 req: &'a mut Request,
215 _params: &'a PathParams,
216 _: &'a Resources,
217 ) -> PinnedFuture<'a, crate::Result<Response>> {
218 let uri = &self.uri;
219 let caching = self.caching.clone();
220
221 PinnedFuture::new(async move {
222 let res_path_buf =
223 req.header().uri().path()[uri.len()..].into_path_buf();
224
225 let path_buf = res_path_buf
228 .map_err(|e| Error::new(ClientErrorKind::BadRequest, e))?;
229
230 let path_buf = Path::new(&*self.path).join(path_buf);
232
233 serve_file(path_buf, &req, caching)
234 .await
235 .map_err(Error::from_client_io)
236 })
237 }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub struct StaticFile {
251 uri: &'static str,
252 path: &'static str,
253 caching: CachingBuilder,
254}
255
256impl StaticFile {
257 pub const fn new(uri: &'static str, path: &'static str) -> Self {
259 Self {
260 uri,
261 path,
262 caching: CachingBuilder::Default,
263 }
264 }
265
266 pub const fn no_cache(uri: &'static str, path: &'static str) -> Self {
267 Self {
268 uri,
269 path,
270 caching: CachingBuilder::None,
271 }
272 }
273
274 pub const fn cache_with_age(
275 uri: &'static str,
276 path: &'static str,
277 max_age: Duration,
278 ) -> Self {
279 Self {
280 uri,
281 path,
282 caching: CachingBuilder::MaxAge(max_age),
283 }
284 }
285}
286
287impl IntoRoute for StaticFile {
288 type IntoRoute = StaticFileRoute;
289
290 fn into_route(self) -> StaticFileRoute {
291 StaticFileRoute {
292 uri: self.uri.trim_end_matches('/').into(),
293 path: self.path.into(),
294 caching: self.caching.into(),
295 }
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct StaticFileOwned {
316 uri: String,
317 path: String,
318 caching: CachingBuilder,
319}
320
321impl StaticFileOwned {
322 pub const fn new(uri: String, path: String) -> Self {
324 Self {
325 uri,
326 path,
327 caching: CachingBuilder::Default,
328 }
329 }
330
331 pub const fn no_cache(uri: String, path: String) -> Self {
332 Self {
333 uri,
334 path,
335 caching: CachingBuilder::None,
336 }
337 }
338
339 pub const fn cache_with_age(
340 uri: String,
341 path: String,
342 max_age: Duration,
343 ) -> Self {
344 Self {
345 uri,
346 path,
347 caching: CachingBuilder::MaxAge(max_age),
348 }
349 }
350}
351
352impl IntoRoute for StaticFileOwned {
353 type IntoRoute = StaticFileRoute;
354
355 fn into_route(self) -> StaticFileRoute {
356 StaticFileRoute {
357 uri: self.uri.into(),
358 path: self.path.into(),
359 caching: self.caching.into(),
360 }
361 }
362}
363
364#[doc(hidden)]
365pub struct StaticFileRoute {
366 uri: Cow<'static, str>,
367 path: Cow<'static, str>,
368 caching: Option<Caching>,
369}
370
371impl Route for StaticFileRoute {
372 fn validate_requirements(&self, _params: &ParamsNames, _data: &Resources) {}
373
374 fn path(&self) -> RoutePath {
375 RoutePath {
376 method: Some(Method::GET),
377 path: self.uri.clone(),
378 }
379 }
380
381 fn call<'a>(
382 &'a self,
383 req: &'a mut Request,
384 _params: &'a PathParams,
385 _data: &'a Resources,
386 ) -> PinnedFuture<'a, crate::Result<Response>> {
387 PinnedFuture::new(async move {
388 serve_file(&*self.path, &req, self.caching.clone())
389 .await
390 .map_err(Error::from_client_io)
391 })
392 }
393}