implementation

This commit is contained in:
Sean McArthur
2014-09-01 18:39:24 -07:00
parent 8865516816
commit c905111f8c
18 changed files with 3744 additions and 0 deletions

14
.travis.yml Normal file
View File

@@ -0,0 +1,14 @@
language: rust
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
cargo doc &&
echo '<meta http-equiv=refresh content=0;url=hyper/index.html>' > 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=

View File

@@ -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

41
examples/client.rs Normal file
View File

@@ -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 <url>");
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)
};
}

40
examples/server.rs Normal file
View File

@@ -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);
}

22
src/client/mod.rs Normal file
View File

@@ -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> {
request(Get, url)
}
/// Create a client request.
pub fn request(method: Method, url: Url) -> HttpResult<Request> {
Request::new(method, url)
}

106
src/client/request.rs Normal file
View File

@@ -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<Request> {
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<Response> {
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()
}
}

71
src/client/response.rs Normal file
View File

@@ -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<TcpStream>,
}
impl Response {
/// Creates a new response from a server.
pub fn new(mut tcp: TcpStream) -> HttpResult<Response> {
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::<TransferEncoding>() {
match headers.get_ref::<TransferEncoding>() {
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::<ContentLength>() {
match headers.get_ref::<ContentLength>() {
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<uint> {
self.body.read(buf)
}
}

532
src/header.rs Normal file
View File

@@ -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<Self>) -> 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<u8>]) -> Option<Self>;
/// 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>(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<T: 'static>(self) -> &'a T;
}
impl<'a> UncheckedAnyRefExt<'a> for &'a Header {
#[inline]
unsafe fn downcast_ref_unchecked<T: 'static>(self) -> &'a T {
let to: TraitObject = transmute_copy(&self);
transmute(to.data)
}
}
fn header_name<T: Header>() -> SendStr {
let name = Header::header_name(None::<T>);
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<SendStr, Item>
}
impl Headers {
/// Creates a new, empty headers map.
pub fn new() -> Headers {
Headers {
data: HashMap::new()
}
}
#[doc(hidden)]
pub fn from_raw<R: Reader>(rdr: &mut R) -> HttpResult<Headers> {
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<H: Header + 'static>(&mut self, value: H) {
self.data.insert(header_name::<H>(), 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::<ContentType>();
/// ```
pub fn get<H: Header + Clone + 'static>(&mut self) -> Option<H> {
self.get_ref().map(|v: &H| v.clone())
}
/// Get a reference to the header field's value, if it exists.
pub fn get_ref<H: Header + 'static>(&mut self) -> Option<&H> {
self.data.find_mut(&header_name::<H>()).and_then(|item| {
debug!("get_ref, name={}, val={}", header_name::<H>(), item);
let header = match *item {
Raw(ref raw) => match Header::parse_header(raw.as_slice()) {
Some::<H>(h) => {
h
},
None => return None
},
Typed(..) => return Some(item)
};
*item = Typed(box header as Box<Header + 'static>);
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::<ContentType>();
/// ```
pub fn has<H: Header + 'static>(&self) -> bool {
self.data.contains_key(&header_name::<H>())
}
/// Removes a header from the map, if one existed. Returns true if a header has been removed.
pub fn remove<H: Header>(&mut self) -> bool {
self.data.remove(&Header::header_name(None::<H>))
}
/// 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<Vec<u8>>),
Typed(Box<Header + 'static>)
}
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<Host>) -> SendStr {
Slice("host")
}
fn parse_header(raw: &[Vec<u8>]) -> Option<Host> {
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<ContentLength>) -> SendStr {
Slice("content-length")
}
fn parse_header(raw: &[Vec<u8>]) -> Option<ContentLength> {
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<ContentType>) -> SendStr {
Slice("content-type")
}
fn parse_header(raw: &[Vec<u8>]) -> Option<ContentType> {
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<Mime>);
impl Header for Accept {
fn header_name(_: Option<Accept>) -> SendStr {
Slice("accept")
}
fn parse_header(_raw: &[Vec<u8>]) -> Option<Accept> {
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<Connection> {
debug!("Connection::from_str =? {}", s);
match s {
"keep-alive" => Some(KeepAlive),
"close" => Some(Close),
_ => None
}
}
}
impl Header for Connection {
fn header_name(_: Option<Connection>) -> SendStr {
Slice("connection")
}
fn parse_header(raw: &[Vec<u8>]) -> Option<Connection> {
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<Encoding>);
/// 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<Encoding> {
match s {
"chunked" => Some(Chunked),
_ => None
}
}
}
impl Header for TransferEncoding {
fn header_name(_: Option<TransferEncoding>) -> SendStr {
Slice("transfer-encoding")
}
fn parse_header(raw: &[Vec<u8>]) -> Option<TransferEncoding> {
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<UserAgent>) -> SendStr {
Slice("user-agent")
}
fn parse_header(raw: &[Vec<u8>]) -> Option<UserAgent> {
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<T: FromStr>(raw: &[Vec<u8>]) -> Option<T> {
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![]))));
}
}

77
src/lib.rs Normal file
View File

@@ -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<T> = Result<T, HttpError>;
/// 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),
}

99
src/method.rs Normal file
View File

@@ -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<Method> {
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)
}
}

1
src/mime.rs Normal file
View File

@@ -0,0 +1 @@

660
src/rfc7230.rs Normal file
View File

@@ -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<R> {
/// 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<uint>),
/// 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<R: Reader> Reader for HttpReader<R> {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
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<R: Reader>(rdr: &mut R) -> IoResult<uint> {
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<R: Reader>(stream: &mut R) -> HttpResult<method::Method> {
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<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> {
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<R: Reader>(stream: &mut R) -> HttpResult<HttpVersion> {
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<u8>, Vec<u8>);
/// 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<R: Reader>(stream: &mut R) -> HttpResult<Option<RawHeaderLine>> {
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<R: Reader>(stream: &mut R) -> HttpResult<RequestLine> {
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<R: Reader>(stream: &mut R) -> HttpResult<StatusLine> {
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<R: Reader>(stream: &mut R) -> HttpResult<status::StatusCode> {
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<u8>, 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<RequestUri>) {
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<HttpVersion>) {
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<status::StatusCode>) {
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<Option<RawHeaderLine>>) {
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)));
}
}

83
src/server/mod.rs Normal file
View File

@@ -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<H: Handler>(&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)
}
}

72
src/server/request.rs Normal file
View File

@@ -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<TcpStream>
}
impl Request {
/// Create a new Request, reading the StartLine and Headers so they are
/// immediately useful.
pub fn new(mut tcp: TcpStream) -> HttpResult<Request> {
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::<ContentLength>() {
match headers.get_ref::<ContentLength>() {
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<uint> {
self.body.read(buf)
}
}

81
src/server/response.rs Normal file
View File

@@ -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()
}
}

1769
src/status.rs Normal file

File diff suppressed because it is too large Load Diff

45
src/uri.rs Normal file
View File

@@ -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,
}

29
src/version.rs Normal file
View File

@@ -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)
}
}