diff --git a/src/examples/client/main.rs b/src/examples/client/main.rs index 7efdc33..9f5b8c0 100644 --- a/src/examples/client/main.rs +++ b/src/examples/client/main.rs @@ -1,17 +1,22 @@ extern mod http; use http::client::RequestWriter; +use http::client::ResponseReader; use http::method::Get; use http::headers::HeaderEnum; use std::os; use std::str; use std::io::Reader; +use std::io::net::tcp::TcpStream; + +// API Examples +use http::client::api::{get, RequestArgs}; fn main() { format!("{}", Get); let args = os::args(); match args.len() { 0 => unreachable!(), - 2 => make_and_print_request(args[1]), + 2 => get_example(args[1]), _ => { println!("Usage: {} URL", args[0]); return; @@ -19,9 +24,10 @@ fn main() { }; } -fn make_and_print_request(url: ~str) { - let request = RequestWriter::new(Get, from_str(url).expect("Invalid URL :-(")); - +// NOTE(flaper87): Consider moving this to the +// request sender and print it if built in debug +// mode. +fn debug_request(request: &RequestWriter) { println("Request"); println("======="); println(""); @@ -29,23 +35,56 @@ fn make_and_print_request(url: ~str) { println!("Remote address: {:?}", request.remote_addr); println!("Method: {}", request.method); println("Headers:"); - for header in request.headers.iter() { - println!(" - {}: {}", header.header_name(), header.header_value()); - } +} +// NOTE(flaper87): Consider moving this to the +// request sender and print it if built in debug +// mode. +fn debug_response(response: &ResponseReader) { println(""); println("Response"); println("========"); println(""); - let mut response = match request.read_response() { - Ok(response) => response, - Err(_request) => fail!("This example can progress no further with no response :-("), - }; println!("Status: {}", response.status); println("Headers:"); for header in response.headers.iter() { println!(" - {}: {}", header.header_name(), header.header_value()); } +} + +fn get_example(url: ~str) { + let params = ~[(~"test", ~"value")]; + let args = RequestArgs{params: Some(params), headers: None, data: None}; + let response = get(url, Some(args)); + + let mut response = match response { + Ok(response) => response, + Err(_request) => fail!("This example can progress no further with no response :-("), + }; + + debug_response(&response); + + print("\n"); + println("Response:"); + let body = response.read_to_end(); + println(str::from_utf8_slice(body)); +} + +fn make_and_print_request(url: ~str) { + let request = RequestWriter::new(Get, from_str(url).expect("Invalid URL :-(")); + + debug_request(&request); + for header in request.headers.iter() { + println!(" - {}: {}", header.header_name(), header.header_value()); + } + + let mut response = match request.read_response() { + Ok(response) => response, + Err(_request) => fail!("This example can progress no further with no response :-("), + }; + + debug_response(&response); + println("Body:"); let body = response.read_to_end(); println(str::from_utf8_slice(body)); diff --git a/src/http/client/api.rs b/src/http/client/api.rs new file mode 100644 index 0000000..404cad0 --- /dev/null +++ b/src/http/client/api.rs @@ -0,0 +1,94 @@ +use std::default::Default; +use std::io::net::tcp::TcpStream; + +use method; +use headers::request::HeaderCollection; + +use client::request::RequestWriter; +use client::response::ResponseReader; +use extra::url::{Url, Query}; + +pub struct RequestArgs { + + // Request data + data: Option<~[u8]>, + + // Query Parameters + params: Option, + + // Request Headers + headers: Option<~HeaderCollection>, +} + +impl Default for RequestArgs { + + fn default() -> RequestArgs { + RequestArgs{data: None, params: None, headers: None} + } +} + +// Need a fix for https://github.com/mozilla/rust/issues/9056 +// before we can use this. +//pub static DEFAULT_ARGS: RequestArgs = RequestArgs{params: None, +// headers: None}; + +// TODO: Implement a Response trait + +pub fn request(method: method::Method, url: ~str, args: Option) + -> Result, RequestWriter>{ + + let default = args.unwrap_or_default(); + + // Push all query params to the URL. + let mut url: Url = FromStr::from_str(url).expect("Uh oh, that's *really* badly broken!"); + + if default.params.is_some() { + url.query.push_all(*(default.params.get_ref())); + } + + // At this point, we're ready to finally send + // the request. First thing is to write headers, + // then the request data and later get the response + // from the server. + let mut request = RequestWriter::new(method, url); + + // Write data if there's some + if default.data.is_some() { + request.send(*(default.data.get_ref())); + } + + // This will flush the request's + // stream and get the response from + // the server. + request.read_response() +} + +pub fn get(url: ~str, args: Option) + -> Result, RequestWriter> { + + request(method::Get, url, args) +} + +pub fn post(url: ~str, args: Option) + -> Result, RequestWriter> { + + request(method::Post, url, args) +} + +pub fn patch(url: ~str, args: Option) + -> Result, RequestWriter> { + + request(method::Patch, url, args) +} + +pub fn put(url: ~str, args: Option) + -> Result, RequestWriter> { + + request(method::Put, url, args) +} + +pub fn delete(url: ~str, args: Option) + -> Result, RequestWriter> { + + request(method::Delete, url, args) +} diff --git a/src/http/client/mod.rs b/src/http/client/mod.rs index c3519fe..85c514b 100644 --- a/src/http/client/mod.rs +++ b/src/http/client/mod.rs @@ -18,5 +18,6 @@ possible, but it's not elegant convenient yet. (Most notably, no transfer-encodi pub use self::request::RequestWriter; pub use self::response::ResponseReader; +pub mod api; pub mod request; pub mod response; diff --git a/src/http/client/request.rs b/src/http/client/request.rs index 1911dee..105627f 100644 --- a/src/http/client/request.rs +++ b/src/http/client/request.rs @@ -206,6 +206,9 @@ impl RequestWriter { if self.url.query.len() > 0 { "?" } else { "" }, url::query_to_str(&self.url.query)); + // `write_all` adds '\r\n' at the end, no + // need to terminate the headers section + // here. self.headers.write_all(&mut self.stream); self.headers_written = true; } @@ -224,14 +227,40 @@ impl RequestWriter { None => Err(self), // TODO: raise condition } } + + /** + * Send data to the remote server. + * This method appends Content-Length + * to headers and sends them. If headers + * where already sent, it will send data + * without the Content-Length. + * TODO: Implement chunked request, perhaps + * in a `send_chunked` method. + */ + pub fn send(&mut self, buf: &[u8]) { + + // NOTE: Should we make this fail? + // If 'Content-Length' is not sent + // some servers won't read the request + // body. + if !self.headers_written { + self.headers.content_length = Some(buf.len()); + self.write_headers(); + } + self.write(buf); + } } /// Write the request body. Note that any calls to `write()` will cause the headers to be sent. impl Writer for RequestWriter { fn write(&mut self, buf: &[u8]) { - if (!self.headers_written) { - self.write_headers(); - } + // No data must be sent before + // sending headers. Let's make + // sure that's the case. + self.try_write_headers(); + + // Now we're good to send + // some data. self.stream.write(buf); } diff --git a/src/http/client/response.rs b/src/http/client/response.rs index 8dae3ae..738b72c 100644 --- a/src/http/client/response.rs +++ b/src/http/client/response.rs @@ -140,6 +140,10 @@ impl ResponseReader { headers: headers, }) } + + pub fn get_content(&mut self) -> ~[u8] { + self.stream.read_to_end() + } } impl Reader for ResponseReader {