Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernize Mount #66

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
(feat) Fix compilation, change .mount to .on, and add VirtualRoot
  • Loading branch information
reem committed Feb 10, 2015
commit 6efe683fed19d3249e2b58be470a728341770b22
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![crate_name = "mount"]
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
#![feature(core, path)]
#![feature(core, path, collections)]

//! `Mount` provides mounting middleware for the Iron framework.

Expand Down
83 changes: 52 additions & 31 deletions src/mount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ use sequence_trie::SequenceTrie;
use std::fmt;

/// Exposes the original, unmodified path to be stored in `Request::extensions`.
#[derive(Copy)]
#[derive(Debug, Copy)]
pub struct OriginalUrl;
impl typemap::Key for OriginalUrl { type Value = Url; }

/// Exposes the mounting path, so a request can know its own relative address.
#[derive(Debug, Copy)]
pub struct VirtualRoot;
impl typemap::Key for VirtualRoot { type Value = Url; }

/// `Mount` is a simple mounting middleware.
///
/// Mounting allows you to install a handler on a route and have it receive requests as if they
Expand All @@ -25,7 +30,7 @@ pub struct Mount {
}

struct Match {
handler: Box<Handler + Send + Sync>,
handler: Box<Handler>,
length: usize
}

Expand Down Expand Up @@ -57,68 +62,84 @@ impl Mount {
/// For a given request, the *most specific* handler will be selected.
///
/// Existing handlers on the same route will be overwritten.
pub fn mount<H: Handler>(&mut self, route: &str, handler: H) -> &mut Mount {
pub fn on<H: Handler>(&mut self, route: &str, handler: H) -> &mut Mount {
// Parse the route into a list of strings. The unwrap is safe because strs are UTF-8.
let key: Vec<String> = Path::new(route).str_components().map(|s| s.unwrap().to_string()).collect();
let key = Path::new(route).str_components()
.map(|s| s.unwrap().to_string()).collect::<Vec<_>>();

// Insert a match struct into the trie.
self.inner.insert(key.as_slice(), Match {
handler: Box::new(handler) as Box<Handler + Send + Sync>,
handler: Box::new(handler) as Box<Handler>,
length: key.len()
});
self
}

/// The old way to mount handlers.
#[deprecated = "use .on instead"]
pub fn mount<H: Handler>(&mut self, route: &str, handler: H) -> &mut Mount {
self.on(route, handler)
}
}

impl Handler for Mount {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let original = req.url.path.clone();

// If present, remove the trailing empty string (which represents a trailing slash).
// If it isn't removed the path will never match anything, because
// Path::str_components ignores trailing slashes and will never create routes
// ending in "".
let mut root = original.as_slice();
while root.last().map(|s| &**s) == Some("") {
root = &root[..root.len() - 1];
}

// Find the matching handler.
let matched = {
// Extract the request path.
let path = req.url.path.as_slice();

// If present, remove the trailing empty string (which represents a trailing slash).
// If it isn't removed the path will never match anything, because
// Path::str_components ignores trailing slashes and will never create routes
// ending in "".
let key = match path.last() {
Some(s) if s.is_empty() => &path[..path.len() - 1],
_ => path
};

// Search the Trie for the nearest most specific match.
match self.inner.get_ancestor(key) {
Some(matched) => matched,
None => return Err(IronError::new(NoMatch, status::NotFound))
}
let matched = match self.inner.get_ancestor(root) {
Some(matched) => matched,
None => return Err(IronError::new(NoMatch, status::NotFound))
};

// We have a match, so fire off the child.
// If another mount middleware hasn't already, insert the unmodified url
// into the extensions as the "original url".
let is_outer_mount = !req.extensions.contains::<OriginalUrl>();
if is_outer_mount {
let mut root_url = req.url.clone();
root_url.path = root.to_vec();

req.extensions.insert::<OriginalUrl>(req.url.clone());
req.extensions.insert::<VirtualRoot>(root_url);
} else {
req.extensions.get_mut::<VirtualRoot>().map(|old| {
old.path.push_all(root);
});
}

// Remove the prefix from the request's path before passing it to the mounted handler.
// If the prefix is entirely removed and no trailing slash was present, the new path
// will be the empty list. For the purposes of redirection, conveying that the path
// did not include a trailing slash is more important than providing a non-empty list.
// Remove the prefix from the request's path before passing it to the mounted
// handler. If the prefix is entirely removed and no trailing slash was present,
// the new path will be the empty list.
//
// For the purposes of redirection, conveying that the path did not include
// a trailing slash is more important than providing a non-empty list.
req.url.path = req.url.path.as_slice()[matched.length..].to_vec();

let res = matched.handler.handle(req);

// Reverse the URL munging, for future middleware.
req.url = match req.extensions.get::<OriginalUrl>() {
Some(original) => original.clone(),
None => panic!("OriginalUrl unexpectedly removed from req.extensions.")
};
req.url.path = original.clone();

// If this mount middleware is the outermost mount middleware,
// remove the original url from the extensions map to prevent leakage.
if is_outer_mount {
req.extensions.remove::<OriginalUrl>();
req.extensions.remove::<VirtualRoot>();
} else {
req.extensions.get_mut::<VirtualRoot>().map(|old| {
let old_len = old.path.len();
old.path.truncate(old_len - root.len());
});
}

res
Expand Down