From d5c6f33c34c4320fc7165a107fe8609a3ce60a0c Mon Sep 17 00:00:00 2001 From: Jonathan Reem Date: Wed, 10 Sep 2014 12:34:05 -0700 Subject: [PATCH] Update the client API to statically track headers and move constructors The client Request now uses the same system as a server Response to track the write status of headers, and the API has been updated accordingly. Additionally, the Request constructors have been moved onto the Request object instead of being top-level hyper functions, as this better namespaces the client and Server. --- benches/client.rs | 12 ++--- benches/server.rs | 4 +- examples/client.rs | 10 ++-- src/client/mod.rs | 31 ------------ src/client/request.rs | 115 +++++++++++++++++++++++++++++++----------- src/lib.rs | 1 - 6 files changed, 100 insertions(+), 73 deletions(-) diff --git a/benches/client.rs b/benches/client.rs index 805b3daa..59380b56 100644 --- a/benches/client.rs +++ b/benches/client.rs @@ -66,14 +66,14 @@ fn bench_hyper(b: &mut test::Bencher) { let s = format!("http://{}/", listening.sockets[0]); let url = s.as_slice(); b.iter(|| { - let mut req = hyper::get(hyper::Url::parse(url).unwrap()).unwrap(); - req.headers.set(Foo); + let mut req = hyper::client::Request::get(hyper::Url::parse(url).unwrap()).unwrap(); + req.headers_mut().set(Foo); - req - .send().unwrap() - .read_to_string().unwrap() + req.start().unwrap() + .send().unwrap() + .read_to_string().unwrap() }); -listening.close().unwrap() + listening.close().unwrap() } #[bench] diff --git a/benches/server.rs b/benches/server.rs index d3ce62f4..5d5f0c11 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -13,8 +13,8 @@ use http::server::Server; static phrase: &'static [u8] = b"Benchmarking hyper vs others!"; fn request(url: hyper::Url) { - let req = hyper::get(url).unwrap(); - req.send().unwrap().read_to_string().unwrap(); + let req = hyper::client::Request::get(url).unwrap(); + req.start().unwrap().send().unwrap().read_to_string().unwrap(); } fn hyper_handle(mut incoming: hyper::server::Incoming) { diff --git a/examples/client.rs b/examples/client.rs index f436eacb..e728cd6c 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -5,6 +5,7 @@ use std::io::stdout; use std::io::util::copy; use hyper::Url; +use hyper::client::Request; fn main() { let args = os::args(); @@ -25,12 +26,15 @@ fn main() { }; - let req = match hyper::get(url) { + let req = match Request::get(url) { Ok(req) => req, Err(err) => fail!("Failed to connect: {}", err) }; - let mut res = req.send().unwrap(); - + + let mut res = req + .start().ok().expect("Error writing Headers.") + .send().ok().expect("Error sending Request."); + println!("Response: {}", res.status); println!("{}", res.headers); match copy(&mut res, &mut stdout()) { diff --git a/src/client/mod.rs b/src/client/mod.rs index 4a8d0e3b..1b24c7b7 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,38 +1,7 @@ //! HTTP Client -use url::Url; - -use method::{Get, Head, Post, Delete, Method}; - pub use self::request::Request; pub use self::response::Response; -use {HttpResult}; pub mod request; pub mod response; - -/// Create a GET client request. -pub fn get(url: Url) -> HttpResult { - request(Get, url) -} - -/// Create a HEAD client request. -pub fn head(url: Url) -> HttpResult { - request(Head, url) -} - -/// Create a POST client request. -pub fn post(url: Url) -> HttpResult { - // TODO: should this accept a Body parameter? or just let user `write` to the request? - request(Post, url) -} - -/// Create a DELETE client request. -pub fn delete(url: Url) -> HttpResult { - request(Delete, url) -} - -/// Create a client request. -pub fn request(method: Method, url: Url) -> HttpResult { - Request::new(method, url) -} diff --git a/src/client/request.rs b/src/client/request.rs index 89ea0131..7b575631 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -3,34 +3,58 @@ use std::io::{BufferedWriter, IoResult}; use url::Url; -use method; +use method::{mod, Get, Post, Delete, Put, Patch, Head, Options}; use header::Headers; use header::common::Host; use net::{NetworkStream, HttpStream}; use http::LINE_ENDING; use version; use {HttpResult, HttpUriError}; -use super::{Response}; +use super::Response; +/// The write-status of a Request that has not had its headers written. +pub struct Fresh; + +/// The write-status of a Request that has had its headers written. +pub struct Streaming; + +/// The write-status of a Request +pub trait WriteStatus: Private {} +impl WriteStatus for Fresh {} +impl WriteStatus for Streaming {} + +// Only Fresh and Streaming can be WriteStatus +#[doc(hidden)] +trait Private {} +impl Private for Fresh {} +impl Private for Streaming {} /// 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, +pub struct Request { /// The target URI for this request. pub url: Url, + /// The HTTP version of this request. pub version: version::HttpVersion, - headers_written: bool, + body: BufferedWriter>, + headers: Headers, + method: method::Method, } -impl Request { +impl Request { + /// Read the Request headers. + #[inline] + pub fn headers(&self) -> &Headers { &self.headers } + /// Read the Request method. + #[inline] + pub fn method(&self) -> method::Method { self.method.clone() } +} + +impl Request { /// Create a new client request. - pub fn new(method: method::Method, url: Url) -> HttpResult { + pub fn new(method: method::Method, url: Url) -> HttpResult> { debug!("{} {}", method, url); let host = match url.serialize_host() { Some(host) => host, @@ -47,38 +71,76 @@ impl Request { let stream = BufferedWriter::new(stream.abstract()); 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; + /// Create a new GET request. + #[inline] + pub fn get(url: Url) -> HttpResult> { Request::new(Get, url) } + /// Create a new POST request. + #[inline] + pub fn post(url: Url) -> HttpResult> { Request::new(Post, url) } + + /// Create a new DELETE request. + #[inline] + pub fn delete(url: Url) -> HttpResult> { Request::new(Delete, url) } + + /// Create a new PUT request. + #[inline] + pub fn put(url: Url) -> HttpResult> { Request::new(Put, url) } + + /// Create a new PATCH request. + #[inline] + pub fn patch(url: Url) -> HttpResult> { Request::new(Patch, url) } + + /// Create a new HEAD request. + #[inline] + pub fn head(url: Url) -> HttpResult> { Request::new(Head, url) } + + /// Create a new OPTIONS request. + #[inline] + pub fn options(url: Url) -> HttpResult> { Request::new(Options, url) } + + /// Consume a Fresh Request, writing the headers and method, + /// returning a Streaming Request. + pub fn start(mut self) -> HttpResult> { 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)); + try_io!(write!(self.body, "{} {} {}", self.method, uri, self.version)) + try_io!(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)); + try_io!(write!(self.body, "{}: {}", name, header)); + try_io!(self.body.write(LINE_ENDING)); } - self.body.write(LINE_ENDING) + try_io!(self.body.write(LINE_ENDING)); + + Ok(Request { + method: self.method, + headers: self.headers, + url: self.url, + version: self.version, + body: self.body + }) } + /// Get a mutable reference to the Request headers. + #[inline] + pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers } +} + +impl Request { /// Completes writing the request, and returns a response to read from. /// /// Consumes the Request. @@ -89,19 +151,12 @@ impl Request { } } - -impl Writer for Request { +impl Writer for Request { fn write(&mut self, msg: &[u8]) -> IoResult<()> { - if !self.headers_written { - try!(self.write_head()); - } self.body.write(msg) } fn flush(&mut self) -> IoResult<()> { - if !self.headers_written { - try!(self.write_head()); - } self.body.flush() } } diff --git a/src/lib.rs b/src/lib.rs index c74b3c61..a4d26cba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ extern crate intertwine; pub use std::io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port}; pub use mimewrapper::mime; pub use url::Url; -pub use client::{get, head, post, delete, request}; pub use method::{Get, Head, Post, Delete}; pub use status::{Ok, BadRequest, NotFound}; pub use server::Server;