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

WIP: Changes for the upcoming iron 0.7 release with hyper 0.12 #104

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion examples/doc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ fn main() {

println!("Doc server running on http://localhost:3000/doc/");

Iron::new(mount).http("127.0.0.1:3000").unwrap();
Iron::new(mount).http("127.0.0.1:3000");
}
6 changes: 3 additions & 3 deletions examples/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extern crate mount;
extern crate router;
extern crate staticfile;

use iron::status;
use iron::StatusCode;
use iron::{Iron, Request, Response, IronResult};

use mount::Mount;
Expand All @@ -28,7 +28,7 @@ use std::path::Path;

fn say_hello(req: &mut Request) -> IronResult<Response> {
println!("Running send_hello handler, URL path: {}", req.url.path().join("/"));
Ok(Response::with((status::Ok, "This request was routed!")))
Ok(Response::with((StatusCode::OK, "This request was routed!")))
}

fn main() {
Expand All @@ -41,5 +41,5 @@ fn main() {
.mount("/", router)
.mount("/docs/", Static::new(Path::new("target/doc")));

Iron::new(mount).http("127.0.0.1:3000").unwrap();
Iron::new(mount).http("127.0.0.1:3000");
}
120 changes: 120 additions & 0 deletions src/httpdate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use std::fmt::{self, Display};
use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use time;

/// A timestamp with HTTP formatting and parsing
// Prior to 1995, there were three different formats commonly used by
// servers to communicate timestamps. For compatibility with old
// implementations, all three are defined here. The preferred format is
// a fixed-length and single-zone subset of the date and time
// specification used by the Internet Message Format [RFC5322].
//
// HTTP-date = IMF-fixdate / obs-date
//
// An example of the preferred format is
//
// Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
//
// Examples of the two obsolete formats are
//
// Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
//
// A recipient that parses a timestamp value in an HTTP header field
// MUST accept all three HTTP-date formats. When a sender generates a
// header field that contains one or more timestamps defined as
// HTTP-date, the sender MUST generate those timestamps in the
// IMF-fixdate format.
//
// This code is based on `src/hyper/header/shared/httpdate.rs` from
// hyper 0.11 (https://github.com/hyperium/hyper)
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct HttpDate(pub time::Tm);

impl FromStr for HttpDate {
type Err = ();
fn from_str(s: &str) -> Result<HttpDate, ()> {
match time::strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| {
time::strptime(s, "%A, %d-%b-%y %T %Z")
}).or_else(|_| {
time::strptime(s, "%c")
}) {
Ok(t) => Ok(HttpDate(t)),
Err(_) => Err(()),
}
}
}

impl Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
}
}

impl From<SystemTime> for HttpDate {
fn from(sys: SystemTime) -> HttpDate {
let tmspec = match sys.duration_since(UNIX_EPOCH) {
Ok(dur) => {
time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
},
Err(err) => {
let neg = err.duration();
time::Timespec::new(-(neg.as_secs() as i64), -(neg.subsec_nanos() as i32))
},
};
HttpDate(time::at_utc(tmspec))
}
}

impl From<HttpDate> for SystemTime {
fn from(date: HttpDate) -> SystemTime {
let spec = date.0.to_timespec();
if spec.sec >= 0 {
UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
} else {
UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
}
}
}

