Skip to main content

tower_http/services/fs/serve_dir/
backend.rs

1//! Pluggable backend trait for [`ServeDir`](super::ServeDir).
2//!
3//! The [`Backend`] trait abstracts file system operations so that `ServeDir` can serve
4//! files from sources other than the local filesystem (e.g. rust-embed, include_dir, S3).
5
6use std::{future::Future, io, path::PathBuf, pin::Pin, time::SystemTime};
7use tokio::io::{AsyncRead, AsyncSeek};
8
9/// Trait for file metadata.
10///
11/// This is the information `ServeDir` needs about a file or directory without opening it.
12pub trait Metadata: Send + 'static {
13    /// Returns `true` if this metadata refers to a directory.
14    fn is_dir(&self) -> bool;
15
16    /// Returns the last modification time, if available.
17    fn modified(&self) -> io::Result<SystemTime>;
18
19    /// Returns the size of the file in bytes.
20    fn len(&self) -> u64;
21
22    /// Returns `true` if the file is empty (zero bytes).
23    fn is_empty(&self) -> bool {
24        self.len() == 0
25    }
26}
27
28/// Trait for an opened file.
29///
30/// Must support async reading and seeking (for HTTP range requests).
31/// In-memory backends can use [`std::io::Cursor`] to satisfy the `AsyncSeek` requirement.
32pub trait File: AsyncRead + AsyncSeek + Unpin + Send + Sync {
33    /// The metadata type returned by this file.
34    type Metadata: Metadata;
35
36    /// Future returned by [`File::metadata`].
37    type MetadataFuture<'a>: Future<Output = io::Result<Self::Metadata>> + Send
38    where
39        Self: 'a;
40
41    /// Returns metadata for this opened file.
42    fn metadata(&self) -> Self::MetadataFuture<'_>;
43}
44
45/// Trait abstracting filesystem operations for [`ServeDir`](super::ServeDir).
46///
47/// Implement this trait to serve files from non-filesystem sources.
48/// The default implementation ([`TokioBackend`]) wraps `tokio::fs`.
49pub trait Backend: Clone + Send + Sync + 'static {
50    /// The file type returned by [`Backend::open`].
51    type File: File<Metadata = Self::Metadata>;
52
53    /// The metadata type returned by [`Backend::metadata`].
54    type Metadata: Metadata;
55
56    /// Future returned by [`Backend::open`].
57    type OpenFuture: Future<Output = io::Result<Self::File>> + Send;
58
59    /// Future returned by [`Backend::metadata`].
60    type MetadataFuture: Future<Output = io::Result<Self::Metadata>> + Send;
61
62    /// Open a file at the given path.
63    fn open(&self, path: PathBuf) -> Self::OpenFuture;
64
65    /// Retrieve metadata for the given path without opening the file.
66    fn metadata(&self, path: PathBuf) -> Self::MetadataFuture;
67}
68
69/// Default [`Backend`] implementation using `tokio::fs`.
70#[derive(Clone, Debug, Default)]
71pub struct TokioBackend;
72
73impl Backend for TokioBackend {
74    type File = TokioFile;
75    type Metadata = std::fs::Metadata;
76    type OpenFuture = Pin<Box<dyn Future<Output = io::Result<TokioFile>> + Send>>;
77    type MetadataFuture = Pin<Box<dyn Future<Output = io::Result<std::fs::Metadata>> + Send>>;
78
79    fn open(&self, path: PathBuf) -> Self::OpenFuture {
80        Box::pin(async move {
81            let file = tokio::fs::File::open(&path).await?;
82            Ok(TokioFile(file))
83        })
84    }
85
86    fn metadata(&self, path: PathBuf) -> Self::MetadataFuture {
87        Box::pin(async move { tokio::fs::metadata(&path).await })
88    }
89}
90
91/// Wrapper around [`tokio::fs::File`] implementing the [`File`] trait.
92#[derive(Debug)]
93pub struct TokioFile(tokio::fs::File);
94
95impl AsyncRead for TokioFile {
96    fn poll_read(
97        mut self: Pin<&mut Self>,
98        cx: &mut std::task::Context<'_>,
99        buf: &mut tokio::io::ReadBuf<'_>,
100    ) -> std::task::Poll<io::Result<()>> {
101        Pin::new(&mut self.0).poll_read(cx, buf)
102    }
103}
104
105impl AsyncSeek for TokioFile {
106    fn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {
107        Pin::new(&mut self.0).start_seek(position)
108    }
109
110    fn poll_complete(
111        mut self: Pin<&mut Self>,
112        cx: &mut std::task::Context<'_>,
113    ) -> std::task::Poll<io::Result<u64>> {
114        Pin::new(&mut self.0).poll_complete(cx)
115    }
116}
117
118impl File for TokioFile {
119    type Metadata = std::fs::Metadata;
120    type MetadataFuture<'a> =
121        Pin<Box<dyn Future<Output = io::Result<std::fs::Metadata>> + Send + 'a>>;
122
123    fn metadata(&self) -> Self::MetadataFuture<'_> {
124        Box::pin(async move { self.0.metadata().await })
125    }
126}
127
128impl Metadata for std::fs::Metadata {
129    fn is_dir(&self) -> bool {
130        self.is_dir()
131    }
132
133    fn modified(&self) -> io::Result<SystemTime> {
134        self.modified()
135    }
136
137    fn len(&self) -> u64 {
138        self.len()
139    }
140}