Document the http module.

This commit is contained in:
Sergio Benitez 2016-10-17 19:29:58 -07:00
parent 762b38efe9
commit 5a1a303c59
10 changed files with 356 additions and 93 deletions

View File

@ -8,6 +8,7 @@ term-painter = "^0.2"
log = "^0.3" log = "^0.3"
url = "^1" url = "^1"
toml = "^0.2" toml = "^0.2"
# cookie = "^0.3"
[dependencies.hyper] [dependencies.hyper]
git = "https://github.com/SergioBenitez/hyper" git = "https://github.com/SergioBenitez/hyper"
@ -16,6 +17,7 @@ branch = "0.9.x"
[dev-dependencies] [dev-dependencies]
lazy_static = "*" lazy_static = "*"
rocket_codegen = { path = "../codegen" }
[features] [features]
testing = [] testing = []

View File

@ -17,23 +17,23 @@ use router::Collider;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ContentType(pub TopLevel, pub SubLevel, pub Option<Vec<Param>>); pub struct ContentType(pub TopLevel, pub SubLevel, pub Option<Vec<Param>>);
macro_rules! is_some { macro_rules! ctrs {
($ct:ident, $name:ident: $top:ident/$sub:ident) => { ($($(#[$attr:meta])* | $name:ident: $top:ident/$sub:ident),+) => {
/// Returns a new ContentType that matches the MIME for this method's $($(#[$attr])*
/// name. #[inline(always)]
pub fn $ct() -> ContentType { pub fn $name() -> ContentType {
ContentType::of(TopLevel::$top, SubLevel::$sub) ContentType::of(TopLevel::$top, SubLevel::$sub)
} })+
is_some!($name: $top/$sub);
}; };
}
($name:ident: $top:ident/$sub:ident) => { macro_rules! checkers {
/// Returns true if `self` is the content type matching the method's ($($(#[$attr:meta])* | $name:ident: $top:ident/$sub:ident),+) => {
/// name. $($(#[$attr])*
pub fn $name(&self) -> bool { #[inline(always)]
self.0 == TopLevel::$top && self.1 == SubLevel::$sub pub fn $name(&self) -> bool {
} self.0 == TopLevel::$top && self.1 == SubLevel::$sub
})+
}; };
} }
@ -61,47 +61,6 @@ impl ContentType {
ContentType(t, s, None) ContentType(t, s, None)
} }
/// Returns a new ContentType for `*/*`, i.e., any.
#[inline(always)]
pub fn any() -> ContentType {
ContentType::of(TopLevel::Star, SubLevel::Star)
}
/// Returns true if this content type is not one of the standard content
/// types, that if, if it is an "extended" content type.
pub fn is_ext(&self) -> bool {
if let TopLevel::Ext(_) = self.0 {
true
} else if let SubLevel::Ext(_) = self.1 {
true
} else {
false
}
}
/// Returns true if the content type is plain text, i.e.: `text/plain`.
is_some!(is_text: Text/Plain);
/// Returns true if the content type is JSON, i.e: `application/json`.
is_some!(json, is_json: Application/Json);
/// Returns true if the content type is XML, i.e: `application/xml`.
is_some!(xml, is_xml: Application/Xml);
/// Returns true if the content type is any, i.e.: `*/*`.
is_some!(is_any: Star/Star);
/// Returns true if the content type is HTML, i.e.: `application/html`.
is_some!(html, is_html: Application/Html);
/// Returns true if the content type is that for non-data HTTP forms, i.e.:
/// `application/x-www-form-urlencoded`.
is_some!(is_form: Application/WwwFormUrlEncoded);
/// Returns true if the content type is that for data HTTP forms, i.e.:
/// `multipart/form-data`.
is_some!(is_data: Multipart/FormData);
/// Returns the Content-Type associated with the extension `ext`. Not all /// Returns the Content-Type associated with the extension `ext`. Not all
/// extensions are recognized. If an extensions is not recognized, then this /// extensions are recognized. If an extensions is not recognized, then this
/// method returns a ContentType of `any`. /// method returns a ContentType of `any`.
@ -128,7 +87,7 @@ impl ContentType {
pub fn from_extension(ext: &str) -> ContentType { pub fn from_extension(ext: &str) -> ContentType {
let (top_level, sub_level) = match ext { let (top_level, sub_level) = match ext {
"txt" => (TopLevel::Text, SubLevel::Plain), "txt" => (TopLevel::Text, SubLevel::Plain),
"html" => (TopLevel::Text, SubLevel::Html), "html" | "htm" => (TopLevel::Text, SubLevel::Html),
"xml" => (TopLevel::Application, SubLevel::Xml), "xml" => (TopLevel::Application, SubLevel::Xml),
"js" => (TopLevel::Application, SubLevel::Javascript), "js" => (TopLevel::Application, SubLevel::Javascript),
"css" => (TopLevel::Text, SubLevel::Css), "css" => (TopLevel::Text, SubLevel::Css),
@ -144,6 +103,57 @@ impl ContentType {
ContentType::of(top_level, sub_level) ContentType::of(top_level, sub_level)
} }
ctrs! {
/// Returns a `ContentType` representing `*/*`, i.e., _any_ ContentType.
| any: Star/Star,
/// Returns a `ContentType` representing JSON, i.e, `application/json`.
| json: Application/Json,
/// Returns a `ContentType` representing XML, i.e, `application/xml`.
| xml: Application/Xml,
/// Returns a `ContentType` representing HTML, i.e, `application/html`.
| html: Application/Html
}
/// Returns true if this content type is not one of the standard content
/// types, that if, if it is an "extended" content type.
pub fn is_ext(&self) -> bool {
if let TopLevel::Ext(_) = self.0 {
true
} else if let SubLevel::Ext(_) = self.1 {
true
} else {
false
}
}
checkers! {
/// Returns true if the content type is plain text, i.e.: `text/plain`.
| is_text: Text/Plain,
/// Returns true if the content type is JSON, i.e: `application/json`.
| is_json: Application/Json,
/// Returns true if the content type is XML, i.e: `application/xml`.
| is_xml: Application/Xml,
/// Returns true if the content type is any, i.e.: `*/*`.
| is_any: Star/Star,
/// Returns true if the content type is HTML, i.e.: `application/html`.
| is_html: Application/Html,
/// Returns true if the content type is that for non-data HTTP forms,
/// i.e.: `application/x-www-form-urlencoded`.
| is_form: Application/WwwFormUrlEncoded,
/// Returns true if the content type is that for data HTTP forms, i.e.:
/// `multipart/form-data`.
| is_form_data: Multipart/FormData
}
} }
impl Default for ContentType { impl Default for ContentType {

View File

@ -1,3 +1,11 @@
//! Re-exported hyper HTTP library types.
//!
//! ## Hyper
//!
//! All types that are re-exported from Hyper resides inside of this module.
//! These types will, with certainty, be removed with time, but they reside here
//! while necessary.
// TODO: Remove from Rocket in favor of a more flexible HTTP library. // TODO: Remove from Rocket in favor of a more flexible HTTP library.
pub use hyper::server::Request as HyperRequest; pub use hyper::server::Request as HyperRequest;
pub use hyper::server::Response as HyperResponse; pub use hyper::server::Response as HyperResponse;

View File

@ -5,6 +5,9 @@ use error::Error;
use http::hyper::HyperMethod; use http::hyper::HyperMethod;
use self::Method::*; use self::Method::*;
// TODO: Support non-standard methods, here and in codegen.
/// Representation of HTTP methods.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Method { pub enum Method {
Get, Get,

View File

@ -1,3 +1,10 @@
//! [unstable] Types that map to concepts in HTTP.
//!
//! This module exports types that map to HTTP concepts or to the underlying
//! HTTP library when needed. Because the underlying HTTP library is likely to
//! change (see <a
//! href="https://github.com/SergioBenitez/Rocket/issues/17">#17</a>), most of
//! this module should be considered unstable.
pub mod hyper; pub mod hyper;
pub mod uri; pub mod uri;
@ -11,4 +18,6 @@ pub use hyper::mime;
pub use self::method::Method; pub use self::method::Method;
pub use self::hyper::StatusCode; pub use self::hyper::StatusCode;
pub use self::content_type::ContentType; pub use self::content_type::ContentType;
/// Can I document it here?
pub use self::cookies::{Cookie, Cookies}; pub use self::cookies::{Cookie, Cookies};

View File

@ -3,11 +3,12 @@
use std::cell::Cell; use std::cell::Cell;
use std::convert::From; use std::convert::From;
use std::fmt::{self, Write}; use std::fmt;
use router::Collider; use router::Collider;
// TODO: Reconsider deriving PartialEq and Eq to make "//a/b" == "/a/b". // TODO: Reconsider deriving PartialEq and Eq to make "//a/b" == "/a/b".
/// Borrowed string type for absolute URIs.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct URI<'a> { pub struct URI<'a> {
uri: &'a str, uri: &'a str,
@ -18,6 +19,7 @@ pub struct URI<'a> {
} }
impl<'a> URI<'a> { impl<'a> URI<'a> {
/// Constructs a new URI from a given string.
pub fn new<T: AsRef<str> + ?Sized>(uri: &'a T) -> URI<'a> { pub fn new<T: AsRef<str> + ?Sized>(uri: &'a T) -> URI<'a> {
let uri = uri.as_ref(); let uri = uri.as_ref();
@ -41,6 +43,31 @@ impl<'a> URI<'a> {
} }
} }
/// Returns the number of segments in the URI. Empty segments, which are
/// invalid according to RFC#3986, are not counted.
///
/// The segment count is cached after the first invocation. As a result,
/// this function is O(1) after the first invocation, and O(n) before.
///
/// ### Examples
///
/// A valid URI with only non-empty segments:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a/b/c");
/// assert_eq!(uri.segment_count(), 3);
/// ```
///
/// A URI with empty segments:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a/b//c/d///e");
/// assert_eq!(uri.segment_count(), 5);
/// ```
#[inline(always)] #[inline(always)]
pub fn segment_count(&self) -> usize { pub fn segment_count(&self) -> usize {
self.segment_count.get().unwrap_or_else(|| { self.segment_count.get().unwrap_or_else(|| {
@ -50,21 +77,114 @@ impl<'a> URI<'a> {
}) })
} }
/// Returns an iterator over the segments of this URI. Skips empty segments.
///
/// ### Examples
///
/// A valid URI with only non-empty segments:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a/b/c?a=true#done");
/// for (i, segment) in uri.segments().enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 1 => assert_eq!(segment, "b"),
/// 2 => assert_eq!(segment, "c"),
/// _ => panic!("only three segments")
/// }
/// }
/// ```
///
/// A URI with empty segments:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("///a//b///c////d?#");
/// for (i, segment) in uri.segments().enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 1 => assert_eq!(segment, "b"),
/// 2 => assert_eq!(segment, "c"),
/// 3 => assert_eq!(segment, "d"),
/// _ => panic!("only four segments")
/// }
/// }
/// ```
#[inline(always)] #[inline(always)]
pub fn segments(&self) -> Segments<'a> { pub fn segments(&self) -> Segments<'a> {
Segments(self.path) Segments(self.path)
} }
/// Returns the query part of this URI without the question mark, if there is
/// any.
///
/// ### Examples
///
/// A URI with a query part:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a/b/c?alphabet=true");
/// assert_eq!(uri.query(), Some("alphabet=true"));
/// ```
///
/// A URI without the query part:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a/b/c");
/// assert_eq!(uri.query(), None);
/// ```
#[inline(always)] #[inline(always)]
pub fn query(&self) -> Option<&'a str> { pub fn query(&self) -> Option<&'a str> {
self.query self.query
} }
/// Returns the fargment part of this URI without the hash mark, if there is
/// any.
///
/// ### Examples
///
/// A URI with a fragment part:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a?alphabet=true#end");
/// assert_eq!(uri.fragment(), Some("end"));
/// ```
///
/// A URI without the fragment part:
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a?query=true");
/// assert_eq!(uri.fragment(), None);
/// ```
#[inline(always)] #[inline(always)]
pub fn fragment(&self) -> Option<&'a str> { pub fn fragment(&self) -> Option<&'a str> {
self.fragment self.fragment
} }
/// Returns the inner string of this URI.
///
/// The returned string is in raw form. It contains empty segments. If you'd
/// like a string without empty segments, use `to_string` instead.
///
/// ### Example
///
/// ```rust
/// use rocket::http::uri::URI;
///
/// let uri = URI::new("/a/b///c/d/e//f?name=Mike#end");
/// assert_eq!(uri.as_str(), "/a/b///c/d/e//f?name=Mike#end");
/// ```
#[inline(always)] #[inline(always)]
pub fn as_str(&self) -> &'a str { pub fn as_str(&self) -> &'a str {
self.uri self.uri
@ -73,13 +193,16 @@ impl<'a> URI<'a> {
impl<'a> fmt::Display for URI<'a> { impl<'a> fmt::Display for URI<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut last = '\0'; for segment in self.segments() {
for c in self.uri.chars() { write!(f, "/{}", segment)?;
if !(c == '/' && last == '/') { }
f.write_char(c)?;
}
last = c; if let Some(query_str) = self.query {
write!(f, "?{}", query_str)?;
}
if let Some(fragment_str) = self.fragment {
write!(f, "#{}", fragment_str)?;
} }
Ok(()) Ok(())
@ -89,54 +212,116 @@ impl<'a> fmt::Display for URI<'a> {
unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ } unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
/// Owned string type for absolute URIs.
///
/// This is the owned analog to [URI](struct.URI.html). It serves simply to hold
/// the backing String. As a result, most functionality will be achieved through
/// the [as_uri](#method.as_uri) method.
///
/// The exception to this is the [segment_count](#method.segment_count) method,
/// which is provided here for performance reasons. This method uses a cached
/// count of the segments on subsequent calls. To avoid computing the segment
/// count again and again, use the `segment_count` method on URIBuf directly.
///
/// ## Constructing
///
/// A URIBuf can be created with either a borrowed or owned string via the
/// [new](#method.new) or `from` methods.
pub struct URIBuf { pub struct URIBuf {
uri: String, uri: String,
segment_count: Cell<Option<usize>>, segment_count: Cell<Option<usize>>,
} }
// I don't like repeating all of this stuff. Is there a better way?
impl URIBuf { impl URIBuf {
/// Construct a new URIBuf.
///
/// # Examples
///
/// From a borrowed string:
///
/// ```rust
/// use rocket::http::uri::URIBuf;
///
/// let uri = URIBuf::new("/a/b/c");
/// assert_eq!(uri.as_uri().as_str(), "/a/b/c");
/// ```
///
/// From an owned string:
///
/// ```rust
/// use rocket::http::uri::URIBuf;
///
/// let uri = URIBuf::new("/a/b/c".to_string());
/// assert_eq!(uri.as_str(), "/a/b/c");
/// ```
#[inline(always)]
pub fn new<S: Into<URIBuf>>(s: S) -> URIBuf {
s.into()
}
/// Returns the number of segments in the URI. Empty segments, which are
/// invalid according to RFC#3986, are not counted.
///
/// The segment count is cached after the first invocation. As a result,
/// this function is O(1) after the first invocation, and O(n) before.
///
/// ### Examples
///
/// A valid URI with only non-empty segments:
///
/// ```rust
/// use rocket::http::uri::URIBuf;
///
/// let uri = URIBuf::new("/a/b/c");
/// assert_eq!(uri.segment_count(), 3);
/// ```
///
/// A URI with empty segments:
///
/// ```rust
/// use rocket::http::uri::URIBuf;
///
/// let uri = URIBuf::new("/a/b//c/d///e");
/// assert_eq!(uri.segment_count(), 5);
/// ```
pub fn segment_count(&self) -> usize { pub fn segment_count(&self) -> usize {
self.segment_count.get().unwrap_or_else(|| { self.segment_count.get().unwrap_or_else(|| {
let count = self.segments().count(); let count = self.as_uri().segments().count();
self.segment_count.set(Some(count)); self.segment_count.set(Some(count));
count count
}) })
} }
/// Converts this URIBuf into a borrowed URI. Does not consume this URIBuf.
#[inline(always)] #[inline(always)]
pub fn segments(&self) -> Segments { pub fn as_uri(&self) -> URI {
self.as_uri_uncached().segments()
}
#[inline(always)]
fn as_uri_uncached(&self) -> URI {
URI::new(self.uri.as_str()) URI::new(self.uri.as_str())
} }
/// Returns the inner string of this URIBuf.
///
/// The returned string is in raw form. It contains empty segments. If you'd
/// like a string without empty segments, use `to_string` instead.
///
/// ### Example
///
/// ```rust
/// use rocket::http::uri::URIBuf;
///
/// let uri = URIBuf::new("/a/b///c/d/e//f?name=Mike#end");
/// assert_eq!(uri.as_str(), "/a/b///c/d/e//f?name=Mike#end");
/// ```
#[inline(always)] #[inline(always)]
pub fn as_uri(&self) -> URI { pub fn as_str<'a>(&'a self) -> &'a str {
let mut uri = URI::new(self.uri.as_str());
uri.segment_count = self.segment_count.clone();
uri
}
#[inline(always)]
pub fn as_str(&self) -> &str {
self.uri.as_str() self.uri.as_str()
} }
#[inline(always)]
pub fn to_string(&self) -> String {
self.uri.clone()
}
} }
unsafe impl Sync for URIBuf { /* It's safe! */ } unsafe impl Sync for URIBuf { /* It's safe! */ }
impl fmt::Display for URIBuf { impl fmt::Display for URIBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_uri_uncached().fmt(f) self.as_uri().fmt(f)
} }
} }
@ -180,6 +365,25 @@ impl<'a, 'b> Collider<URI<'b>> for URI<'a> {
} }
} }
/// Iterator over the segments of an absolute URI path. Skips empty segments.
///
/// ### Examples
///
/// ```rust
/// use rocket::http::uri::URI;
/// use rocket::http::uri::Segments;
///
/// let segments: Segments = URI::new("/a/////b/c////////d").segments();
/// for (i, segment) in segments.enumerate() {
/// match i {
/// 0 => assert_eq!(segment, "a"),
/// 1 => assert_eq!(segment, "b"),
/// 2 => assert_eq!(segment, "c"),
/// 3 => assert_eq!(segment, "d"),
/// _ => panic!("only four segments")
/// }
/// }
/// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Segments<'a>(&'a str); pub struct Segments<'a>(&'a str);
@ -238,7 +442,7 @@ mod tests {
let actual: Vec<&str> = uri.segments().collect(); let actual: Vec<&str> = uri.segments().collect();
let uri_buf = URIBuf::from(path); let uri_buf = URIBuf::from(path);
let actual_buf: Vec<&str> = uri_buf.segments().collect(); let actual_buf: Vec<&str> = uri_buf.as_uri().segments().collect();
actual == expected && actual_buf == expected actual == expected && actual_buf == expected
} }