#[cfg(test)]
mod tests {
use time::Tm;
use super::HttpDate;

const NOV_07: HttpDate = HttpDate(Tm {
tm_nsec: 0,
tm_sec: 37,
tm_min: 48,
tm_hour: 8,
tm_mday: 7,
tm_mon: 10,
tm_year: 94,
tm_wday: 0,
tm_isdst: 0,
tm_yday: 0,
tm_utcoff: 0,
});

#[test]
fn test_imf_fixdate() {
assert_eq!("Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(), NOV_07);
}

#[test]
fn test_rfc_850() {
assert_eq!("Sunday, 07-Nov-94 08:48:37 GMT".parse::<HttpDate>().unwrap(), NOV_07);
}

#[test]
fn test_asctime() {
assert_eq!("Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(), NOV_07);
}

#[test]
fn test_no_date() {
assert!("this-is-no-date".parse::<HttpDate>().is_err());
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ pub use static_handler::Cache;

mod requested_path;
mod static_handler;
mod httpdate;
62 changes: 34 additions & 28 deletions src/static_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use time::{self, Timespec};
#[cfg(feature = "cache")]
use std::time::Duration;

use iron::{Handler, Url, StatusCode};
use iron::prelude::*;
use iron::{Handler, Url, status};
#[cfg(feature = "cache")]
use iron::modifier::Modifier;
use iron::modifiers::Redirect;
Expand All @@ -28,7 +28,7 @@ use url;
/// ## Errors
///
/// If the path doesn't match any real object in the filesystem, the handler will return
/// a Response with `status::NotFound`. If an IO error occurs whilst attempting to serve
/// a Response with `StatusCode::NOT_FOUND`. If an IO error occurs whilst attempting to serve
/// a file, `FileError(IoError)` will be returned.
#[derive(Clone)]
pub struct Static {
Expand Down Expand Up @@ -76,7 +76,7 @@ impl Static {
#[cfg(feature = "cache")]
fn try_cache<P: AsRef<Path>>(&self, req: &mut Request, path: P) -> IronResult<Response> {
match self.cache {
None => Ok(Response::with((status::Ok, path.as_ref()))),
None => Ok(Response::with((StatusCode::OK, path.as_ref()))),
Some(ref cache) => cache.handle(req, path.as_ref()),
}
}
Expand All @@ -92,9 +92,9 @@ impl Handler for Static {
Ok(meta) => meta,
Err(e) => {
let status = match e.kind() {
io::ErrorKind::NotFound => status::NotFound,
io::ErrorKind::PermissionDenied => status::Forbidden,
_ => status::InternalServerError,
io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};

return Err(IronError::new(e, status))
Expand All @@ -117,21 +117,21 @@ impl Handler for Static {
original_url.path_segments_mut().unwrap().push("");
let redirect_path = Url::from_generic_url(original_url).unwrap();

return Ok(Response::with((status::MovedPermanently,
return Ok(Response::with((StatusCode::MOVED_PERMANENTLY,
format!("Redirecting to {}", redirect_path),
Redirect(redirect_path))));
}

match requested_path.get_file(&metadata) {
// If no file is found, return a 404 response.
None => Err(IronError::new(NoFile, status::NotFound)),
None => Err(IronError::new(NoFile, StatusCode::NOT_FOUND)),
// Won't panic because we know the file exists from get_file.
#[cfg(feature = "cache")]
Some(path) => self.try_cache(req, path),
#[cfg(not(feature = "cache"))]
Some(path) => {
let path: &Path = &path;
Ok(Response::with((status::Ok, path)))
Ok(Response::with((StatusCode::OK, path)))
},
}
}
Expand All @@ -155,12 +155,14 @@ impl Cache {
}

fn handle<P: AsRef<Path>>(&self, req: &mut Request, path: P) -> IronResult<Response> {
use iron::headers::{IfModifiedSince, HttpDate};
use iron::headers::IF_MODIFIED_SINCE;
use httpdate::HttpDate;
use std::str::FromStr;

let path = path.as_ref();

let (size, last_modified_time) = match fs::metadata(path) {
Err(error) => return Err(IronError::new(error, status::InternalServerError)),
Err(error) => return Err(IronError::new(error, StatusCode::INTERNAL_SERVER_ERROR)),
Ok(metadata) => {
use filetime::FileTime;

Expand All @@ -169,13 +171,14 @@ impl Cache {
},
};

let if_modified_since = match req.headers.get::<IfModifiedSince>().cloned() {
let if_modified_since = match req.headers.get(IF_MODIFIED_SINCE).cloned() {
None => return self.response_with_cache(req, path, size, last_modified_time),
Some(IfModifiedSince(HttpDate(time))) => time.to_timespec(),
// TODO: Error handling for to_str() & from_str()
Some(time) => HttpDate::from_str(time.to_str().unwrap()).unwrap().0.to_timespec(),
};

if last_modified_time <= if_modified_since {
Ok(Response::with(status::NotModified))
Ok(Response::with(StatusCode::NOT_MODIFIED))
} else {
self.response_with_cache(req, path, size, last_modified_time)
}
Expand All @@ -186,32 +189,35 @@ impl Cache {
path: P,
size: u64,
modified: Timespec) -> IronResult<Response> {
use iron::headers::{CacheControl, LastModified, CacheDirective, HttpDate};
use iron::headers::{ContentLength, ContentType, ETag, EntityTag};
use iron::headers::{CACHE_CONTROL, LAST_MODIFIED, CONTENT_LENGTH, CONTENT_TYPE, ETAG};
use httpdate::HttpDate;
use iron::method::Method;
use iron::mime::{Mime, TopLevel, SubLevel};
use iron::modifiers::Header;
use iron::mime;

let seconds = self.duration.as_secs() as u32;
let cache = vec![CacheDirective::Public, CacheDirective::MaxAge(seconds)];
let metadata = fs::metadata(path.as_ref());

let metadata = try!(metadata.map_err(|e| IronError::new(e, status::InternalServerError)));
let metadata = try!(metadata.map_err(|e| IronError::new(e, StatusCode::INTERNAL_SERVER_ERROR)));

let mut response = if req.method == Method::Head {
let has_ct = req.headers.get::<ContentType>();
let mut response = if req.method == Method::HEAD {
let has_ct = req.headers.get(CONTENT_TYPE);
let cont_type = match has_ct {
None => ContentType(Mime(TopLevel::Text, SubLevel::Plain, vec![])),
None => mime::TEXT_PLAIN.as_ref().parse().unwrap(),
Some(t) => t.clone()
};
Response::with((status::Ok, Header(cont_type), Header(ContentLength(metadata.len()))))
let mut response = Response::with(StatusCode::OK);
response.headers.insert(CONTENT_TYPE, cont_type);
response.headers.insert(CONTENT_LENGTH, metadata.len().to_string().parse().unwrap());
response

} else {
Response::with((status::Ok, path.as_ref()))
Response::with((StatusCode::OK, path.as_ref()))
};

response.headers.set(CacheControl(cache));
response.headers.set(LastModified(HttpDate(time::at(modified))));
response.headers.set(ETag(EntityTag::weak(format!("{0:x}-{1:x}.{2:x}", size, modified.sec, modified.nsec))));
response.headers.insert(CACHE_CONTROL, "public".parse().unwrap());;
response.headers.insert(CACHE_CONTROL, format!("max-age={}", seconds).parse().unwrap());
response.headers.insert(LAST_MODIFIED, format!("{}",HttpDate(time::at(modified))).parse().unwrap());
response.headers.insert(ETAG, format!("{0:x}-{1:x}.{2:x}", size, modified.sec, modified.nsec).parse().unwrap());

Ok(response)
}
Expand Down