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

View File

@ -17,23 +17,23 @@ use router::Collider;
#[derive(Debug, Clone, PartialEq)]
pub struct ContentType(pub TopLevel, pub SubLevel, pub Option<Vec<Param>>);
macro_rules! is_some {
($ct:ident, $name:ident: $top:ident/$sub:ident) => {
/// Returns a new ContentType that matches the MIME for this method's
/// name.
pub fn $ct() -> ContentType {
macro_rules! ctrs {
($($(#[$attr:meta])* | $name:ident: $top:ident/$sub:ident),+) => {
$($(#[$attr])*
#[inline(always)]
pub fn $name() -> ContentType {
ContentType::of(TopLevel::$top, SubLevel::$sub)
})+
};
}
is_some!($name: $top/$sub);
};
($name:ident: $top:ident/$sub:ident) => {
/// Returns true if `self` is the content type matching the method's
/// name.
macro_rules! checkers {
($($(#[$attr:meta])* | $name:ident: $top:ident/$sub:ident),+) => {
$($(#[$attr])*
#[inline(always)]
pub fn $name(&self) -> bool {
self.0 == TopLevel::$top && self.1 == SubLevel::$sub
}
})+
};
}
@ -61,47 +61,6 @@ impl ContentType {
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
/// extensions are recognized. If an extensions is not recognized, then this
/// method returns a ContentType of `any`.
@ -128,7 +87,7 @@ impl ContentType {
pub fn from_extension(ext: &str) -> ContentType {
let (top_level, sub_level) = match ext {
"txt" => (TopLevel::Text, SubLevel::Plain),
"html" => (TopLevel::Text, SubLevel::Html),
"html" | "htm" => (TopLevel::Text, SubLevel::Html),
"xml" => (TopLevel::Application, SubLevel::Xml),
"js" => (TopLevel::Application, SubLevel::Javascript),
"css" => (TopLevel::Text, SubLevel::Css),
@ -144,6 +103,57 @@ impl ContentType {
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 {

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.
pub use hyper::server::Request as HyperRequest;
pub use hyper::server::Response as HyperResponse;

View File

@ -5,6 +5,9 @@ use error::Error;
use http::hyper::HyperMethod;
use self::Method::*;
// TODO: Support non-standard methods, here and in codegen.
/// Representation of HTTP methods.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Method {
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 uri;
@ -11,4 +18,6 @@ pub use hyper::mime;
pub use self::method::Method;
pub use self::hyper::StatusCode;
pub use self::content_type::ContentType;
/// Can I document it here?
pub use self::cookies::{Cookie, Cookies};

View File

@ -3,11 +3,12 @@
use std::cell::Cell;
use std::convert::From;
use std::fmt::{self, Write};
use std::fmt;
use router::Collider;
// TODO: Reconsider deriving PartialEq and Eq to make "//a/b" == "/a/b".
/// Borrowed string type for absolute URIs.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct URI<'a> {
uri: &'a str,
@ -18,6 +19,7 @@ pub struct 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> {
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)]
pub fn segment_count(&self) -> usize {
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)]
pub fn segments(&self) -> Segments<'a> {
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)]
pub fn query(&self) -> Option<&'a str> {
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)]
pub fn fragment(&self) -> Option<&'a str> {
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)]
pub fn as_str(&self) -> &'a str {
self.uri
@ -73,13 +193,16 @@ impl<'a> URI<'a> {
impl<'a> fmt::Display for URI<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut last = '\0';
for c in self.uri.chars() {
if !(c == '/' && last == '/') {
f.write_char(c)?;
for segment in self.segments() {
write!(f, "/{}", segment)?;
}
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(())
@ -89,54 +212,116 @@ impl<'a> fmt::Display for URI<'a> {
unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ }
#[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 {
uri: String,
segment_count: Cell<Option<usize>>,
}
// I don't like repeating all of this stuff. Is there a better way?
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 {
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));
count
})
}
/// Converts this URIBuf into a borrowed URI. Does not consume this URIBuf.
#[inline(always)]
pub fn segments(&self) -> Segments {
self.as_uri_uncached().segments()
}
#[inline(always)]
fn as_uri_uncached(&self) -> URI {
pub fn as_uri(&self) -> URI {
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)]
pub fn as_uri(&self) -> URI {
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 {
pub fn as_str<'a>(&'a self) -> &'a str {
self.uri.as_str()
}
#[inline(always)]
pub fn to_string(&self) -> String {
self.uri.clone()
}
}
unsafe impl Sync for URIBuf { /* It's safe! */ }
impl fmt::Display for URIBuf {
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)]
pub struct Segments<'a>(&'a str);
@ -238,7 +442,7 @@ mod tests {
let actual: Vec<&str> = uri.segments().collect();
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
}

View File

@ -45,7 +45,7 @@
//!
//! Then, add the following to top of your `main.rs` file:
//!
//! ```rust,ignore
//! ```rust
//! #![feature(plugin)]
//! #![plugin(rocket_codegen)]
//!
@ -53,11 +53,38 @@
//! ```
//!
//! 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
//!
//! 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;

View File

@ -88,7 +88,7 @@ impl Request {
} else {
// TODO: Really want to do self.uri.segments().skip(i).into_inner(),
// 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 */ }
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 {
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
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);
info_!("{}", route);

View File

@ -61,7 +61,7 @@ impl Route {
// 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!)
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 mut result = Vec::with_capacity(self.path.segment_count());