Skip to content

Commit c135201

Browse files
authored
Merge pull request #46 from stephank/feat/rewrite
Add rewrite function
2 parents 960a750 + 3c47005 commit c135201

File tree

2 files changed

+75
-12
lines changed

2 files changed

+75
-12
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ url = "2.1.0"
2525

2626
[dev-dependencies]
2727
hyper = { version = "1.0.0", features = ["http1", "server"] }
28-
hyper-util = { git = "https://github.com/hyperium/hyper-util.git" }
28+
hyper-util = { version = "0.1.1", features = ["tokio"] }
2929
http-body-util = "0.1.0"
3030
tempfile = "3"
3131
tokio = { version = "1.0.0", features = ["macros", "rt-multi-thread", "net", "io-util"] }

src/resolve.rs

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::{
2-
io::{Error as IoError, ErrorKind as IoErrorKind},
2+
future::Future,
3+
io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult},
34
ops::BitAnd,
45
path::PathBuf,
56
sync::Arc,
67
time::SystemTime,
78
};
89

10+
use futures_util::future::BoxFuture;
911
use http::{header, HeaderValue, Method, Request};
1012
use mime_guess::MimeGuess;
1113
use tokio::fs::File;
@@ -70,6 +72,32 @@ pub struct Resolver<O = TokioFileOpener> {
7072
///
7173
/// Typically initialized with `AcceptEncoding::all()` or `AcceptEncoding::none()`.
7274
pub allowed_encodings: AcceptEncoding,
75+
76+
/// Optional function that can rewrite requests.
77+
///
78+
/// This function is called after parsing the request and before querying the filesystem.
79+
///
80+
/// See `set_rewrite` for a convenience setter that simplifies these types.
81+
pub rewrite: Option<Arc<dyn (Fn(ResolveParams) -> BoxRewriteFuture) + Send + Sync>>,
82+
}
83+
84+
/// Future returned by a rewrite function. See `Resolver::set_rewrite`.
85+
pub type BoxRewriteFuture = BoxFuture<'static, IoResult<ResolveParams>>;
86+
87+
/// All of the parsed request parameters used in resolving a file.
88+
///
89+
/// This struct is primarily used for `Resolver::rewrite` / `Resolver::set_rewrite`.
90+
#[derive(Debug, Clone)]
91+
pub struct ResolveParams {
92+
/// Sanitized path of the request.
93+
pub path: PathBuf,
94+
/// Whether a directory was requested. (The request path ended with a slash.)
95+
pub is_dir_request: bool,
96+
/// Intersection of the request `Accept-Encoding` header and `allowed_encodings`.
97+
///
98+
/// Only modify this field to disable encodings. Enabling additional encodings here may cause
99+
/// a client to receive encodings it does not understand.
100+
pub accept_encoding: AcceptEncoding,
73101
}
74102

75103
/// The result of `Resolver` methods.
@@ -93,7 +121,7 @@ pub enum ResolveResult<F = File> {
93121
}
94122

95123
/// Some IO errors are expected when serving files, and mapped to a regular result here.
96-
fn map_open_err<F>(err: IoError) -> Result<ResolveResult<F>, IoError> {
124+
fn map_open_err<F>(err: IoError) -> IoResult<ResolveResult<F>> {
97125
match err.kind() {
98126
IoErrorKind::NotFound => Ok(ResolveResult::NotFound),
99127
IoErrorKind::PermissionDenied => Ok(ResolveResult::PermissionDenied),
@@ -114,18 +142,38 @@ impl<O: FileOpener> Resolver<O> {
114142
Self {
115143
opener: Arc::new(opener),
116144
allowed_encodings: AcceptEncoding::none(),
145+
rewrite: None,
117146
}
118147
}
119148

149+
/// Configure a function that can rewrite requests.
150+
///
151+
/// This function is called after parsing the request and before querying the filesystem.
152+
///
153+
/// ```rust
154+
/// let mut resolver = hyper_staticfile::Resolver::new("/");
155+
/// resolver.set_rewrite(|mut params| async move {
156+
/// if params.path.extension() == Some("htm".as_ref()) {
157+
/// params.path.set_extension("html");
158+
/// }
159+
/// Ok(params)
160+
/// });
161+
/// ```
162+
pub fn set_rewrite<R, F>(&mut self, rewrite: F) -> &mut Self
163+
where
164+
R: Future<Output = IoResult<ResolveParams>> + Send + 'static,
165+
F: (Fn(ResolveParams) -> R) + Send + Sync + 'static,
166+
{
167+
self.rewrite = Some(Arc::new(move |params| Box::pin(rewrite(params))));
168+
self
169+
}
170+
120171
/// Resolve the request by trying to find the file in the root.
121172
///
122173
/// The returned future may error for unexpected IO errors, passing on the `std::io::Error`.
123174
/// Certain expected IO errors are handled, though, and simply reflected in the result. These are
124175
/// `NotFound` and `PermissionDenied`.
125-
pub async fn resolve_request<B>(
126-
&self,
127-
req: &Request<B>,
128-
) -> Result<ResolveResult<O::File>, IoError> {
176+
pub async fn resolve_request<B>(&self, req: &Request<B>) -> IoResult<ResolveResult<O::File>> {
129177
// Handle only `GET`/`HEAD` and absolute paths.
130178
match *req.method() {
131179
Method::HEAD | Method::GET => {}
@@ -157,12 +205,26 @@ impl<O: FileOpener> Resolver<O> {
157205
&self,
158206
request_path: &str,
159207
accept_encoding: AcceptEncoding,
160-
) -> Result<ResolveResult<O::File>, IoError> {
208+
) -> IoResult<ResolveResult<O::File>> {
161209
// Sanitize input path.
162-
let RequestedPath {
163-
sanitized: mut path,
210+
let requested_path = RequestedPath::resolve(request_path);
211+
212+
// Apply optional rewrite.
213+
let ResolveParams {
214+
mut path,
164215
is_dir_request,
165-
} = RequestedPath::resolve(request_path);
216+
accept_encoding,
217+
} = {
218+
let mut params = ResolveParams {
219+
path: requested_path.sanitized,
220+
is_dir_request: requested_path.is_dir_request,
221+
accept_encoding,
222+
};
223+
if let Some(ref rewrite) = self.rewrite {
224+
params = rewrite(params).await?;
225+
}
226+
params
227+
};
166228

167229
// Try to open the file.
168230
let file = match self.opener.open(&path).await {
@@ -219,7 +281,7 @@ impl<O: FileOpener> Resolver<O> {
219281
file: FileWithMetadata<O::File>,
220282
path: PathBuf,
221283
accept_encoding: AcceptEncoding,
222-
) -> Result<ResolveResult<O::File>, IoError> {
284+
) -> IoResult<ResolveResult<O::File>> {
223285
// Determine MIME-type. This needs to happen before we resolve a pre-encoded file.
224286
let mime = MimeGuess::from_path(&path)
225287
.first()
@@ -263,6 +325,7 @@ impl<O> Clone for Resolver<O> {
263325
Self {
264326
opener: self.opener.clone(),
265327
allowed_encodings: self.allowed_encodings,
328+
rewrite: self.rewrite.clone(),
266329
}
267330
}
268331
}

0 commit comments

Comments
 (0)