diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..b088f40e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: rust + +after_success: | + [ $TRAVIS_BRANCH = master ] && + [ $TRAVIS_PULL_REQUEST = false ] && + cargo doc && + echo '' > target/doc/index.html && + sudo pip install ghp-import && + ghp-import -n target/doc && + git push -fq https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages + +env: + global: + - secure: kHuPGsSt14Y7TTy+4NeNMQ4yhENXm38OM26G0ZER870QVOQH8cBZk9a9jgA36F8CGkGAMkFJ5lQw5RginQX01zaCev765XqCF8VvToXq9n/Vg8+oxR5LepC1ybY06yd7AuW/znB6cnQ8BB8HJK5FvZJ1PqH+yubzyyada8c/sVQ= diff --git a/README.md b/README.md index 8efa69cc..3c9987fd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # hyper +[![Build Status](https://travis-ci.org/seanmonstar/hyper.svg?branch=master)](https://travis-ci.org/seanmonstar/hyper) + An HTTP library for Rust. ## License diff --git a/examples/client.rs b/examples/client.rs new file mode 100644 index 00000000..f436eacb --- /dev/null +++ b/examples/client.rs @@ -0,0 +1,41 @@ +extern crate hyper; + +use std::os; +use std::io::stdout; +use std::io::util::copy; + +use hyper::Url; + +fn main() { + let args = os::args(); + match args.len() { + 2 => (), + _ => { + println!("Usage: client "); + return; + } + }; + + let url = match Url::parse(args[1].as_slice()) { + Ok(url) => { + println!("GET {}...", url) + url + }, + Err(e) => fail!("Invalid URL: {}", e) + }; + + + let req = match hyper::get(url) { + Ok(req) => req, + Err(err) => fail!("Failed to connect: {}", err) + }; + let mut res = req.send().unwrap(); + + println!("Response: {}", res.status); + println!("{}", res.headers); + match copy(&mut res, &mut stdout()) { + Ok(..) => (), + Err(e) => fail!("Stream failure: {}", e) + }; + +} diff --git a/examples/server.rs b/examples/server.rs new file mode 100644 index 00000000..b24be5b1 --- /dev/null +++ b/examples/server.rs @@ -0,0 +1,40 @@ +extern crate hyper; +extern crate debug; + +use std::io::{IoResult}; +use std::io::util::copy; +use std::io::net::ip::Ipv4Addr; + +use hyper::method::{Get, Post}; +use hyper::server::{Server, Handler, Request, Response}; + +struct Echo; + +impl Handler for Echo { + fn handle(&mut self, mut req: Request, mut res: Response) -> IoResult<()> { + match &req.uri { + &hyper::uri::AbsolutePath(ref path) => match (&req.method, path.as_slice()) { + (&Get, "/") | (&Get, "/echo") => { + try!(res.write_str("Try POST /echo")); + return res.end(); + }, + (&Post, "/echo") => (), // fall through, fighting mutable borrows + _ => { + res.status = hyper::status::NotFound; + return res.end(); + } + }, + _ => return res.end() + }; + + println!("copying..."); + try!(copy(&mut req, &mut res)); + println!("copied..."); + res.end() + } +} + +fn main() { + let server = Server::http(Ipv4Addr(127, 0, 0, 1), 1337); + server.listen(Echo); +} diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 00000000..e51306a3 --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,22 @@ +//! # Client +use url::Url; + +use method::{Get, Method}; + +pub use self::request::Request; +pub use self::response::Response; +use {HttpResult}; + +pub mod request; +pub mod response; + + +/// Create a GET client request. +pub fn get(url: Url) -> HttpResult { + request(Get, url) +} + +/// Create a client request. +pub fn request(method: Method, url: Url) -> HttpResult { + Request::new(method, url) +} diff --git a/src/client/request.rs b/src/client/request.rs new file mode 100644 index 00000000..621e079d --- /dev/null +++ b/src/client/request.rs @@ -0,0 +1,106 @@ +//! # Client Requests +use std::io::net::tcp::TcpStream; +use std::io::IoResult; + +use url::Url; + +use method; +use header::{Headers, Host}; +use rfc7230::LINE_ENDING; +use version; +use {HttpResult, HttpUriError}; +use super::{Response}; + + +/// A client request to a remote server. +pub struct Request { + /// The method of this request. + pub method: method::Method, + /// The headers that will be sent with this request. + pub headers: Headers, + /// The target URI for this request. + pub url: Url, + /// The HTTP version of this request. + pub version: version::HttpVersion, + headers_written: bool, + body: TcpStream, +} + +impl Request { + + /// Create a new client request. + pub fn new(method: method::Method, url: Url) -> HttpResult { + debug!("{} {}", method, url); + let host = match url.serialize_host() { + Some(host) => host, + None => return Err(HttpUriError) + }; + debug!("host={}", host); + let port = match url.port_or_default() { + Some(port) => port, + None => return Err(HttpUriError) + }; + debug!("port={}", port); + + let stream = try_io!(TcpStream::connect(host.as_slice(), port)); + let mut headers = Headers::new(); + headers.set(Host(host)); + Ok(Request { + method: method, + headers: headers, + url: url, + version: version::Http11, + headers_written: false, + body: stream + }) + } + + fn write_head(&mut self) -> IoResult<()> { + if self.headers_written { + debug!("headers previsouly written, nooping"); + return Ok(()); + } + self.headers_written = true; + + let uri = self.url.serialize_path().unwrap(); + debug!("writing head: {} {} {}", self.method, uri, self.version); + try!(write!(self.body, "{} {} {}", self.method, uri, self.version)) + try!(self.body.write(LINE_ENDING)); + + debug!("{}", self.headers); + + for (name, header) in self.headers.iter() { + try!(write!(self.body, "{}: {}", name, header)); + try!(self.body.write(LINE_ENDING)); + } + + self.body.write(LINE_ENDING) + } + + /// Completes writing the request, and returns a response to read from. + /// + /// Consumes the Request. + pub fn send(mut self) -> HttpResult { + try_io!(self.flush()); + try_io!(self.body.close_write()); + Response::new(self.body) + } +} + + +impl Writer for Request { + fn write(&mut self, msg: &[u8]) -> IoResult<()> { + if !self.headers_written { + try!(self.write_head()); + } + self.body.write(msg) + } + + fn flush(&mut self) -> IoResult<()> { + if !self.headers_written { + try!(self.write_head()); + } + self.body.flush() + } +} + diff --git a/src/client/response.rs b/src/client/response.rs new file mode 100644 index 00000000..92bf2068 --- /dev/null +++ b/src/client/response.rs @@ -0,0 +1,71 @@ +//! # Client Responses +use std::io::{Reader, IoResult}; +use std::io::net::tcp::TcpStream; + +use header::{mod, ContentLength, TransferEncoding, Chunked}; +use rfc7230::{read_status_line, HttpReader, SizedReader, ChunkedReader, EofReader}; +use status; +use version; +use {HttpResult}; + +/// A response for a client request to a remote server. +pub struct Response { + /// The status from the server. + pub status: status::StatusCode, + /// The headers from the server. + pub headers: header::Headers, + /// The HTTP version of this response from the server. + pub version: version::HttpVersion, + body: HttpReader, +} + +impl Response { + + /// Creates a new response from a server. + pub fn new(mut tcp: TcpStream) -> HttpResult { + let (version, status) = try!(read_status_line(&mut tcp)); + let mut headers = try!(header::Headers::from_raw(&mut tcp)); + + debug!("{} {}", version, status); + debug!("{}", headers); + + let body = if headers.has::() { + match headers.get_ref::() { + Some(&TransferEncoding(ref codings)) => { + if codings.len() > 1 { + debug!("TODO: handle other codings: {}", codings); + }; + + if codings.contains(&Chunked) { + ChunkedReader(tcp, None) + } else { + debug!("not chucked. read till eof"); + EofReader(tcp) + } + } + None => unreachable!() + } + } else if headers.has::() { + match headers.get_ref::() { + Some(&ContentLength(len)) => SizedReader(tcp, len), + None => unreachable!() + } + } else { + debug!("neither Transfer-Encoding nor Content-Length"); + EofReader(tcp) + }; + + Ok(Response { + status: status, + version: version, + headers: headers, + body: body, + }) + } +} + +impl Reader for Response { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.body.read(buf) + } +} diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 00000000..1e34c507 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,532 @@ +//! # Headers +//! +//! hyper has the opinion that Headers should be strongly-typed, because that's +//! why we're using Rust in the first place. To set or get any header, an object +//! must implement the `Header` trait from this module. Several common headers +//! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. +//! +//! ## Mime +//! +//! Several header fields use MIME values for their contents. Keeping with the +//! strongly-typed theme, the [mime](http://seanmonstar.github.io/mime.rs) crate +//! is used, such as `ContentType(pub Mime)`. +use std::any::Any; +use std::ascii::OwnedAsciiExt; +use std::char::is_lowercase; +use std::fmt::{mod, Show}; +use std::from_str::{FromStr, from_str}; +use std::mem::{transmute, transmute_copy}; +use std::raw::TraitObject; +use std::str::{from_utf8, SendStr, Slice}; +use std::string::raw; +use std::collections::hashmap::{HashMap, Entries}; + +use mime::Mime; + +use rfc7230::read_header; +use {HttpResult}; + +/// A trait for any object that will represent a header field and value. +pub trait Header: Any { + /// Returns the name of the header field this belongs to. + fn header_name(marker: Option) -> SendStr; + /// Parse a header from a raw stream of bytes. + /// + /// It's possible that a request can include a header field more than once, + /// and in that case, the slice will have a length greater than 1. However, + /// it's not necessarily the case that a Header is *allowed* to have more + /// than one field value. If that's the case, you **should** return `None` + /// if `raw.len() > 1`. + fn parse_header(raw: &[Vec]) -> Option; + /// Format a header to be output into a TcpStream. + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result; +} + +/// A trait for downcasting owned TraitObjects to another type without checking +/// `AnyRefExt::is::(t)` first. +trait UncheckedAnyRefExt<'a> { + /// This will downcast an object to another type without checking that it is + /// legal. Do not call this unless you are ABSOLUTE SURE of the types. + unsafe fn downcast_ref_unchecked(self) -> &'a T; +} + +impl<'a> UncheckedAnyRefExt<'a> for &'a Header { + #[inline] + unsafe fn downcast_ref_unchecked(self) -> &'a T { + let to: TraitObject = transmute_copy(&self); + transmute(to.data) + } +} + +fn header_name() -> SendStr { + let name = Header::header_name(None::); + debug_assert!(name.as_slice().chars().all(|c| c == '-' || is_lowercase(c)), + "Header names should be lowercase: {}", name); + name +} + +/// A map of header fields on requests and responses. +pub struct Headers { + data: HashMap +} + +impl Headers { + + /// Creates a new, empty headers map. + pub fn new() -> Headers { + Headers { + data: HashMap::new() + } + } + + #[doc(hidden)] + pub fn from_raw(rdr: &mut R) -> HttpResult { + let mut headers = Headers::new(); + loop { + match try!(read_header(rdr)) { + Some((name, value)) => { + // read_header already checks that name is a token, which + // means its safe utf8 + let name = unsafe { + raw::from_utf8(name) + }.into_ascii_lower().into_maybe_owned(); + match headers.data.find_or_insert(name, Raw(vec![])) { + &Raw(ref mut pieces) => pieces.push(value), + // at this point, Raw is the only thing that has been inserted + _ => unreachable!() + } + }, + None => break, + } + } + Ok(headers) + } + + /// Set a header field to the corresponding value. + /// + /// The field is determined by the type of the value being set. + pub fn set(&mut self, value: H) { + self.data.insert(header_name::(), Typed(box value)); + } + + /// Get a clone of the header field's value, if it exists. + /// + /// Example: + /// + /// ``` + /// # use hyper::header::{Headers, ContentType}; + /// # let mut headers = Headers::new(); + /// let content_type = headers.get::(); + /// ``` + pub fn get(&mut self) -> Option { + self.get_ref().map(|v: &H| v.clone()) + } + + /// Get a reference to the header field's value, if it exists. + pub fn get_ref(&mut self) -> Option<&H> { + self.data.find_mut(&header_name::()).and_then(|item| { + debug!("get_ref, name={}, val={}", header_name::(), item); + let header = match *item { + Raw(ref raw) => match Header::parse_header(raw.as_slice()) { + Some::(h) => { + h + }, + None => return None + }, + Typed(..) => return Some(item) + }; + *item = Typed(box header as Box
); + Some(item) + }).and_then(|item| { + debug!("downcasting {}", item); + let ret = match *item { + Typed(ref val) => { + unsafe { + Some(val.downcast_ref_unchecked()) + } + }, + Raw(..) => unreachable!() + }; + debug!("returning {}", ret.is_some()); + ret + }) + } + + /// Returns a boolean of whether a certain header is in the map. + /// + /// Example: + /// + /// ``` + /// # use hyper::header::{Headers, ContentType}; + /// # let mut headers = Headers::new(); + /// let has_type = headers.has::(); + /// ``` + pub fn has(&self) -> bool { + self.data.contains_key(&header_name::()) + } + + /// Removes a header from the map, if one existed. Returns true if a header has been removed. + pub fn remove(&mut self) -> bool { + self.data.remove(&Header::header_name(None::)) + } + + /// Returns an iterator over the header fields. + pub fn iter<'a>(&'a self) -> HeadersItems<'a> { + HeadersItems { + inner: self.data.iter() + } + } +} + +impl fmt::Show for Headers { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + try!("Headers {\n".fmt(fmt)); + for (k, v) in self.iter() { + try!(write!(fmt, "\t{}: {}\n", k, v)); + } + "}".fmt(fmt) + } +} + +/// An `Iterator` over the fields in a `Headers` map. +pub struct HeadersItems<'a> { + inner: Entries<'a, SendStr, Item> +} + +impl<'a> Iterator<(&'a SendStr, HeaderView<'a>)> for HeadersItems<'a> { + fn next(&mut self) -> Option<(&'a SendStr, HeaderView<'a>)> { + match self.inner.next() { + Some((k, v)) => Some((k, HeaderView(v))), + None => None + } + } +} + +/// Returned with the `HeadersItems` iterator. +pub struct HeaderView<'a>(&'a Item); + +impl<'a> fmt::Show for HeaderView<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let HeaderView(item) = *self; + item.fmt(fmt) + } +} + +impl Collection for Headers { + fn len(&self) -> uint { + self.data.len() + } +} + +impl Mutable for Headers { + fn clear(&mut self) { + self.data.clear() + } +} + +enum Item { + Raw(Vec>), + Typed(Box
) +} + +impl fmt::Show for Item { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Raw(ref v) => { + for part in v.iter() { + try!(fmt.write(part.as_slice())); + } + Ok(()) + }, + Typed(ref h) => h.fmt_header(fmt) + } + } +} + + +// common headers + +/// The `Host` header. +/// +/// HTTP/1.1 requires that all requests include a `Host` header, and so hyper +/// client requests add one automatically. +/// +/// Currently is just a String, but it should probably become a better type, +/// like url::Host or something. +#[deriving(Clone, PartialEq, Show)] +pub struct Host(pub String); + +impl Header for Host { + fn header_name(_: Option) -> SendStr { + Slice("host") + } + + fn parse_header(raw: &[Vec]) -> Option { + from_one_raw_str(raw).map(|s| Host(s)) + } + + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let Host(ref value) = *self; + value.fmt(fmt) + } +} + +/// The `Content-Length` header. +/// +/// Simply a wrapper around a `uint`. +#[deriving(Clone, PartialEq, Show)] +pub struct ContentLength(pub uint); + +impl Header for ContentLength { + fn header_name(_: Option) -> SendStr { + Slice("content-length") + } + + fn parse_header(raw: &[Vec]) -> Option { + from_one_raw_str(raw).map(|u| ContentLength(u)) + } + + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let ContentLength(ref value) = *self; + value.fmt(fmt) + } +} + +/// The `Content-Type` header. +/// +/// Used to describe the MIME type of message body. Can be used with both +/// requests and responses. +#[deriving(Clone, PartialEq, Show)] +pub struct ContentType(pub Mime); + +impl Header for ContentType { + fn header_name(_: Option) -> SendStr { + Slice("content-type") + } + + fn parse_header(raw: &[Vec]) -> Option { + from_one_raw_str(raw).map(|mime| ContentType(mime)) + } + + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let ContentType(ref value) = *self; + value.fmt(fmt) + } +} + +/// The `Accept` header. +/// +/// The `Accept` header is used to tell a server which content-types the client +/// is capable of using. It can be a comma-separated list of `Mime`s, and the +/// priority can be indicated with a `q` parameter. +/// +/// Example: +/// +/// ``` +/// # use hyper::header::{Headers, Accept}; +/// use hyper::mime::{Mime, Text, Html, Xml}; +/// # let mut headers = Headers::new(); +/// headers.set(Accept(vec![ Mime(Text, Html, vec![]), Mime(Text, Xml, vec![]) ])); +/// ``` +#[deriving(Clone, PartialEq, Show)] +pub struct Accept(pub Vec); + +impl Header for Accept { + fn header_name(_: Option) -> SendStr { + Slice("accept") + } + + fn parse_header(_raw: &[Vec]) -> Option { + unimplemented!() + } + + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let Accept(ref value) = *self; + let last = value.len() - 1; + for (i, mime) in value.iter().enumerate() { + try!(mime.fmt(fmt)); + if i < last { + try!(", ".fmt(fmt)); + } + } + Ok(()) + } +} + +/// The `Connection` header. +/// +/// Describes whether the socket connection should be closed or reused after +/// this request/response is completed. +#[deriving(Clone, PartialEq, Show)] +pub enum Connection { + /// The `keep-alive` connection value. + KeepAlive, + /// The `close` connection value. + Close +} + +impl FromStr for Connection { + fn from_str(s: &str) -> Option { + debug!("Connection::from_str =? {}", s); + match s { + "keep-alive" => Some(KeepAlive), + "close" => Some(Close), + _ => None + } + } +} + +impl Header for Connection { + fn header_name(_: Option) -> SendStr { + Slice("connection") + } + + fn parse_header(raw: &[Vec]) -> Option { + from_one_raw_str(raw) + } + + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + KeepAlive => "keep-alive", + Close => "close", + }.fmt(fmt) + } +} + +/// The `Transfer-Encoding` header. +/// +/// This header describes the encoding of the message body. It can be +/// comma-separated, including multiple encodings. +/// +/// ```notrust +/// Transfer-Encoding: gzip, chunked +/// ``` +/// +/// According to the spec, if a `Content-Length` header is not included, +/// this header should include `chunked` as the last encoding. +/// +/// The implementation uses a vector of `Encoding` values. +#[deriving(Clone, PartialEq, Show)] +pub struct TransferEncoding(pub Vec); + +/// A value to be used with the `Transfer-Encoding` header. +/// +/// Example: +/// +/// ``` +/// # use hyper::header::{Headers, TransferEncoding, Gzip, Chunked}; +/// # let mut headers = Headers::new(); +/// headers.set(TransferEncoding(vec![Gzip, Chunked])); +#[deriving(Clone, PartialEq, Show)] +pub enum Encoding { + /// The `chunked` encoding. + Chunked, + + // TODO: implement this in `HttpReader`. + /// The `gzip` encoding. + Gzip, + /// The `deflate` encoding. + Deflate, + /// The `compress` encoding. + Compress, + /// Some other encoding that is less common, can be any String. + EncodingExt(String) +} + +impl FromStr for Encoding { + fn from_str(s: &str) -> Option { + match s { + "chunked" => Some(Chunked), + _ => None + } + } +} + +impl Header for TransferEncoding { + fn header_name(_: Option) -> SendStr { + Slice("transfer-encoding") + } + + fn parse_header(raw: &[Vec]) -> Option { + if raw.len() != 1 { + return None; + } + // we JUST checked that raw.len() == 1, so raw[0] WILL exist. + match from_utf8(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) { + Some(s) => { + Some(TransferEncoding(s.as_slice() + .split(&[',', ' ']) + .filter_map(from_str) + .collect())) + } + None => None + } + } + + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let TransferEncoding(ref parts) = *self; + let last = parts.len() - 1; + for (i, part) in parts.iter().enumerate() { + try!(part.fmt(fmt)); + if i < last { + try!(", ".fmt(fmt)); + } + } + Ok(()) + } +} + +/// The `User-Agent` header field. +/// +/// They can contain any value, so it just wraps a `String`. +#[deriving(Clone, PartialEq, Show)] +pub struct UserAgent(pub String); + +impl Header for UserAgent { + fn header_name(_: Option) -> SendStr { + Slice("user-agent") + } + + fn parse_header(raw: &[Vec]) -> Option { + from_one_raw_str(raw).map(|s| UserAgent(s)) + } + + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let UserAgent(ref value) = *self; + value.fmt(fmt) + } +} + +fn from_one_raw_str(raw: &[Vec]) -> Option { + if raw.len() != 1 { + return None; + } + // we JUST checked that raw.len() == 1, so raw[0] WILL exist. + match from_utf8(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) { + Some(s) => FromStr::from_str(s), + None => None + } +} + +#[cfg(test)] +mod tests { + use std::io::MemReader; + use mime::{Mime, Text, Plain}; + use super::{Headers, Header, ContentLength, ContentType}; + + fn mem(s: &str) -> MemReader { + MemReader::new(s.as_bytes().to_vec()) + } + + #[test] + fn test_from_raw() { + let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap(); + assert_eq!(headers.get_ref(), Some(&ContentLength(10))); + } + + #[test] + fn test_content_type() { + let content_type = Header::parse_header(["text/plain".as_bytes().to_vec()].as_slice()); + assert_eq!(content_type, Some(ContentType(Mime(Text, Plain, vec![])))); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..f6870a3a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,77 @@ +//! # hyper +#![feature(macro_rules, phase)] +#![warn(missing_doc)] +#![deny(warnings)] +#![experimental] + +extern crate url; +#[phase(plugin,link)] extern crate log; +#[cfg(test)] extern crate test; + +pub use std::io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port}; +pub use mimewrapper::mime; +pub use url::Url; +pub use client::{get}; +pub use server::Server; + +use std::fmt; +use std::io::IoError; + +use std::rt::backtrace; + + +macro_rules! try_io( + ($e:expr) => (match $e { Ok(v) => v, Err(e) => return Err(::HttpIoError(e)) }) +) + +#[allow(dead_code)] +struct Trace; + +impl fmt::Show for Trace { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let _ = backtrace::write(fmt); + Ok(()) + } +} + +macro_rules! trace( + ($($arg:tt)*) => (if cfg!(not(ndebug)) { + format_args!(|args| log!(5, "{}\n{}", args, ::Trace), $($arg)*) + }) +) + +pub mod client; +pub mod method; +pub mod header; +pub mod server; +pub mod status; +pub mod uri; +pub mod version; + +mod rfc7230; + +mod mimewrapper { + extern crate mime; +} + + +/// Result type often returned from methods that can have `HttpError`s. +pub type HttpResult = Result; + +/// A set of errors that can occur parsing HTTP streams. +#[deriving(Show, PartialEq, Clone)] +pub enum HttpError { + /// An invalid `Method`, such as `GE,T`. + HttpMethodError, + /// An invalid `RequestUri`, such as `exam ple.domain`. + HttpUriError, + /// An invalid `HttpVersion`, such as `HTP/1.1` + HttpVersionError, + /// An invalid `Header`. + HttpHeaderError, + /// An invalid `Status`, such as `1337 ELITE`. + HttpStatusError, + /// An `IoError` that occured while trying to read or write to a network stream. + HttpIoError(IoError), +} + diff --git a/src/method.rs b/src/method.rs new file mode 100644 index 00000000..5dd9372a --- /dev/null +++ b/src/method.rs @@ -0,0 +1,99 @@ +//! # HTTP Method +use std::fmt; +use std::from_str::FromStr; + +/// The Request Method (VERB) +/// +/// Currently includes 8 variants representing the 8 methods defined in +/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH, +/// and an Extension variant for all extensions. +/// +/// It may make sense to grow this to include all variants currently +/// registered with IANA, if they are at all common to use. +#[deriving(Clone, PartialEq)] +pub enum Method { + /// OPTIONS + Options, + /// GET + Get, + /// POST + Post, + /// PUT + Put, + /// DELETE + Delete, + /// HEAD + Head, + /// TRACE + Trace, + /// CONNECT + Connect, + /// PATCH + Patch, + /// Method extentions. An example would be `let m = Extension("FOO".to_string())`. + Extension(String) +} + +impl Method { + /// Whether a method is considered "safe", meaning the request is + /// essentially read-only. + /// + /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1) + /// for more words. + pub fn safe(&self) -> bool { + match *self { + Get | Head | Options | Trace => true, + _ => false + } + } + + /// Whether a method is considered "idempotent", meaning the request has + /// the same result is executed multiple times. + /// + /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for + /// more words. + pub fn idempotent(&self) -> bool { + if self.safe() { + true + } else { + match *self { + Put | Delete => true, + _ => false + } + } + } +} + +impl FromStr for Method { + fn from_str(s: &str) -> Option { + Some(match s { + "OPTIONS" => Options, + "GET" => Get, + "POST" => Post, + "PUT" => Put, + "DELETE" => Delete, + "HEAD" => Head, + "TRACE" => Trace, + "CONNECT" => Connect, + "PATCH" => Patch, + _ => Extension(s.to_string()) + }) + } +} + +impl fmt::Show for Method { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Options => "OPTIONS", + Get => "GET", + Post => "POST", + Put => "PUT", + Delete => "DELETE", + Head => "HEAD", + Trace => "TRACE", + Connect => "CONNECT", + Patch => "PATCH", + Extension(ref s) => s.as_slice() + }.fmt(fmt) + } +} diff --git a/src/mime.rs b/src/mime.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/mime.rs @@ -0,0 +1 @@ + diff --git a/src/rfc7230.rs b/src/rfc7230.rs new file mode 100644 index 00000000..88f4e0dc --- /dev/null +++ b/src/rfc7230.rs @@ -0,0 +1,660 @@ +use std::cmp::min; +use std::io::{mod, Reader, IoResult}; +use std::u16; + +use url::Url; + +use method; +use status; +use uri; +use version::{HttpVersion, Http09, Http10, Http11, Http20}; +use {HttpResult, HttpMethodError, HttpVersionError, HttpIoError, HttpUriError}; +use {HttpHeaderError, HttpStatusError}; + +/// Readers to handle different Transfer-Encodings. +/// +/// If a message body does not include a Transfer-Encoding, it *should* +/// include a Content-Length header. +pub enum HttpReader { + /// A Reader used when a Content-Length header is passed with a positive integer. + SizedReader(R, uint), + /// A Reader used when Transfer-Encoding is `chunked`. + ChunkedReader(R, Option), + /// A Reader used for responses that don't indicate a length or chunked. + /// Note: This should only used for `Response`s. It is illegal for a + /// `Request` to be made with both `Content-Length` and + /// `Transfer-Encoding: chunked` missing, as explained from the spec: + /// + /// > If a Transfer-Encoding header field is present in a response and + /// > the chunked transfer coding is not the final encoding, the + /// > message body length is determined by reading the connection until + /// > it is closed by the server. If a Transfer-Encoding header field + /// > is present in a request and the chunked transfer coding is not + /// > the final encoding, the message body length cannot be determined + /// > reliably; the server MUST respond with the 400 (Bad Request) + /// > status code and then close the connection. + EofReader(R), +} + +impl Reader for HttpReader { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + match *self { + SizedReader(ref mut body, ref mut remaining) => { + debug!("Sized read, remaining={}", remaining); + if *remaining == 0 { + Err(io::standard_error(io::EndOfFile)) + } else { + let num = try!(body.read(buf)); + if num > *remaining { + *remaining = 0; + } else { + *remaining -= num; + } + Ok(num) + } + }, + ChunkedReader(ref mut body, ref mut opt_remaining) => { + let mut rem = match *opt_remaining { + Some(ref rem) => *rem, + // None means we don't know the size of the next chunk + None => try!(read_chunk_size(body)) + }; + + if rem == 0 { + // chunk of size 0 signals the end of the chunked stream + // if the 0 digit was missing from the stream, it would + // be an InvalidInput error instead. + return Err(io::standard_error(io::EndOfFile)); + } + + let to_read = min(rem, buf.len()); + let count = try!(body.read(buf.mut_slice_to(to_read))); + + rem -= count; + *opt_remaining = if rem > 0 { Some(rem) } else { None }; + Ok(count) + }, + EofReader(ref mut body) => { + body.read(buf) + } + } + } +} + +/// Chunked chunks start with 1*HEXDIGIT, indicating the size of the chunk. +fn read_chunk_size(rdr: &mut R) -> IoResult { + let mut size = 0u; + let radix = 16; + let mut in_ext = false; + loop { + match try!(rdr.read_byte()) { + b@b'0'..b'9' if !in_ext => { + size *= radix; + size += (b - b'0') as uint; + }, + b@b'a'..b'f' if !in_ext => { + size *= radix; + size += (b + 10 - b'a') as uint; + }, + b@b'A'..b'F' if !in_ext => { + size *= radix; + size += (b + 10 - b'A') as uint; + }, + CR => { + match try!(rdr.read_byte()) { + LF => break, + _ => return Err(io::standard_error(io::InvalidInput)) + } + }, + ext => { + in_ext = true; + debug!("TODO: chunk extension byte={}", ext); + } + } + } + Ok(size) +} + +pub static SP: u8 = b' '; +pub static CR: u8 = b'\r'; +pub static LF: u8 = b'\n'; +pub static STAR: u8 = b'*'; +pub static LINE_ENDING: &'static [u8] = &[CR, LF]; + +/// Determines if byte is a token char. +/// +/// > ```notrust +/// > token = 1*tchar +/// > +/// > tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" +/// > / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +/// > / DIGIT / ALPHA +/// > ; any VCHAR, except delimiters +/// > ``` +#[inline] +pub fn is_token(b: u8) -> bool { + match b { + b'a'..b'z' | + b'A'..b'Z' | + b'0'..b'9' | + b'!' | + b'#' | + b'$' | + b'%' | + b'&' | + b'\''| + b'*' | + b'+' | + b'-' | + b'.' | + b'^' | + b'_' | + b'`' | + b'|' | + b'~' => true, + _ => false + } +} + +// omg +enum MethodState { + MsStart, + MsG, + MsGE, + MsGET, + MsP, + MsPO, + MsPOS, + MsPOST, + MsPU, + MsPUT, + MsPA, + MsPAT, + MsPATC, + MsPATCH, + MsH, + MsHE, + MsHEA, + MsHEAD, + MsD, + MsDE, + MsDEL, + MsDELE, + MsDELET, + MsDELETE, + MsT, + MsTR, + MsTRA, + MsTRAC, + MsTRACE, + MsO, + MsOP, + MsOPT, + MsOPTI, + MsOPTIO, + MsOPTION, + MsOPTIONS, + MsC, + MsCO, + MsCON, + MsCONN, + MsCONNE, + MsCONNEC, + MsCONNECT, + MsExt +} + +// omg +impl MethodState { + fn as_slice(&self) -> &str { + match *self { + MsG => "G", + MsGE => "GE", + MsGET => "GET", + MsP => "P", + MsPO => "PO", + MsPOS => "POS", + MsPOST => "POST", + MsPU => "PU", + MsPUT => "PUT", + MsPA => "PA", + MsPAT => "PAT", + MsPATC => "PATC", + MsPATCH => "PATCH", + MsH => "H", + MsHE => "HE", + MsHEA => "HEA", + MsHEAD => "HEAD", + MsD => "D", + MsDE => "DE", + MsDEL => "DEL", + MsDELE => "DELE", + MsDELET => "DELET", + MsDELETE => "DELETE", + MsT => "T", + MsTR => "TR", + MsTRA => "TRA", + MsTRAC => "TRAC", + MsTRACE => "TRACE", + MsO => "O", + MsOP => "OP", + MsOPT => "OPT", + MsOPTI => "OPTI", + MsOPTIO => "OPTIO", + MsOPTION => "OPTION", + MsOPTIONS => "OPTIONS", + MsC => "C", + MsCO => "CO", + MsCON => "CON", + MsCONN => "CONN", + MsCONNE => "CONNE", + MsCONNEC => "CONNEC", + MsCONNECT => "CONNECT", + MsStart | MsExt => unreachable!() + } + } +} + +/// Read a `Method` from a raw stream, such as `GET`. +pub fn read_method(stream: &mut R) -> HttpResult { + let mut s = String::new(); + let mut state = MsStart; + + // omg + loop { + match (state, try_io!(stream.read_byte())) { + (MsStart, b'G') => state = MsG, + (MsStart, b'P') => state = MsP, + (MsStart, b'H') => state = MsH, + (MsStart, b'O') => state = MsO, + (MsStart, b'T') => state = MsT, + (MsStart, b'C') => state = MsC, + (MsStart, b@b'A'..b'Z') => { + state = MsExt; + s.push_char(b as char) + }, + + (MsG, b'E') => state = MsGE, + (MsGE, b'T') => state = MsGET, + + (MsP, b'O') => state = MsPO, + (MsPO, b'S') => state = MsPOS, + (MsPOS, b'T') => state = MsPOST, + + (MsP, b'U') => state = MsPU, + (MsPU, b'T') => state = MsPUT, + + (MsP, b'A') => state = MsPA, + (MsPA, b'T') => state = MsPAT, + (MsPAT, b'C') => state = MsPATC, + (MsPATC, b'H') => state = MsPATCH, + + (MsH, b'E') => state = MsHE, + (MsHE, b'A') => state = MsHEA, + (MsHEA, b'D') => state = MsHEAD, + + (MsO, b'P') => state = MsOP, + (MsOP, b'T') => state = MsOPT, + (MsOPT, b'I') => state = MsOPTI, + (MsOPTI, b'O') => state = MsOPTIO, + (MsOPTIO, b'N') => state = MsOPTION, + (MsOPTION, b'S') => state = MsOPTIONS, + + (MsT, b'R') => state = MsTR, + (MsTR, b'A') => state = MsTRA, + (MsTRA, b'C') => state = MsTRAC, + (MsTRAC, b'E') => state = MsTRACE, + + (MsC, b'O') => state = MsCO, + (MsCO, b'N') => state = MsCON, + (MsCON, b'N') => state = MsCONN, + (MsCONN, b'E') => state = MsCONNE, + (MsCONNE, b'C') => state = MsCONNEC, + (MsCONNEC, b'T') => state = MsCONNECT, + + (MsExt, b@b'A'..b'Z') => s.push_char(b as char), + + (_, b@b'A'..b'Z') => { + s = state.as_slice().to_string(); + s.push_char(b as char); + }, + + (MsGET, SP) => return Ok(method::Get), + (MsPOST, SP) => return Ok(method::Post), + (MsPUT, SP) => return Ok(method::Put), + (MsPATCH, SP) => return Ok(method::Patch), + (MsHEAD, SP) => return Ok(method::Head), + (MsDELETE, SP) => return Ok(method::Delete), + (MsTRACE, SP) => return Ok(method::Trace), + (MsOPTIONS, SP) => return Ok(method::Options), + (MsCONNECT, SP) => return Ok(method::Connect), + (MsExt, SP) => return Ok(method::Extension(s)), + + (_, _) => return Err(HttpMethodError) + } + } +} + +/// Read a `RequestUri` from a raw stream. +pub fn read_uri(stream: &mut R) -> HttpResult { + let mut b = try_io!(stream.read_byte()); + while b == SP { + b = try_io!(stream.read_byte()); + } + + let mut s = String::new(); + if b == STAR { + try!(expect(stream.read_byte(), SP)); + return Ok(uri::Star) + } else { + s.push_char(b as char); + loop { + match try_io!(stream.read_byte()) { + SP => { + break; + }, + CR | LF => { + return Err(HttpUriError) + }, + b => s.push_char(b as char) + } + } + } + + if s.as_slice().starts_with("/") { + Ok(uri::AbsolutePath(s)) + } else if s.as_slice().contains("/") { + match Url::parse(s.as_slice()) { + Ok(u) => Ok(uri::AbsoluteUri(u)), + Err(_e) => { + debug!("URL err {}", _e); + Err(HttpUriError) + } + } + } else { + let mut temp = "http://".to_string(); + temp.push_str(s.as_slice()); + match Url::parse(temp.as_slice()) { + Ok(_u) => { + debug!("TODO: compare vs u.authority()"); + Ok(uri::Authority(s)) + } + Err(_e) => { + debug!("URL err {}", _e); + Err(HttpUriError) + } + } + } + + +} + + +/// Read the `HttpVersion` from a raw stream, such as `HTTP/1.1`. +pub fn read_http_version(stream: &mut R) -> HttpResult { + try!(expect(stream.read_byte(), b'H')); + try!(expect(stream.read_byte(), b'T')); + try!(expect(stream.read_byte(), b'T')); + try!(expect(stream.read_byte(), b'P')); + try!(expect(stream.read_byte(), b'/')); + + match try_io!(stream.read_byte()) { + b'0' => { + try!(expect(stream.read_byte(), b'.')); + try!(expect(stream.read_byte(), b'9')); + Ok(Http09) + }, + b'1' => { + try!(expect(stream.read_byte(), b'.')); + match try_io!(stream.read_byte()) { + b'0' => Ok(Http10), + b'1' => Ok(Http11), + _ => Err(HttpVersionError) + } + }, + b'2' => { + try!(expect(stream.read_byte(), b'.')); + try!(expect(stream.read_byte(), b'0')); + Ok(Http20) + }, + _ => Err(HttpVersionError) + } +} + +/// The raw bytes when parsing a header line. +/// +/// 2 vectors of u8s, divided by COLON (`:`). The first vector is guaranteed +/// to be all `token`s. See `is_token_char` source for all valid characters. +pub type RawHeaderLine = (Vec, Vec); + +/// Read a RawHeaderLine from a Reader. +/// +/// From [spec](https://tools.ietf.org/html/rfc7230#section-3.2): +/// +/// > Each header field consists of a case-insensitive field name followed +/// > by a colon (":"), optional leading whitespace, the field value, and +/// > optional trailing whitespace. +/// > +/// > ```notrust +/// > header-field = field-name ":" OWS field-value OWS +/// > +/// > field-name = token +/// > field-value = *( field-content / obs-fold ) +/// > field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] +/// > field-vchar = VCHAR / obs-text +/// > +/// > obs-fold = CRLF 1*( SP / HTAB ) +/// > ; obsolete line folding +/// > ; see Section 3.2.4 +/// > ``` +pub fn read_header(stream: &mut R) -> HttpResult> { + let mut name = vec![]; + let mut value = vec![]; + + loop { + match try_io!(stream.read_byte()) { + CR if name.len() == 0 => { + match try_io!(stream.read_byte()) { + LF => return Ok(None), + _ => return Err(HttpHeaderError) + } + }, + b':' => break, + b if is_token(b) => name.push(b), + _nontoken => return Err(HttpHeaderError) + }; + } + + let mut ows = true; //optional whitespace + + //TODO: handling obs-folding (gross!) + loop { + match try_io!(stream.read_byte()) { + CR => break, + LF => return Err(HttpHeaderError), + b' ' if ows => {}, + b => { + ows = false; + value.push(b) + } + }; + } + + match try_io!(stream.read_byte()) { + LF => Ok(Some((name, value))), + _ => Err(HttpHeaderError) + } + +} + +/// `request-line = method SP request-target SP HTTP-version CRLF` +pub type RequestLine = (method::Method, uri::RequestUri, HttpVersion); + +/// Read the `RequestLine`, such as `GET / HTTP/1.1`. +pub fn read_request_line(stream: &mut R) -> HttpResult { + let method = try!(read_method(stream)); + let uri = try!(read_uri(stream)); + let version = try!(read_http_version(stream)); + + if try_io!(stream.read_byte()) != CR { + return Err(HttpVersionError); + } + if try_io!(stream.read_byte()) != LF { + return Err(HttpVersionError); + } + + Ok((method, uri, version)) +} + +/// `status-line = HTTP-version SP status-code SP reason-phrase CRLF` +/// +/// However, reason-phrase is absolutely useless, so its tossed. +pub type StatusLine = (HttpVersion, status::StatusCode); + +/// Read the StatusLine, such as `HTTP/1.1 200 OK`. +/// +/// > The first line of a response message is the status-line, consisting +/// > of the protocol version, a space (SP), the status code, another +/// > space, a possibly empty textual phrase describing the status code, +/// > and ending with CRLF. +/// > +/// >```notrust +/// > status-line = HTTP-version SP status-code SP reason-phrase CRLF +/// > status-code = 3DIGIT +/// > reason-phrase = *( HTAB / SP / VCHAR / obs-text ) +/// >``` +pub fn read_status_line(stream: &mut R) -> HttpResult { + let version = try!(read_http_version(stream)); + if try_io!(stream.read_byte()) != SP { + return Err(HttpVersionError); + } + let code = try!(read_status(stream)); + + Ok((version, code)) +} + +pub fn read_status(stream: &mut R) -> HttpResult { + let code = [ + try_io!(stream.read_byte()), + try_io!(stream.read_byte()), + try_io!(stream.read_byte()), + ]; + + let code = match u16::parse_bytes(code.as_slice(), 10) { + Some(num) => match FromPrimitive::from_u16(num) { + Some(code) => code, + None => return Err(HttpStatusError) + }, + None => return Err(HttpStatusError) + }; + + // reason is purely for humans, so just consume it till we get to CRLF + loop { + match try_io!(stream.read_byte()) { + CR => match try_io!(stream.read_byte()) { + LF => break, + _ => return Err(HttpStatusError) + }, + _ => () + } + } + + Ok(code) +} + +#[inline] +fn expect(r: IoResult, expected: u8) -> HttpResult<()> { + match r { + Ok(b) if b == expected => Ok(()), + Ok(_) => Err(HttpVersionError), + Err(e) => Err(HttpIoError(e)) + } +} + +#[cfg(test)] +mod tests { + use std::io::MemReader; + use test::Bencher; + use uri::{RequestUri, Star, AbsoluteUri, AbsolutePath, Authority}; + use method; + use status; + use version::{HttpVersion, Http10, Http11, Http20}; + use {HttpResult, HttpVersionError}; + use url::Url; + + use super::{read_method, read_uri, read_http_version, read_header, RawHeaderLine, read_status}; + + fn mem(s: &str) -> MemReader { + MemReader::new(s.as_bytes().to_vec()) + } + + #[test] + fn test_read_method() { + fn read(s: &str, m: method::Method) { + assert_eq!(read_method(&mut mem(s)), Ok(m)); + } + + read("GET /", method::Get); + read("POST /", method::Post); + read("PUT /", method::Put); + read("HEAD /", method::Head); + read("OPTIONS /", method::Options); + read("CONNECT /", method::Connect); + read("TRACE /", method::Trace); + read("PATCH /", method::Patch); + read("FOO /", method::Extension("FOO".to_string())); + } + + #[test] + fn test_read_uri() { + fn read(s: &str, result: HttpResult) { + assert_eq!(read_uri(&mut mem(s)), result); + } + + read("* ", Ok(Star)); + read("http://hyper.rs/ ", Ok(AbsoluteUri(Url::parse("http://hyper.rs/").unwrap()))); + read("hyper.rs ", Ok(Authority("hyper.rs".to_string()))); + read("/ ", Ok(AbsolutePath("/".to_string()))); + } + + #[test] + fn test_read_http_version() { + fn read(s: &str, result: HttpResult) { + assert_eq!(read_http_version(&mut mem(s)), result); + } + + read("HTTP/1.0", Ok(Http10)); + read("HTTP/1.1", Ok(Http11)); + read("HTTP/2.0", Ok(Http20)); + read("HTP/2.0", Err(HttpVersionError)); + read("HTTP.2.0", Err(HttpVersionError)); + read("HTTP 2.0", Err(HttpVersionError)); + read("TTP 2.0", Err(HttpVersionError)); + } + + #[test] + fn test_read_status() { + fn read(s: &str, result: HttpResult) { + assert_eq!(read_status(&mut mem(s)), result); + } + + read("200 OK\r\n", Ok(status::Ok)); + } + + #[test] + fn test_read_header() { + fn read(s: &str, result: HttpResult>) { + assert_eq!(read_header(&mut mem(s)), result); + } + + read("Host: rust-lang.org\r\n", Ok(Some(("Host".as_bytes().to_vec(), + "rust-lang.org".as_bytes().to_vec())))); + } + + #[bench] + fn bench_read_method(b: &mut Bencher) { + b.bytes = b"CONNECT ".len() as u64; + b.iter(|| assert_eq!(read_method(&mut mem("CONNECT ")), Ok(method::Connect))); + } + +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 00000000..086f1be2 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,83 @@ +//! # Server +use std::io::net::tcp::TcpListener; +use std::io::{Acceptor, Listener, IoResult}; +use std::io::net::ip::{IpAddr, Port}; + +pub use self::request::Request; +pub use self::response::Response; + +pub mod request; +pub mod response; + +/// A server can listen on a TCP socket. +/// +/// Once listening, it will create a `Request`/`Response` pair for each +/// incoming connection, and hand them to the provided handler. +pub struct Server { + ip: IpAddr, + port: Port +} + + +impl Server { + + /// Creates a server to be used for `http` conenctions. + pub fn http(ip: IpAddr, port: Port) -> Server { + Server { + ip: ip, + port: port + } + } + + /// Binds to a socket, and starts handling connections. + pub fn listen(&self, mut handler: H) { + let listener = match TcpListener::bind(self.ip.to_string().as_slice(), self.port) { + Ok(listener) => listener, + Err(err) => fail!("Listen failed: {}", err) + }; + let mut acceptor = listener.listen(); + + for conn in acceptor.incoming() { + match conn { + Ok(stream) => { + debug!("Incoming stream"); + let clone = stream.clone(); + let req = match Request::new(stream) { + Ok(r) => r, + Err(err) => { + error!("creating Request: {}", err); + continue; + } + }; + let mut res = Response::new(clone); + res.version = req.version; + match handler.handle(req, res) { + Ok(..) => debug!("Stream handled"), + Err(e) => { + error!("Error from handler: {}", e) + //TODO try to send a status code + } + } + }, + Err(e) => { + error!("Connection failed: {}", e); + } + } + } + } + +} + +/// A handler that can handle incoming requests for a server. +pub trait Handler { + /// Receives a `Request`/`Response` pair, and should perform some action on them. + /// + /// This could reading from the request, and writing to the response. + fn handle(&mut self, req: Request, res: Response) -> IoResult<()>; +} + +impl<'a> Handler for |Request, Response|: 'a -> IoResult<()> { + fn handle(&mut self, req: Request, res: Response) -> IoResult<()> { + (*self)(req, res) + } +} diff --git a/src/server/request.rs b/src/server/request.rs new file mode 100644 index 00000000..cd277823 --- /dev/null +++ b/src/server/request.rs @@ -0,0 +1,72 @@ +//! # Server Requests +//! +//! These are requests that a `hyper::Server` receives, and include its method, +//! target URI, headers, and message body. +use std::io::{Reader, IoResult}; +use std::io::net::ip::SocketAddr; +use std::io::net::tcp::TcpStream; + +use {HttpResult}; +use version::{HttpVersion}; +use method; +use header::{Headers, ContentLength}; +use rfc7230::{read_request_line}; +use rfc7230::{HttpReader, SizedReader, ChunkedReader}; +use uri::RequestUri; + +/// A request bundles several parts of an incoming TCP stream, given to a `Handler`. +pub struct Request { + /// The IP address of the remote connection. + pub remote_addr: SocketAddr, + /// The `Method`, such as `Get`, `Post`, etc. + pub method: method::Method, + /// The headers of the incoming request. + pub headers: Headers, + /// The target request-uri for this request. + pub uri: RequestUri, + /// The version of HTTP for this request. + pub version: HttpVersion, + body: HttpReader +} + + +impl Request { + + /// Create a new Request, reading the StartLine and Headers so they are + /// immediately useful. + pub fn new(mut tcp: TcpStream) -> HttpResult { + let (method, uri, version) = try!(read_request_line(&mut tcp)); + let mut headers = try!(Headers::from_raw(&mut tcp)); + + debug!("{} {} {}", method, uri, version); + debug!("{}", headers); + + let remote_addr = try_io!(tcp.peer_name()); + + // TODO: handle Transfer-Encoding + let body = if headers.has::() { + match headers.get_ref::() { + Some(&ContentLength(len)) => SizedReader(tcp, len), + None => unreachable!() + } + } else { + ChunkedReader(tcp, None) + }; + + Ok(Request { + remote_addr: remote_addr, + method: method, + uri: uri, + headers: headers, + version: version, + body: body, + }) + } +} + +impl Reader for Request { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + self.body.read(buf) + } +} + diff --git a/src/server/response.rs b/src/server/response.rs new file mode 100644 index 00000000..bfa11471 --- /dev/null +++ b/src/server/response.rs @@ -0,0 +1,81 @@ +//! # Server Responses +//! +//! These are responses sent by a `hyper::Server` to clients, after +//! receiving a request. +use std::io::IoResult; +use std::io::net::tcp::TcpStream; + +use header; +use status; +use version; +use rfc7230::{CR, LF, LINE_ENDING}; + + +/// The outgoing half for a Tcp connection, created by a `Server` and given to a `Handler`. +pub struct Response { + /// The status code for the request. + pub status: status::StatusCode, + /// The outgoing headers on this response. + pub headers: header::Headers, + /// The HTTP version of this response. + pub version: version::HttpVersion, + + headers_written: bool, // TODO: can this check be moved to compile time? + body: TcpStream +} + +impl Response { + + /// Creates a new Response that can be used to write to a network stream. + pub fn new(tcp: TcpStream) -> Response { + Response { + status: status::Ok, + version: version::Http11, + headers: header::Headers::new(), + headers_written: false, + body: tcp + } + } + + fn write_head(&mut self) -> IoResult<()> { + if self.headers_written { + debug!("headers previsouly written, nooping"); + return Ok(()); + } + self.headers_written = true; + debug!("writing head: {} {}", self.version, self.status); + try!(write!(self.body, "{} {}{}{}", self.version, self.status, CR as char, LF as char)); + + for (name, header) in self.headers.iter() { + debug!("headers {}: {}", name, header); + try!(write!(self.body, "{}: {}", name, header)); + try!(self.body.write(LINE_ENDING)); + } + + self.body.write(LINE_ENDING) + } + + /// Flushes all writing of a response to the client. + pub fn end(&mut self) -> IoResult<()> { + try!(self.flush()); + self.body.close_write() + } +} + + +impl Writer for Response { + fn write(&mut self, msg: &[u8]) -> IoResult<()> { + if !self.headers_written { + try!(self.write_head()); + } + debug!("write {:u} bytes", msg.len()); + self.body.write(msg) + } + + fn flush(&mut self) -> IoResult<()> { + if !self.headers_written { + try!(self.write_head()); + } + self.body.flush() + } +} diff --git a/src/status.rs b/src/status.rs new file mode 100644 index 00000000..eb8d9faa --- /dev/null +++ b/src/status.rs @@ -0,0 +1,1769 @@ +//! Status Codes +use std::fmt; +use std::mem::transmute; + +// shamelessly lifted from Teepee. I tried a few schemes, this really +// does seem like the best. + +/// An HTTP status code (`Status-Code` in RFC 2616). +/// +/// This enum is absolutely exhaustive, covering all 500 possible values (100–599). +/// +/// For HTTP/2.0, statuses belonging to the 1xx Informational class are invalid. +/// +/// As this is a C‐style enum with each variant having a corresponding value, you may use the likes +/// of `Continue as u16` to retreive the value `100u16`. Normally, though, you should not need to do +/// any such thing; just use the status code as a `StatusCode`. +/// +/// If you encounter a status code that you do not know how to deal with, you should treat it as the +/// `x00` status code—e.g. for code 123, treat it as 100 (Continue). This can be achieved with +/// `self.class().default_code()`: +/// +/// ```rust +/// # use hyper::status::{Code123, Continue}; +/// assert_eq!(Code123.class().default_code(), Continue); +/// ``` +pub enum StatusCode { + /// 100 Continue + Continue = 100, + /// 101 Switching Protocols + SwitchingProtocols = 101, + /// 102 Processing + Processing = 102, + /// 103 (unregistered) + Code103 = 103, + /// 104 (unregistered) + Code104 = 104, + /// 105 (unregistered) + Code105 = 105, + /// 106 (unregistered) + Code106 = 106, + /// 107 (unregistered) + Code107 = 107, + /// 108 (unregistered) + Code108 = 108, + /// 109 (unregistered) + Code109 = 109, + /// 110 (unregistered) + Code110 = 110, + /// 111 (unregistered) + Code111 = 111, + /// 112 (unregistered) + Code112 = 112, + /// 113 (unregistered) + Code113 = 113, + /// 114 (unregistered) + Code114 = 114, + /// 115 (unregistered) + Code115 = 115, + /// 116 (unregistered) + Code116 = 116, + /// 117 (unregistered) + Code117 = 117, + /// 118 (unregistered) + Code118 = 118, + /// 119 (unregistered) + Code119 = 119, + /// 120 (unregistered) + Code120 = 120, + /// 121 (unregistered) + Code121 = 121, + /// 122 (unregistered) + Code122 = 122, + /// 123 (unregistered) + Code123 = 123, + /// 124 (unregistered) + Code124 = 124, + /// 125 (unregistered) + Code125 = 125, + /// 126 (unregistered) + Code126 = 126, + /// 127 (unregistered) + Code127 = 127, + /// 128 (unregistered) + Code128 = 128, + /// 129 (unregistered) + Code129 = 129, + /// 130 (unregistered) + Code130 = 130, + /// 131 (unregistered) + Code131 = 131, + /// 132 (unregistered) + Code132 = 132, + /// 133 (unregistered) + Code133 = 133, + /// 134 (unregistered) + Code134 = 134, + /// 135 (unregistered) + Code135 = 135, + /// 136 (unregistered) + Code136 = 136, + /// 137 (unregistered) + Code137 = 137, + /// 138 (unregistered) + Code138 = 138, + /// 139 (unregistered) + Code139 = 139, + /// 140 (unregistered) + Code140 = 140, + /// 141 (unregistered) + Code141 = 141, + /// 142 (unregistered) + Code142 = 142, + /// 143 (unregistered) + Code143 = 143, + /// 144 (unregistered) + Code144 = 144, + /// 145 (unregistered) + Code145 = 145, + /// 146 (unregistered) + Code146 = 146, + /// 147 (unregistered) + Code147 = 147, + /// 148 (unregistered) + Code148 = 148, + /// 149 (unregistered) + Code149 = 149, + /// 150 (unregistered) + Code150 = 150, + /// 151 (unregistered) + Code151 = 151, + /// 152 (unregistered) + Code152 = 152, + /// 153 (unregistered) + Code153 = 153, + /// 154 (unregistered) + Code154 = 154, + /// 155 (unregistered) + Code155 = 155, + /// 156 (unregistered) + Code156 = 156, + /// 157 (unregistered) + Code157 = 157, + /// 158 (unregistered) + Code158 = 158, + /// 159 (unregistered) + Code159 = 159, + /// 160 (unregistered) + Code160 = 160, + /// 161 (unregistered) + Code161 = 161, + /// 162 (unregistered) + Code162 = 162, + /// 163 (unregistered) + Code163 = 163, + /// 164 (unregistered) + Code164 = 164, + /// 165 (unregistered) + Code165 = 165, + /// 166 (unregistered) + Code166 = 166, + /// 167 (unregistered) + Code167 = 167, + /// 168 (unregistered) + Code168 = 168, + /// 169 (unregistered) + Code169 = 169, + /// 170 (unregistered) + Code170 = 170, + /// 171 (unregistered) + Code171 = 171, + /// 172 (unregistered) + Code172 = 172, + /// 173 (unregistered) + Code173 = 173, + /// 174 (unregistered) + Code174 = 174, + /// 175 (unregistered) + Code175 = 175, + /// 176 (unregistered) + Code176 = 176, + /// 177 (unregistered) + Code177 = 177, + /// 178 (unregistered) + Code178 = 178, + /// 179 (unregistered) + Code179 = 179, + /// 180 (unregistered) + Code180 = 180, + /// 181 (unregistered) + Code181 = 181, + /// 182 (unregistered) + Code182 = 182, + /// 183 (unregistered) + Code183 = 183, + /// 184 (unregistered) + Code184 = 184, + /// 185 (unregistered) + Code185 = 185, + /// 186 (unregistered) + Code186 = 186, + /// 187 (unregistered) + Code187 = 187, + /// 188 (unregistered) + Code188 = 188, + /// 189 (unregistered) + Code189 = 189, + /// 190 (unregistered) + Code190 = 190, + /// 191 (unregistered) + Code191 = 191, + /// 192 (unregistered) + Code192 = 192, + /// 193 (unregistered) + Code193 = 193, + /// 194 (unregistered) + Code194 = 194, + /// 195 (unregistered) + Code195 = 195, + /// 196 (unregistered) + Code196 = 196, + /// 197 (unregistered) + Code197 = 197, + /// 198 (unregistered) + Code198 = 198, + /// 199 (unregistered) + Code199 = 199, + + /// 200 OK + Ok = 200, + /// 201 Created + Created = 201, + /// 202 Accepted + Accepted = 202, + /// 203 Non-Authoritative Information + NonAuthoritativeInformation = 203, + /// 204 No Content + NoContent = 204, + /// 205 Reset Content + ResetContent = 205, + /// 206 Partial Content + PartialContent = 206, + /// 207 Multi-Status + MultiStatus = 207, + /// 208 Already Reported + AlreadyReported = 208, + /// 209 (unregistered) + Code209 = 209, + /// 210 (unregistered) + Code210 = 210, + /// 211 (unregistered) + Code211 = 211, + /// 212 (unregistered) + Code212 = 212, + /// 213 (unregistered) + Code213 = 213, + /// 214 (unregistered) + Code214 = 214, + /// 215 (unregistered) + Code215 = 215, + /// 216 (unregistered) + Code216 = 216, + /// 217 (unregistered) + Code217 = 217, + /// 218 (unregistered) + Code218 = 218, + /// 219 (unregistered) + Code219 = 219, + /// 220 (unregistered) + Code220 = 220, + /// 221 (unregistered) + Code221 = 221, + /// 222 (unregistered) + Code222 = 222, + /// 223 (unregistered) + Code223 = 223, + /// 224 (unregistered) + Code224 = 224, + /// 225 (unregistered) + Code225 = 225, + /// 226 IM Used + ImUsed = 226, + /// 227 (unregistered) + Code227 = 227, + /// 228 (unregistered) + Code228 = 228, + /// 229 (unregistered) + Code229 = 229, + /// 230 (unregistered) + Code230 = 230, + /// 231 (unregistered) + Code231 = 231, + /// 232 (unregistered) + Code232 = 232, + /// 233 (unregistered) + Code233 = 233, + /// 234 (unregistered) + Code234 = 234, + /// 235 (unregistered) + Code235 = 235, + /// 236 (unregistered) + Code236 = 236, + /// 237 (unregistered) + Code237 = 237, + /// 238 (unregistered) + Code238 = 238, + /// 239 (unregistered) + Code239 = 239, + /// 240 (unregistered) + Code240 = 240, + /// 241 (unregistered) + Code241 = 241, + /// 242 (unregistered) + Code242 = 242, + /// 243 (unregistered) + Code243 = 243, + /// 244 (unregistered) + Code244 = 244, + /// 245 (unregistered) + Code245 = 245, + /// 246 (unregistered) + Code246 = 246, + /// 247 (unregistered) + Code247 = 247, + /// 248 (unregistered) + Code248 = 248, + /// 249 (unregistered) + Code249 = 249, + /// 250 (unregistered) + Code250 = 250, + /// 251 (unregistered) + Code251 = 251, + /// 252 (unregistered) + Code252 = 252, + /// 253 (unregistered) + Code253 = 253, + /// 254 (unregistered) + Code254 = 254, + /// 255 (unregistered) + Code255 = 255, + /// 256 (unregistered) + Code256 = 256, + /// 257 (unregistered) + Code257 = 257, + /// 258 (unregistered) + Code258 = 258, + /// 259 (unregistered) + Code259 = 259, + /// 260 (unregistered) + Code260 = 260, + /// 261 (unregistered) + Code261 = 261, + /// 262 (unregistered) + Code262 = 262, + /// 263 (unregistered) + Code263 = 263, + /// 264 (unregistered) + Code264 = 264, + /// 265 (unregistered) + Code265 = 265, + /// 266 (unregistered) + Code266 = 266, + /// 267 (unregistered) + Code267 = 267, + /// 268 (unregistered) + Code268 = 268, + /// 269 (unregistered) + Code269 = 269, + /// 270 (unregistered) + Code270 = 270, + /// 271 (unregistered) + Code271 = 271, + /// 272 (unregistered) + Code272 = 272, + /// 273 (unregistered) + Code273 = 273, + /// 274 (unregistered) + Code274 = 274, + /// 275 (unregistered) + Code275 = 275, + /// 276 (unregistered) + Code276 = 276, + /// 277 (unregistered) + Code277 = 277, + /// 278 (unregistered) + Code278 = 278, + /// 279 (unregistered) + Code279 = 279, + /// 280 (unregistered) + Code280 = 280, + /// 281 (unregistered) + Code281 = 281, + /// 282 (unregistered) + Code282 = 282, + /// 283 (unregistered) + Code283 = 283, + /// 284 (unregistered) + Code284 = 284, + /// 285 (unregistered) + Code285 = 285, + /// 286 (unregistered) + Code286 = 286, + /// 287 (unregistered) + Code287 = 287, + /// 288 (unregistered) + Code288 = 288, + /// 289 (unregistered) + Code289 = 289, + /// 290 (unregistered) + Code290 = 290, + /// 291 (unregistered) + Code291 = 291, + /// 292 (unregistered) + Code292 = 292, + /// 293 (unregistered) + Code293 = 293, + /// 294 (unregistered) + Code294 = 294, + /// 295 (unregistered) + Code295 = 295, + /// 296 (unregistered) + Code296 = 296, + /// 297 (unregistered) + Code297 = 297, + /// 298 (unregistered) + Code298 = 298, + /// 299 (unregistered) + Code299 = 299, + + /// 300 Multiple Choices + MultipleChoices = 300, + /// 301 Moved Permanently + MovedPermanently = 301, + /// 302 Found + Found = 302, + /// 303 See Other + SeeOther = 303, + /// 304 Not Modified + NotModified = 304, + /// 305 Use Proxy + UseProxy = 305, + /// 306 Switch Proxy + SwitchProxy = 306, + /// 307 Temporary Redirect + TemporaryRedirect = 307, + /// 308 Permanent Redirect + PermanentRedirect = 308, + /// 309 (unregistered) + Code309 = 309, + /// 310 (unregistered) + Code310 = 310, + /// 311 (unregistered) + Code311 = 311, + /// 312 (unregistered) + Code312 = 312, + /// 313 (unregistered) + Code313 = 313, + /// 314 (unregistered) + Code314 = 314, + /// 315 (unregistered) + Code315 = 315, + /// 316 (unregistered) + Code316 = 316, + /// 317 (unregistered) + Code317 = 317, + /// 318 (unregistered) + Code318 = 318, + /// 319 (unregistered) + Code319 = 319, + /// 320 (unregistered) + Code320 = 320, + /// 321 (unregistered) + Code321 = 321, + /// 322 (unregistered) + Code322 = 322, + /// 323 (unregistered) + Code323 = 323, + /// 324 (unregistered) + Code324 = 324, + /// 325 (unregistered) + Code325 = 325, + /// 326 (unregistered) + Code326 = 326, + /// 327 (unregistered) + Code327 = 327, + /// 328 (unregistered) + Code328 = 328, + /// 329 (unregistered) + Code329 = 329, + /// 330 (unregistered) + Code330 = 330, + /// 331 (unregistered) + Code331 = 331, + /// 332 (unregistered) + Code332 = 332, + /// 333 (unregistered) + Code333 = 333, + /// 334 (unregistered) + Code334 = 334, + /// 335 (unregistered) + Code335 = 335, + /// 336 (unregistered) + Code336 = 336, + /// 337 (unregistered) + Code337 = 337, + /// 338 (unregistered) + Code338 = 338, + /// 339 (unregistered) + Code339 = 339, + /// 340 (unregistered) + Code340 = 340, + /// 341 (unregistered) + Code341 = 341, + /// 342 (unregistered) + Code342 = 342, + /// 343 (unregistered) + Code343 = 343, + /// 344 (unregistered) + Code344 = 344, + /// 345 (unregistered) + Code345 = 345, + /// 346 (unregistered) + Code346 = 346, + /// 347 (unregistered) + Code347 = 347, + /// 348 (unregistered) + Code348 = 348, + /// 349 (unregistered) + Code349 = 349, + /// 350 (unregistered) + Code350 = 350, + /// 351 (unregistered) + Code351 = 351, + /// 352 (unregistered) + Code352 = 352, + /// 353 (unregistered) + Code353 = 353, + /// 354 (unregistered) + Code354 = 354, + /// 355 (unregistered) + Code355 = 355, + /// 356 (unregistered) + Code356 = 356, + /// 357 (unregistered) + Code357 = 357, + /// 358 (unregistered) + Code358 = 358, + /// 359 (unregistered) + Code359 = 359, + /// 360 (unregistered) + Code360 = 360, + /// 361 (unregistered) + Code361 = 361, + /// 362 (unregistered) + Code362 = 362, + /// 363 (unregistered) + Code363 = 363, + /// 364 (unregistered) + Code364 = 364, + /// 365 (unregistered) + Code365 = 365, + /// 366 (unregistered) + Code366 = 366, + /// 367 (unregistered) + Code367 = 367, + /// 368 (unregistered) + Code368 = 368, + /// 369 (unregistered) + Code369 = 369, + /// 370 (unregistered) + Code370 = 370, + /// 371 (unregistered) + Code371 = 371, + /// 372 (unregistered) + Code372 = 372, + /// 373 (unregistered) + Code373 = 373, + /// 374 (unregistered) + Code374 = 374, + /// 375 (unregistered) + Code375 = 375, + /// 376 (unregistered) + Code376 = 376, + /// 377 (unregistered) + Code377 = 377, + /// 378 (unregistered) + Code378 = 378, + /// 379 (unregistered) + Code379 = 379, + /// 380 (unregistered) + Code380 = 380, + /// 381 (unregistered) + Code381 = 381, + /// 382 (unregistered) + Code382 = 382, + /// 383 (unregistered) + Code383 = 383, + /// 384 (unregistered) + Code384 = 384, + /// 385 (unregistered) + Code385 = 385, + /// 386 (unregistered) + Code386 = 386, + /// 387 (unregistered) + Code387 = 387, + /// 388 (unregistered) + Code388 = 388, + /// 389 (unregistered) + Code389 = 389, + /// 390 (unregistered) + Code390 = 390, + /// 391 (unregistered) + Code391 = 391, + /// 392 (unregistered) + Code392 = 392, + /// 393 (unregistered) + Code393 = 393, + /// 394 (unregistered) + Code394 = 394, + /// 395 (unregistered) + Code395 = 395, + /// 396 (unregistered) + Code396 = 396, + /// 397 (unregistered) + Code397 = 397, + /// 398 (unregistered) + Code398 = 398, + /// 399 (unregistered) + Code399 = 399, + + /// 400 Bad Request + BadRequest = 400, + /// 401 Unauthorized + Unauthorized = 401, + /// 402 Payment Required + PaymentRequired = 402, + /// 403 Forbidden + Forbidden = 403, + /// 404 Not Found + NotFound = 404, + /// 405 Method Not Allowed + MethodNotAllowed = 405, + /// 406 Not Acceptable + NotAcceptable = 406, + /// 407 Proxy Authentication Required + ProxyAuthenticationRequired = 407, + /// 408 Request Timeout + RequestTimeout = 408, + /// 409 Conflict + Conflict = 409, + /// 410 Gone + Gone = 410, + /// 411 Length Required + LengthRequired = 411, + /// 412 Precondition Failed + PreconditionFailed = 412, + /// 413 Request Entity Too Large + RequestEntityTooLarge = 413, + /// 414 Request-URI Too Long + RequestUriTooLong = 414, + /// 415 Unsupported Media Type + UnsupportedMediaType = 415, + /// 416 Requested Range Not Satisfiable + RequestedRangeNotSatisfiable = 416, + /// 417 Expectation Failed + ExpectationFailed = 417, + /// 418 I'm a teapot + ImATeapot = 418, + /// 419 Authentication Timeout + AuthenticationTimeout = 419, + /// 420 (unregistered) + Code420 = 420, + /// 421 (unregistered) + Code421 = 421, + /// 422 Unprocessable Entity + UnprocessableEntity = 422, + /// 423 Locked + Locked = 423, + /// 424 Failed Dependency + FailedDependency = 424, + /// 425 Unordered Collection + UnorderedCollection = 425, + /// 426 Upgrade Required + UpgradeRequired = 426, + /// 427 (unregistered) + Code427 = 427, + /// 428 Precondition Required + PreconditionRequired = 428, + /// 429 Too Many Requests + TooManyRequests = 429, + /// 430 (unregistered) + Code430 = 430, + /// 431 Request Header Fields Too Large + RequestHeaderFieldsTooLarge = 431, + /// 432 (unregistered) + Code432 = 432, + /// 433 (unregistered) + Code433 = 433, + /// 434 (unregistered) + Code434 = 434, + /// 435 (unregistered) + Code435 = 435, + /// 436 (unregistered) + Code436 = 436, + /// 437 (unregistered) + Code437 = 437, + /// 438 (unregistered) + Code438 = 438, + /// 439 (unregistered) + Code439 = 439, + /// 440 (unregistered) + Code440 = 440, + /// 441 (unregistered) + Code441 = 441, + /// 442 (unregistered) + Code442 = 442, + /// 443 (unregistered) + Code443 = 443, + /// 444 (unregistered) + Code444 = 444, + /// 445 (unregistered) + Code445 = 445, + /// 446 (unregistered) + Code446 = 446, + /// 447 (unregistered) + Code447 = 447, + /// 448 (unregistered) + Code448 = 448, + /// 449 (unregistered) + Code449 = 449, + /// 450 (unregistered) + Code450 = 450, + /// 451 Unavailable For Legal Reasons + UnavailableForLegalReasons = 451, + /// 452 (unregistered) + Code452 = 452, + /// 453 (unregistered) + Code453 = 453, + /// 454 (unregistered) + Code454 = 454, + /// 455 (unregistered) + Code455 = 455, + /// 456 (unregistered) + Code456 = 456, + /// 457 (unregistered) + Code457 = 457, + /// 458 (unregistered) + Code458 = 458, + /// 459 (unregistered) + Code459 = 459, + /// 460 (unregistered) + Code460 = 460, + /// 461 (unregistered) + Code461 = 461, + /// 462 (unregistered) + Code462 = 462, + /// 463 (unregistered) + Code463 = 463, + /// 464 (unregistered) + Code464 = 464, + /// 465 (unregistered) + Code465 = 465, + /// 466 (unregistered) + Code466 = 466, + /// 467 (unregistered) + Code467 = 467, + /// 468 (unregistered) + Code468 = 468, + /// 469 (unregistered) + Code469 = 469, + /// 470 (unregistered) + Code470 = 470, + /// 471 (unregistered) + Code471 = 471, + /// 472 (unregistered) + Code472 = 472, + /// 473 (unregistered) + Code473 = 473, + /// 474 (unregistered) + Code474 = 474, + /// 475 (unregistered) + Code475 = 475, + /// 476 (unregistered) + Code476 = 476, + /// 477 (unregistered) + Code477 = 477, + /// 478 (unregistered) + Code478 = 478, + /// 479 (unregistered) + Code479 = 479, + /// 480 (unregistered) + Code480 = 480, + /// 481 (unregistered) + Code481 = 481, + /// 482 (unregistered) + Code482 = 482, + /// 483 (unregistered) + Code483 = 483, + /// 484 (unregistered) + Code484 = 484, + /// 485 (unregistered) + Code485 = 485, + /// 486 (unregistered) + Code486 = 486, + /// 487 (unregistered) + Code487 = 487, + /// 488 (unregistered) + Code488 = 488, + /// 489 (unregistered) + Code489 = 489, + /// 490 (unregistered) + Code490 = 490, + /// 491 (unregistered) + Code491 = 491, + /// 492 (unregistered) + Code492 = 492, + /// 493 (unregistered) + Code493 = 493, + /// 494 (unregistered) + Code494 = 494, + /// 495 (unregistered) + Code495 = 495, + /// 496 (unregistered) + Code496 = 496, + /// 497 (unregistered) + Code497 = 497, + /// 498 (unregistered) + Code498 = 498, + /// 499 (unregistered) + Code499 = 499, + + /// 500 Internal Server Error + InternalServerError = 500, + /// 501 Not Implemented + NotImplemented = 501, + /// 502 Bad Gateway + BadGateway = 502, + /// 503 Service Unavailable + ServiceUnavailable = 503, + /// 504 Gateway Timeout + GatewayTimeout = 504, + /// 505 HTTP Version Not Supported + HttpVersionNotSupported = 505, + /// 506 Variant Also Negotiates + VariantAlsoNegotiates = 506, + /// 507 Insufficient Storage + InsufficientStorage = 507, + /// 508 Loop Detected + LoopDetected = 508, + /// 509 (unregistered) + Code509 = 509, + /// 510 Not Extended + NotExtended = 510, + /// 511 Network Authentication Required + NetworkAuthenticationRequired = 511, + /// 512 (unregistered) + Code512 = 512, + /// 513 (unregistered) + Code513 = 513, + /// 514 (unregistered) + Code514 = 514, + /// 515 (unregistered) + Code515 = 515, + /// 516 (unregistered) + Code516 = 516, + /// 517 (unregistered) + Code517 = 517, + /// 518 (unregistered) + Code518 = 518, + /// 519 (unregistered) + Code519 = 519, + /// 520 (unregistered) + Code520 = 520, + /// 521 (unregistered) + Code521 = 521, + /// 522 (unregistered) + Code522 = 522, + /// 523 (unregistered) + Code523 = 523, + /// 524 (unregistered) + Code524 = 524, + /// 525 (unregistered) + Code525 = 525, + /// 526 (unregistered) + Code526 = 526, + /// 527 (unregistered) + Code527 = 527, + /// 528 (unregistered) + Code528 = 528, + /// 529 (unregistered) + Code529 = 529, + /// 530 (unregistered) + Code530 = 530, + /// 531 (unregistered) + Code531 = 531, + /// 532 (unregistered) + Code532 = 532, + /// 533 (unregistered) + Code533 = 533, + /// 534 (unregistered) + Code534 = 534, + /// 535 (unregistered) + Code535 = 535, + /// 536 (unregistered) + Code536 = 536, + /// 537 (unregistered) + Code537 = 537, + /// 538 (unregistered) + Code538 = 538, + /// 539 (unregistered) + Code539 = 539, + /// 540 (unregistered) + Code540 = 540, + /// 541 (unregistered) + Code541 = 541, + /// 542 (unregistered) + Code542 = 542, + /// 543 (unregistered) + Code543 = 543, + /// 544 (unregistered) + Code544 = 544, + /// 545 (unregistered) + Code545 = 545, + /// 546 (unregistered) + Code546 = 546, + /// 547 (unregistered) + Code547 = 547, + /// 548 (unregistered) + Code548 = 548, + /// 549 (unregistered) + Code549 = 549, + /// 550 (unregistered) + Code550 = 550, + /// 551 (unregistered) + Code551 = 551, + /// 552 (unregistered) + Code552 = 552, + /// 553 (unregistered) + Code553 = 553, + /// 554 (unregistered) + Code554 = 554, + /// 555 (unregistered) + Code555 = 555, + /// 556 (unregistered) + Code556 = 556, + /// 557 (unregistered) + Code557 = 557, + /// 558 (unregistered) + Code558 = 558, + /// 559 (unregistered) + Code559 = 559, + /// 560 (unregistered) + Code560 = 560, + /// 561 (unregistered) + Code561 = 561, + /// 562 (unregistered) + Code562 = 562, + /// 563 (unregistered) + Code563 = 563, + /// 564 (unregistered) + Code564 = 564, + /// 565 (unregistered) + Code565 = 565, + /// 566 (unregistered) + Code566 = 566, + /// 567 (unregistered) + Code567 = 567, + /// 568 (unregistered) + Code568 = 568, + /// 569 (unregistered) + Code569 = 569, + /// 570 (unregistered) + Code570 = 570, + /// 571 (unregistered) + Code571 = 571, + /// 572 (unregistered) + Code572 = 572, + /// 573 (unregistered) + Code573 = 573, + /// 574 (unregistered) + Code574 = 574, + /// 575 (unregistered) + Code575 = 575, + /// 576 (unregistered) + Code576 = 576, + /// 577 (unregistered) + Code577 = 577, + /// 578 (unregistered) + Code578 = 578, + /// 579 (unregistered) + Code579 = 579, + /// 580 (unregistered) + Code580 = 580, + /// 581 (unregistered) + Code581 = 581, + /// 582 (unregistered) + Code582 = 582, + /// 583 (unregistered) + Code583 = 583, + /// 584 (unregistered) + Code584 = 584, + /// 585 (unregistered) + Code585 = 585, + /// 586 (unregistered) + Code586 = 586, + /// 587 (unregistered) + Code587 = 587, + /// 588 (unregistered) + Code588 = 588, + /// 589 (unregistered) + Code589 = 589, + /// 590 (unregistered) + Code590 = 590, + /// 591 (unregistered) + Code591 = 591, + /// 592 (unregistered) + Code592 = 592, + /// 593 (unregistered) + Code593 = 593, + /// 594 (unregistered) + Code594 = 594, + /// 595 (unregistered) + Code595 = 595, + /// 596 (unregistered) + Code596 = 596, + /// 597 (unregistered) + Code597 = 597, + /// 598 (unregistered) + Code598 = 598, + /// 599 (unregistered) + Code599 = 599, +} + +impl StatusCode { + + /// Get the standardised `Reason-Phrase` for this status code. + /// + /// This is mostly here for servers writing responses, but could potentially have application at + /// other times. + /// + /// The reason phrase is defined as being exclusively for human readers. You should avoid + /// deriving any meaning from it at all costs. + /// + /// Bear in mind also that in HTTP/2.0 the reason phrase is abolished from transmission, and so + /// this canonical reason phrase really is the only reason phrase you’ll find. + pub fn canonical_reason(&self) -> Option<&'static str> { + match *self { + Continue => Some("Continue"), + SwitchingProtocols => Some("Switching Protocols"), + Processing => Some("Processing"), + Code103 => None, + Code104 => None, + Code105 => None, + Code106 => None, + Code107 => None, + Code108 => None, + Code109 => None, + Code110 => None, + Code111 => None, + Code112 => None, + Code113 => None, + Code114 => None, + Code115 => None, + Code116 => None, + Code117 => None, + Code118 => None, + Code119 => None, + Code120 => None, + Code121 => None, + Code122 => None, + Code123 => None, + Code124 => None, + Code125 => None, + Code126 => None, + Code127 => None, + Code128 => None, + Code129 => None, + Code130 => None, + Code131 => None, + Code132 => None, + Code133 => None, + Code134 => None, + Code135 => None, + Code136 => None, + Code137 => None, + Code138 => None, + Code139 => None, + Code140 => None, + Code141 => None, + Code142 => None, + Code143 => None, + Code144 => None, + Code145 => None, + Code146 => None, + Code147 => None, + Code148 => None, + Code149 => None, + Code150 => None, + Code151 => None, + Code152 => None, + Code153 => None, + Code154 => None, + Code155 => None, + Code156 => None, + Code157 => None, + Code158 => None, + Code159 => None, + Code160 => None, + Code161 => None, + Code162 => None, + Code163 => None, + Code164 => None, + Code165 => None, + Code166 => None, + Code167 => None, + Code168 => None, + Code169 => None, + Code170 => None, + Code171 => None, + Code172 => None, + Code173 => None, + Code174 => None, + Code175 => None, + Code176 => None, + Code177 => None, + Code178 => None, + Code179 => None, + Code180 => None, + Code181 => None, + Code182 => None, + Code183 => None, + Code184 => None, + Code185 => None, + Code186 => None, + Code187 => None, + Code188 => None, + Code189 => None, + Code190 => None, + Code191 => None, + Code192 => None, + Code193 => None, + Code194 => None, + Code195 => None, + Code196 => None, + Code197 => None, + Code198 => None, + Code199 => None, + + Ok => Some("OK"), + Created => Some("Created"), + Accepted => Some("Accepted"), + NonAuthoritativeInformation => Some("Non-Authoritative Information"), + NoContent => Some("No Content"), + ResetContent => Some("Reset Content"), + PartialContent => Some("Partial Content"), + MultiStatus => Some("Multi-Status"), + AlreadyReported => Some("Already Reported"), + Code209 => None, + Code210 => None, + Code211 => None, + Code212 => None, + Code213 => None, + Code214 => None, + Code215 => None, + Code216 => None, + Code217 => None, + Code218 => None, + Code219 => None, + Code220 => None, + Code221 => None, + Code222 => None, + Code223 => None, + Code224 => None, + Code225 => None, + ImUsed => Some("IM Used"), + Code227 => None, + Code228 => None, + Code229 => None, + Code230 => None, + Code231 => None, + Code232 => None, + Code233 => None, + Code234 => None, + Code235 => None, + Code236 => None, + Code237 => None, + Code238 => None, + Code239 => None, + Code240 => None, + Code241 => None, + Code242 => None, + Code243 => None, + Code244 => None, + Code245 => None, + Code246 => None, + Code247 => None, + Code248 => None, + Code249 => None, + Code250 => None, + Code251 => None, + Code252 => None, + Code253 => None, + Code254 => None, + Code255 => None, + Code256 => None, + Code257 => None, + Code258 => None, + Code259 => None, + Code260 => None, + Code261 => None, + Code262 => None, + Code263 => None, + Code264 => None, + Code265 => None, + Code266 => None, + Code267 => None, + Code268 => None, + Code269 => None, + Code270 => None, + Code271 => None, + Code272 => None, + Code273 => None, + Code274 => None, + Code275 => None, + Code276 => None, + Code277 => None, + Code278 => None, + Code279 => None, + Code280 => None, + Code281 => None, + Code282 => None, + Code283 => None, + Code284 => None, + Code285 => None, + Code286 => None, + Code287 => None, + Code288 => None, + Code289 => None, + Code290 => None, + Code291 => None, + Code292 => None, + Code293 => None, + Code294 => None, + Code295 => None, + Code296 => None, + Code297 => None, + Code298 => None, + Code299 => None, + + MultipleChoices => Some("Multiple Choices"), + MovedPermanently => Some("Moved Permanently"), + Found => Some("Found"), + SeeOther => Some("See Other"), + NotModified => Some("Not Modified"), + UseProxy => Some("Use Proxy"), + SwitchProxy => Some("Switch Proxy"), + TemporaryRedirect => Some("Temporary Redirect"), + PermanentRedirect => Some("Permanent Redirect"), + Code309 => None, + Code310 => None, + Code311 => None, + Code312 => None, + Code313 => None, + Code314 => None, + Code315 => None, + Code316 => None, + Code317 => None, + Code318 => None, + Code319 => None, + Code320 => None, + Code321 => None, + Code322 => None, + Code323 => None, + Code324 => None, + Code325 => None, + Code326 => None, + Code327 => None, + Code328 => None, + Code329 => None, + Code330 => None, + Code331 => None, + Code332 => None, + Code333 => None, + Code334 => None, + Code335 => None, + Code336 => None, + Code337 => None, + Code338 => None, + Code339 => None, + Code340 => None, + Code341 => None, + Code342 => None, + Code343 => None, + Code344 => None, + Code345 => None, + Code346 => None, + Code347 => None, + Code348 => None, + Code349 => None, + Code350 => None, + Code351 => None, + Code352 => None, + Code353 => None, + Code354 => None, + Code355 => None, + Code356 => None, + Code357 => None, + Code358 => None, + Code359 => None, + Code360 => None, + Code361 => None, + Code362 => None, + Code363 => None, + Code364 => None, + Code365 => None, + Code366 => None, + Code367 => None, + Code368 => None, + Code369 => None, + Code370 => None, + Code371 => None, + Code372 => None, + Code373 => None, + Code374 => None, + Code375 => None, + Code376 => None, + Code377 => None, + Code378 => None, + Code379 => None, + Code380 => None, + Code381 => None, + Code382 => None, + Code383 => None, + Code384 => None, + Code385 => None, + Code386 => None, + Code387 => None, + Code388 => None, + Code389 => None, + Code390 => None, + Code391 => None, + Code392 => None, + Code393 => None, + Code394 => None, + Code395 => None, + Code396 => None, + Code397 => None, + Code398 => None, + Code399 => None, + + BadRequest => Some("Bad Request"), + Unauthorized => Some("Unauthorized"), + PaymentRequired => Some("Payment Required"), + Forbidden => Some("Forbidden"), + NotFound => Some("Not Found"), + MethodNotAllowed => Some("Method Not Allowed"), + NotAcceptable => Some("Not Acceptable"), + ProxyAuthenticationRequired => Some("Proxy Authentication Required"), + RequestTimeout => Some("Request Timeout"), + Conflict => Some("Conflict"), + Gone => Some("Gone"), + LengthRequired => Some("Length Required"), + PreconditionFailed => Some("Precondition Failed"), + RequestEntityTooLarge => Some("Request Entity Too Large"), + RequestUriTooLong => Some("Request-URI Too Long"), + UnsupportedMediaType => Some("Unsupported Media Type"), + RequestedRangeNotSatisfiable => Some("Requested Range Not Satisfiable"), + ExpectationFailed => Some("Expectation Failed"), + ImATeapot => Some("I'm a teapot"), + AuthenticationTimeout => Some("Authentication Timeout"), + Code420 => None, + Code421 => None, + UnprocessableEntity => Some("Unprocessable Entity"), + Locked => Some("Locked"), + FailedDependency => Some("Failed Dependency"), + UnorderedCollection => Some("Unordered Collection"), + UpgradeRequired => Some("Upgrade Required"), + Code427 => None, + PreconditionRequired => Some("Precondition Required"), + TooManyRequests => Some("Too Many Requests"), + Code430 => None, + RequestHeaderFieldsTooLarge => Some("Request Header Fields Too Large"), + Code432 => None, + Code433 => None, + Code434 => None, + Code435 => None, + Code436 => None, + Code437 => None, + Code438 => None, + Code439 => None, + Code440 => None, + Code441 => None, + Code442 => None, + Code443 => None, + Code444 => None, + Code445 => None, + Code446 => None, + Code447 => None, + Code448 => None, + Code449 => None, + Code450 => None, + UnavailableForLegalReasons => Some("Unavailable For Legal Reasons"), + Code452 => None, + Code453 => None, + Code454 => None, + Code455 => None, + Code456 => None, + Code457 => None, + Code458 => None, + Code459 => None, + Code460 => None, + Code461 => None, + Code462 => None, + Code463 => None, + Code464 => None, + Code465 => None, + Code466 => None, + Code467 => None, + Code468 => None, + Code469 => None, + Code470 => None, + Code471 => None, + Code472 => None, + Code473 => None, + Code474 => None, + Code475 => None, + Code476 => None, + Code477 => None, + Code478 => None, + Code479 => None, + Code480 => None, + Code481 => None, + Code482 => None, + Code483 => None, + Code484 => None, + Code485 => None, + Code486 => None, + Code487 => None, + Code488 => None, + Code489 => None, + Code490 => None, + Code491 => None, + Code492 => None, + Code493 => None, + Code494 => None, + Code495 => None, + Code496 => None, + Code497 => None, + Code498 => None, + Code499 => None, + + InternalServerError => Some("Internal Server Error"), + NotImplemented => Some("Not Implemented"), + BadGateway => Some("Bad Gateway"), + ServiceUnavailable => Some("Service Unavailable"), + GatewayTimeout => Some("Gateway Timeout"), + HttpVersionNotSupported => Some("HTTP Version Not Supported"), + VariantAlsoNegotiates => Some("Variant Also Negotiates"), + InsufficientStorage => Some("Insufficient Storage"), + LoopDetected => Some("Loop Detected"), + Code509 => None, + NotExtended => Some("Not Extended"), + NetworkAuthenticationRequired => Some("Network Authentication Required"), + Code512 => None, + Code513 => None, + Code514 => None, + Code515 => None, + Code516 => None, + Code517 => None, + Code518 => None, + Code519 => None, + Code520 => None, + Code521 => None, + Code522 => None, + Code523 => None, + Code524 => None, + Code525 => None, + Code526 => None, + Code527 => None, + Code528 => None, + Code529 => None, + Code530 => None, + Code531 => None, + Code532 => None, + Code533 => None, + Code534 => None, + Code535 => None, + Code536 => None, + Code537 => None, + Code538 => None, + Code539 => None, + Code540 => None, + Code541 => None, + Code542 => None, + Code543 => None, + Code544 => None, + Code545 => None, + Code546 => None, + Code547 => None, + Code548 => None, + Code549 => None, + Code550 => None, + Code551 => None, + Code552 => None, + Code553 => None, + Code554 => None, + Code555 => None, + Code556 => None, + Code557 => None, + Code558 => None, + Code559 => None, + Code560 => None, + Code561 => None, + Code562 => None, + Code563 => None, + Code564 => None, + Code565 => None, + Code566 => None, + Code567 => None, + Code568 => None, + Code569 => None, + Code570 => None, + Code571 => None, + Code572 => None, + Code573 => None, + Code574 => None, + Code575 => None, + Code576 => None, + Code577 => None, + Code578 => None, + Code579 => None, + Code580 => None, + Code581 => None, + Code582 => None, + Code583 => None, + Code584 => None, + Code585 => None, + Code586 => None, + Code587 => None, + Code588 => None, + Code589 => None, + Code590 => None, + Code591 => None, + Code592 => None, + Code593 => None, + Code594 => None, + Code595 => None, + Code596 => None, + Code597 => None, + Code598 => None, + Code599 => None, + } + } + + /// Determine the class of a status code, based on its first digit. + pub fn class(&self) -> StatusClass { + let code = *self as u16; // Range of possible values: 100..599. + // We could match 100..199 &c., but this way we avoid unreachable!() at the end. + if code < 200 { + Informational + } else if code < 300 { + Success + } else if code < 400 { + Redirection + } else if code < 500 { + ClientError + } else { + ServerError + } + } +} + +impl fmt::Unsigned for StatusCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::secret_unsigned(&(*self as u16), f) + } +} + +/// Formats the status code, *including* the canonical reason. +/// +/// ```rust +/// # use hyper::status::{ImATeapot, Code123}; +/// assert_eq!(format!("{}", ImATeapot).as_slice(), +/// "418 I'm a teapot"); +/// assert_eq!(format!("{}", Code123).as_slice(), +/// "123 "); +/// ``` +/// +/// If you wish to just include the number, use `Unsigned` instead (`{:u}`). +impl fmt::Show for StatusCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", *self as u16, + self.canonical_reason().unwrap_or("")) + } +} + +// Specified manually because the codegen for derived is slow (at the time of writing on the machine +// of writing, 1.2 seconds) and verbose (though the optimiser cuts it down to size). +impl PartialEq for StatusCode { + #[inline] + fn eq(&self, other: &StatusCode) -> bool { + *self as u16 == *other as u16 + } +} + +impl Eq for StatusCode {} + +// Ditto (though #[deriving(Clone)] only takes about 0.4 seconds). +impl Clone for StatusCode { + #[inline] + fn clone(&self) -> StatusCode { + *self + } +} + +// Of the other common derivable traits, I didn’t measure them, but I guess they would be slow too. + +impl FromPrimitive for StatusCode { + fn from_i64(n: i64) -> Option { + if n < 100 || n > 599 { + None + } else { + Some(unsafe { transmute::(n as u16) }) + } + } + + fn from_u64(n: u64) -> Option { + if n < 100 || n > 599 { + None + } else { + Some(unsafe { transmute::(n as u16) }) + } + } +} + +impl PartialOrd for StatusCode { + #[inline] + fn partial_cmp(&self, other: &StatusCode) -> Option { + (*self as u16).partial_cmp(&(*other as u16)) + } +} + +impl Ord for StatusCode { + #[inline] + fn cmp(&self, other: &StatusCode) -> Ordering { + if *self < *other { + Less + } else if *self > *other { + Greater + } else { + Equal + } + } +} + +impl ToPrimitive for StatusCode { + fn to_i64(&self) -> Option { + Some(*self as i64) + } + + fn to_u64(&self) -> Option { + Some(*self as u64) + } +} + +/// The class of an HTTP `Status-Code`. +/// +/// [RFC 2616, section 6.1.1 (Status Code and Reason +/// Phrase)](https://tools.ietf.org/html/rfc2616#section-6.1.1): +/// +/// > The first digit of the Status-Code defines the class of response. The +/// > last two digits do not have any categorization role. +/// > +/// > ... +/// > +/// > HTTP status codes are extensible. HTTP applications are not required +/// > to understand the meaning of all registered status codes, though such +/// > understanding is obviously desirable. However, applications MUST +/// > understand the class of any status code, as indicated by the first +/// > digit, and treat any unrecognized response as being equivalent to the +/// > x00 status code of that class, with the exception that an +/// > unrecognized response MUST NOT be cached. For example, if an +/// > unrecognized status code of 431 is received by the client, it can +/// > safely assume that there was something wrong with its request and +/// > treat the response as if it had received a 400 status code. In such +/// > cases, user agents SHOULD present to the user the entity returned +/// > with the response, since that entity is likely to include human- +/// > readable information which will explain the unusual status. +/// +/// This can be used in cases where a status code’s meaning is unknown, also, +/// to get the appropriate *category* of status. +/// +/// For HTTP/2.0, the 1xx Informational class is invalid. +#[deriving(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum StatusClass { + /// 1xx: Informational - Request received, continuing process + Informational = 100, + + /// 2xx: Success - The action was successfully received, understood, and accepted + Success = 200, + + /// 3xx: Redirection - Further action must be taken in order to complete the request + Redirection = 300, + + /// 4xx: Client Error - The request contains bad syntax or cannot be fulfilled + ClientError = 400, + + /// 5xx: Server Error - The server failed to fulfill an apparently valid request + ServerError = 500, +} + +impl StatusClass { + /// Get the default status code for the class. + /// + /// This produces the x00 status code; thus, for `ClientError` (4xx), for example, this will + /// produce `BadRequest` (400): + /// + /// ```rust + /// # use hyper::status::{ClientError, BadRequest}; + /// assert_eq!(ClientError.default_code(), BadRequest); + /// ``` + /// + /// The use for this is outlined in [RFC 2616, section 6.1.1 (Status Code and Reason + /// Phrase)](https://tools.ietf.org/html/rfc2616#section-6.1.1): + /// + /// > HTTP status codes are extensible. HTTP applications are not required + /// > to understand the meaning of all registered status codes, though such + /// > understanding is obviously desirable. However, applications MUST + /// > understand the class of any status code, as indicated by the first + /// > digit, and treat any unrecognized response as being equivalent to the + /// > x00 status code of that class, with the exception that an + /// > unrecognized response MUST NOT be cached. For example, if an + /// > unrecognized status code of 431 is received by the client, it can + /// > safely assume that there was something wrong with its request and + /// > treat the response as if it had received a 400 status code. In such + /// > cases, user agents SHOULD present to the user the entity returned + /// > with the response, since that entity is likely to include human- + /// > readable information which will explain the unusual status. + /// + /// This is demonstrated thusly (I’ll use 432 rather than 431 as 431 *is* now in use): + /// + /// ```rust + /// # use hyper::status::{Code432, BadRequest}; + /// // Suppose we have received this status code. + /// let status = Code432; + /// + /// // Uh oh! Don’t know what to do with it. + /// // Let’s fall back to the default: + /// let status = status.class().default_code(); + /// + /// // And look! That is 400 Bad Request. + /// assert_eq!(status, BadRequest); + /// // So now let’s treat it as that. + /// ``` + #[inline] + pub fn default_code(&self) -> StatusCode { + unsafe { transmute::(*self) } + } +} + +impl ToPrimitive for StatusClass { + fn to_i64(&self) -> Option { + Some(*self as i64) + } + + fn to_u64(&self) -> Option { + Some(*self as u64) + } +} + diff --git a/src/uri.rs b/src/uri.rs new file mode 100644 index 00000000..bb72c5f9 --- /dev/null +++ b/src/uri.rs @@ -0,0 +1,45 @@ +//! # RequestUri +use url::Url; + +/// The Request-URI of a Request's StartLine. +/// +/// From Section 5.3, Request Target: +/// > Once an inbound connection is obtained, the client sends an HTTP +/// > request message (Section 3) with a request-target derived from the +/// > target URI. There are four distinct formats for the request-target, +/// > depending on both the method being requested and whether the request +/// > is to a proxy. +/// > +/// > request-target = origin-form +/// > / absolute-form +/// > / authority-form +/// > / asterisk-form +#[deriving(Show, PartialEq, Clone)] +pub enum RequestUri { + /// The most common request target, an absolute path and optional query. + /// + /// For example, the line `GET /where?q=now HTTP/1.1` would parse the URI + /// as `AbsolutePath("/where?q=now".to_string())`. + AbsolutePath(String), + + /// An absolute URI. Used in conjunction with proxies. + /// + /// > When making a request to a proxy, other than a CONNECT or server-wide + /// > OPTIONS request (as detailed below), a client MUST send the target + /// > URI in absolute-form as the request-target. + /// + /// An example StartLine with an `AbsoluteUri` would be + /// `GET http://www.example.org/pub/WWW/TheProject.html HTTP/1.1`. + AbsoluteUri(Url), + + /// The authority form is only for use with `CONNECT` requests. + /// + /// An example StartLine: `CONNECT www.example.com:80 HTTP/1.1`. + Authority(String), + + /// The star is used to target the entire server, instead of a specific resource. + /// + /// This is only used for a server-wide `OPTIONS` request. + Star, +} + diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 00000000..43959820 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,29 @@ +//! # HTTP Versions +//! +//! Instead of relying on typo-prone Strings, use expected HTTP versions as +//! the `HttpVersion` enum. +use std::fmt; + +/// Represents a version of the HTTP spec. +#[deriving(PartialEq, PartialOrd)] +pub enum HttpVersion { + /// `HTTP/0.9` + Http09, + /// `HTTP/1.0` + Http10, + /// `HTTP/1.1` + Http11, + /// `HTTP/2.0` + Http20 +} + +impl fmt::Show for HttpVersion { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Http09 => "HTTP/0.9", + Http10 => "HTTP/1.0", + Http11 => "HTTP/1.1", + Http20 => "HTTP/2.0", + }.fmt(fmt) + } +}