Skip to main content

tako_rs_streams/static/
file.rs

1use std::path::PathBuf;
2
3#[cfg(feature = "compio")]
4use compio::fs;
5use http::StatusCode;
6use tako_rs_core::body::TakoBody;
7use tako_rs_core::responder::Responder;
8use tako_rs_core::types::Request;
9use tako_rs_core::types::Response;
10#[cfg(not(feature = "compio"))]
11use tokio::fs;
12
13/// Static file server for serving individual files.
14#[doc(alias = "serve_file")]
15pub struct ServeFile {
16  path: PathBuf,
17}
18
19/// Builder for configuring a `ServeFile` instance.
20#[must_use]
21pub struct ServeFileBuilder {
22  path: PathBuf,
23}
24
25impl ServeFileBuilder {
26  /// Creates a new builder with the specified file path.
27  #[inline]
28  pub fn new<P: Into<PathBuf>>(path: P) -> Self {
29    Self { path: path.into() }
30  }
31
32  /// Builds and returns the configured `ServeFile` instance.
33  #[inline]
34  #[must_use]
35  pub fn build(self) -> ServeFile {
36    ServeFile { path: self.path }
37  }
38}
39
40impl ServeFile {
41  /// Creates a new builder for configuring a `ServeFile`.
42  pub fn builder<P: Into<PathBuf>>(path: P) -> ServeFileBuilder {
43    ServeFileBuilder::new(path)
44  }
45
46  /// Serves the configured file with appropriate MIME type.
47  async fn serve_file(&self) -> Option<Response> {
48    match fs::read(&self.path).await {
49      Ok(contents) => {
50        let mime = mime_guess::from_path(&self.path).first_or_octet_stream();
51        Some(
52          http::Response::builder()
53            .status(StatusCode::OK)
54            .header(http::header::CONTENT_TYPE, mime.to_string())
55            .body(TakoBody::from(contents))
56            .unwrap(),
57        )
58      }
59      Err(_) => None,
60    }
61  }
62
63  /// Handles an HTTP request to serve the configured static file.
64  ///
65  /// The request itself is **ignored** — `ServeFile` always serves the file
66  /// configured on the builder, regardless of `req.uri()`. Mount this
67  /// handler on a single specific route (e.g. `/manifest.json`), not on a
68  /// catch-all glob, otherwise every URL under that glob will return the
69  /// same file. Use [`ServeDir`](super::ServeDir) when you want path-aware static serving.
70  pub async fn handle(&self, _req: Request) -> impl Responder {
71    if let Some(resp) = self.serve_file().await {
72      resp
73    } else {
74      let mut resp = http::Response::new(TakoBody::from("File not found"));
75      *resp.status_mut() = StatusCode::NOT_FOUND;
76      resp
77    }
78  }
79}