View File

@ -45,7 +45,7 @@
//! //!
//! Then, add the following to top of your `main.rs` file: //! Then, add the following to top of your `main.rs` file:
//! //!
//! ```rust,ignore //! ```rust
//! #![feature(plugin)] //! #![feature(plugin)]
//! #![plugin(rocket_codegen)] //! #![plugin(rocket_codegen)]
//! //!
@ -53,11 +53,38 @@
//! ``` //! ```
//! //!
//! See the [guide](https://guide.rocket.rs) for more information on how to //! See the [guide](https://guide.rocket.rs) for more information on how to
//! write Rocket applications. //! write Rocket applications. Here's a simple example to get you started:
//!
//! ```rust
//! #![feature(plugin)]
//! #![plugin(rocket_codegen)]
//!
//! extern crate rocket;
//!
//! #[get("/")]
//! fn hello() -> &'static str {
//! "Hello, world!"
//! }
//!
//! fn main() {
//! # if false { // We don't actually want to launch the server in an example.
//! rocket::ignite().mount("/", routes![hello]).launch()
//! # }
//! }
//! ```
//! //!
//! ## Configuration //! ## Configuration
//! //!
//! Rocket is configured via the `Rocket.toml` file. //! Rocket and Rocket libraries are configured via the `Rocket.toml` file. For
//! more information on how to configure Rocket, see the [configuration
//! section](/guide/configuration) of the guide as well as the [config](config)
//! module documentation.
//!
//! ## Testing
//!
//! Rocket includes a small testing library that can be used to test your Rocket
//! application. The library's API is unstable. For information on how to test
//! your Rocket applications, the [testing module](testing) documentation.
//! //!
#[macro_use] extern crate log; #[macro_use] extern crate log;

