implementation
This commit is contained in:
14
.travis.yml
Normal file
14
.travis.yml
Normal 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=
|
||||
@@ -1,5 +1,7 @@
|
||||
# hyper
|
||||
|
||||
[](https://travis-ci.org/seanmonstar/hyper)
|
||||
|
||||
An HTTP library for Rust.
|
||||
|
||||
## License
|
||||
|
||||
41
examples/client.rs
Normal file
41
examples/client.rs
Normal 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
40
examples/server.rs
Normal 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
22
src/client/mod.rs
Normal 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
106
src/client/request.rs
Normal 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
71
src/client/response.rs
Normal 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
532
src/header.rs
Normal 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
77
src/lib.rs
Normal 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
99
src/method.rs
Normal 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
1
src/mime.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
660
src/rfc7230.rs
Normal file
660
src/rfc7230.rs
Normal 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
83
src/server/mod.rs
Normal 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
72
src/server/request.rs
Normal 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
81
src/server/response.rs
Normal 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
1769
src/status.rs
Normal file
File diff suppressed because it is too large
Load Diff
45
src/uri.rs
Normal file
45
src/uri.rs
Normal 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
29
src/version.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user