View File

@ -88,7 +88,7 @@ impl Request {
} else { } else {
// TODO: Really want to do self.uri.segments().skip(i).into_inner(), // TODO: Really want to do self.uri.segments().skip(i).into_inner(),
// but the std lib doesn't implement it for Skip. // but the std lib doesn't implement it for Skip.
let mut segments = self.uri.segments(); let mut segments = self.uri.as_uri().segments();
for _ in segments.by_ref().take(i) { /* do nothing */ } for _ in segments.by_ref().take(i) { /* do nothing */ }
T::from_segments(segments).map_err(|_| Error::BadParse) T::from_segments(segments).map_err(|_| Error::BadParse)

View File

@ -175,7 +175,7 @@ impl Rocket {
pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self { pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self {
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base); info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
for mut route in routes { for mut route in routes {
let path = format!("{}/{}", base, route.path.as_str()); let path = format!("{}/{}", base, route.path.as_uri());
route.set_path(path); route.set_path(path);
info_!("{}", route); info_!("{}", route);

View File

@ -61,7 +61,7 @@ impl Route {
// is, whether you can have: /a<a>b/ or even /<a>:<b>/ // is, whether you can have: /a<a>b/ or even /<a>:<b>/
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!) // TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
pub fn get_params<'a>(&self, uri: URI<'a>) -> Vec<&'a str> { pub fn get_params<'a>(&self, uri: URI<'a>) -> Vec<&'a str> {
let route_segs = self.path.segments(); let route_segs = self.path.as_uri().segments();
let uri_segs = uri.segments(); let uri_segs = uri.segments();
let mut result = Vec::with_capacity(self.path.segment_count()); let mut result = Vec::with_capacity(self.path.segment_count());