diff --git a/examples/from_request/Cargo.toml b/examples/from_request/Cargo.toml index 204b79fa..087c7620 100644 --- a/examples/from_request/Cargo.toml +++ b/examples/from_request/Cargo.toml @@ -7,3 +7,6 @@ workspace = "../../" [dependencies] rocket = { path = "../../lib" } rocket_codegen = { path = "../../codegen" } + +[dev-dependencies] +rocket = { path = "../../lib", features = ["testing"] } diff --git a/examples/from_request/src/main.rs b/examples/from_request/src/main.rs index 5807a64d..c2da67f8 100644 --- a/examples/from_request/src/main.rs +++ b/examples/from_request/src/main.rs @@ -30,3 +30,35 @@ fn header_count(header_count: HeaderCount) -> String { fn main() { rocket::ignite().mount("/", routes![header_count]).launch() } + +#[cfg(test)] +mod test { + use super::rocket; + use rocket::testing::MockRequest; + use rocket::http::Method::*; + + fn test_header_count<'h>(headers: &[(&'h str, &'h str)]) { + let rocket = rocket::ignite().mount("/", routes![super::header_count]); + let req = MockRequest::new(Get, "/").headers(headers); + let result = req.dispatch_with(&rocket); + assert_eq!(result.unwrap(), + format!("Your request contained {} headers!", headers.len())); + } + + #[test] + fn test_n_headers() { + for i in 0..50 { + let mut headers = vec![]; + for j in 0..i { + let string = format!("{}", j); + headers.push((string.clone(), string)); + } + + let h_strs: Vec<_> = headers.iter() + .map(|&(ref a, ref b)| (a.as_str(), b.as_str())) + .collect(); + + test_header_count(&h_strs); + } + } +} diff --git a/examples/testing/Cargo.toml b/examples/testing/Cargo.toml index dab28d40..49129bb4 100644 --- a/examples/testing/Cargo.toml +++ b/examples/testing/Cargo.toml @@ -7,3 +7,6 @@ workspace = "../../" [dependencies] rocket = { path = "../../lib" } rocket_codegen = { path = "../../codegen" } + +[dev-dependencies] +rocket = { path = "../../lib", features = ["testing"] } diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index 52cc15fb..2ada1e79 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -14,21 +14,15 @@ fn main() { #[cfg(test)] mod test { - use super::rocket::{Rocket, Request}; - use super::rocket::http::Method; - - fn run_test(f: F) where F: Fn(Rocket) { - let rocket = Rocket::ignite().mount("/", routes![super::hello]); - f(rocket); - } + use super::rocket; + use rocket::testing::MockRequest; + use rocket::http::Method::*; #[test] fn test_hello() { - run_test(|_rocket: Rocket| { - let _req = Request::mock(Method::Get, "/"); - // TODO: Allow something like this: - // let result = rocket.route(&req); - // assert_eq!(result.as_str(), "Hello, world!") - }); + let rocket = rocket::ignite().mount("/", routes![super::hello]); + let req = MockRequest::new(Get, "/"); + let result = req.dispatch_with(&rocket); + assert_eq!(result.unwrap().as_str(), "Hello, world!"); } } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 20ce1099..19e2099f 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -16,3 +16,6 @@ branch = "0.9.x" [dev-dependencies] lazy_static = "*" + +[features] +testing = [] diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs index 65f390be..f3d0acd2 100644 --- a/lib/src/config/mod.rs +++ b/lib/src/config/mod.rs @@ -2,6 +2,7 @@ mod error; mod environment; mod config; +use std::sync::{Once, ONCE_INIT}; use std::fs::{self, File}; use std::collections::HashMap; use std::io::Read; @@ -17,6 +18,7 @@ use self::Environment::*; use toml::{self, Table}; use logger::{self, LoggingLevel}; +static INIT: Once = ONCE_INIT; static mut CONFIG: Option = None; const CONFIG_FILENAME: &'static str = "Rocket.toml"; @@ -142,6 +144,9 @@ impl RocketConfig { } } +/// Returns the active configuration and whether this call initialized the +/// configuration. The configuration can only be initialized once. +/// /// Initializes the global RocketConfig by reading the Rocket config file from /// the current directory or any of its parents. Returns the active /// configuration, which is determined by the config env variable. If there as a @@ -152,18 +157,21 @@ impl RocketConfig { /// /// # Panics /// -/// If there is a problem, prints a nice error message and bails. This function -/// also panics if it is called more than once. -/// -/// # Thread-Safety -/// -/// This function is _not_ thread-safe. It should be called by a single thread. +/// If there is a problem, prints a nice error message and bails. #[doc(hidden)] -pub fn init() -> &'static Config { - if active().is_some() { - panic!("Configuration has already been initialized!") - } +pub fn init() -> (&'static Config, bool) { + let mut this_init = false; + unsafe { + INIT.call_once(|| { + private_init(); + this_init = true; + }); + (CONFIG.as_ref().unwrap().active(), this_init) + } +} + +unsafe fn private_init() { let bail = |e: ConfigError| -> ! { logger::init(LoggingLevel::Debug); e.pretty_print(); @@ -188,10 +196,7 @@ pub fn init() -> &'static Config { RocketConfig::active_default(&filename).unwrap_or_else(|e| bail(e)) }); - unsafe { - CONFIG = Some(config); - CONFIG.as_ref().unwrap().active() - } + CONFIG = Some(config); } /// Retrieve the active configuration, if there is one. diff --git a/lib/src/http/method.rs b/lib/src/http/method.rs index effb152a..1b535410 100644 --- a/lib/src/http/method.rs +++ b/lib/src/http/method.rs @@ -35,6 +35,12 @@ impl Method { } } + #[doc(hidden)] + #[inline(always)] + pub fn to_hyp(&self) -> HyperMethod { + self.to_string().as_str().parse().unwrap() + } + /// Whether an HTTP request with the given method supports a payload. pub fn supports_payload(&self) -> bool { match *self { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 2144f339..95f67755 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -69,6 +69,7 @@ extern crate toml; #[cfg(test)] #[macro_use] extern crate lazy_static; #[doc(hidden)] #[macro_use] pub mod logger; +#[cfg(any(test, feature = "testing"))] pub mod testing; pub mod http; pub mod request; pub mod response; diff --git a/lib/src/request/data/data.rs b/lib/src/request/data/data.rs new file mode 100644 index 00000000..896392db --- /dev/null +++ b/lib/src/request/data/data.rs @@ -0,0 +1,148 @@ +use std::io::{self, BufRead, Read, Write, Cursor, BufReader}; +use std::path::Path; +use std::fs::File; +use std::time::Duration; +use std::mem::transmute; + +use super::data_stream::{DataStream, StreamReader, kill_stream}; +use http::hyper::{HyperBodyReader, HyperHttpStream}; +use http::hyper::HyperNetworkStream; +use http::hyper::HyperHttpReader::*; + +pub struct Data { + buffer: Vec, + is_done: bool, + // TODO: This sucks as it depends on a TCPStream. Oh, hyper. + stream: StreamReader, + // Ideally we wouldn't have these, but Hyper forces us to. + position: usize, + capacity: usize, +} + +impl Data { + pub fn open(mut self) -> impl BufRead { + // Swap out the buffer and stream for empty ones so we can move. + let mut buffer = vec![]; + let mut stream = EmptyReader(self.stream.get_ref().clone()); + ::std::mem::swap(&mut buffer, &mut self.buffer); + ::std::mem::swap(&mut stream, &mut self.stream); + + // Setup the underlying reader at the correct pointers. + let mut cursor = Cursor::new(buffer); + cursor.set_position(self.position as u64); + let buffered = cursor.take((self.capacity - self.position) as u64); + + // Create the actual DataSteam. + DataStream { + network: stream.get_ref().clone(), + stream: buffered.chain(BufReader::new(stream)), + } + } + + #[doc(hidden)] + pub fn from_hyp(mut h_body: HyperBodyReader) -> Result { + // FIXME: This is asolutely terrible, thanks to Hyper. + + // Retrieve the underlying HTTPStream from Hyper. + let mut stream = match h_body.get_ref().get_ref() + .downcast_ref::() { + Some(s) => { + let owned_stream = s.clone(); + let buf_len = h_body.get_ref().get_buf().len() as u64; + match h_body { + SizedReader(_, n) => SizedReader(owned_stream, n - buf_len), + EofReader(_) => EofReader(owned_stream), + EmptyReader(_) => EmptyReader(owned_stream), + ChunkedReader(_, n) => + ChunkedReader(owned_stream, n.map(|k| k - buf_len)), + } + }, + None => return Err("Stream is not an HTTP stream!"), + }; + + // Set the read timeout to 5 seconds. + stream.get_mut().set_read_timeout(Some(Duration::from_secs(5))).unwrap(); + + // Create the Data object from hyper's buffer. + let (vec, pos, cap) = h_body.get_mut().take_buf(); + Ok(Data::new(vec, pos, cap, stream)) + } + + #[inline(always)] + pub fn peek(&self) -> &[u8] { + &self.buffer[self.position..self.capacity] + } + + #[inline(always)] + pub fn peek_complete(&self) -> bool { + self.is_done + } + + #[inline(always)] + pub fn stream_to(self, writer: &mut W) -> io::Result { + io::copy(&mut self.open(), writer) + } + + #[inline(always)] + pub fn stream_to_file>(self, path: P) -> io::Result { + io::copy(&mut self.open(), &mut File::create(path)?) + } + + #[doc(hidden)] + pub fn new(mut buf: Vec, + pos: usize, + mut cap: usize, + mut stream: StreamReader) + -> Data { + // Make sure the buffer is large enough for the bytes we want to peek. + const PEEK_BYTES: usize = 4096; + if buf.len() < PEEK_BYTES { + trace!("Resizing peek buffer from {} to {}.", buf.len(), PEEK_BYTES); + buf.resize(PEEK_BYTES, 0); + } + + trace!("Init buffer cap: {}", cap); + let buf_len = buf.len(); + let eof = match stream.read(&mut buf[cap..(buf_len - 1)]) { + Ok(n) if n == 0 => true, + Ok(n) => { + trace!("Filled peek buf with {} bytes.", n); + cap += n; + match stream.read(&mut buf[cap..(cap + 1)]) { + Ok(n) => { + cap += n; + n == 0 + } + Err(e) => { + error_!("Failed to check stream EOF status: {:?}", e); + false + } + } + } + Err(e) => { + error_!("Failed to read into peek buffer: {:?}", e); + false + } + }; + + trace!("Peek buffer size: {}, remaining: {}", buf_len, buf_len - cap); + Data { + buffer: buf, + stream: stream, + is_done: eof, + position: pos, + capacity: cap, + } + } +} + +impl Drop for Data { + fn drop(&mut self) { + // This is okay since the network stream expects to be shared mutably. + unsafe { + let stream: &mut StreamReader = transmute(self.stream.by_ref()); + kill_stream(stream, self.stream.get_mut()); + } + } +} + diff --git a/lib/src/request/data/from_data.rs b/lib/src/request/data/from_data.rs index edce083a..c1552e33 100644 --- a/lib/src/request/data/from_data.rs +++ b/lib/src/request/data/from_data.rs @@ -36,6 +36,13 @@ pub trait FromData: Sized { fn from_data(request: &Request, data: Data) -> DataOutcome; } +impl FromData for Data { + type Error = (); + fn from_data(_: &Request, data: Data) -> DataOutcome { + DataOutcome::success(data) + } +} + impl FromData for Result { type Error = (); diff --git a/lib/src/request/data/mod.rs b/lib/src/request/data/mod.rs index 62976a77..2ec4d409 100644 --- a/lib/src/request/data/mod.rs +++ b/lib/src/request/data/mod.rs @@ -1,163 +1,11 @@ //! Talk about the data thing. +#[cfg(any(test, feature = "testing"))] pub mod test_data; +#[cfg(not(any(test, feature = "testing")))] pub mod data; +#[cfg(not(any(test, feature = "testing")))] pub mod data_stream; mod from_data; -mod data_stream; pub use self::from_data::{FromData, DataOutcome}; -use std::io::{self, BufRead, Read, Write, Cursor, BufReader}; -use std::path::Path; -use std::fs::File; -use std::time::Duration; -use std::mem::transmute; - -use self::data_stream::{DataStream, StreamReader, kill_stream}; -use request::Request; -use http::hyper::{HyperBodyReader, HyperHttpStream}; -use http::hyper::HyperNetworkStream; -use http::hyper::HyperHttpReader::*; - -pub struct Data { - buffer: Vec, - is_done: bool, - stream: StreamReader, - position: usize, - capacity: usize, -} - -impl Data { - pub fn open(mut self) -> impl BufRead { - // Swap out the buffer and stream for empty ones so we can move. - let mut buffer = vec![]; - let mut stream = EmptyReader(self.stream.get_ref().clone()); - ::std::mem::swap(&mut buffer, &mut self.buffer); - ::std::mem::swap(&mut stream, &mut self.stream); - - // Setup the underlying reader at the correct pointers. - let mut cursor = Cursor::new(buffer); - cursor.set_position(self.position as u64); - let buffered = cursor.take((self.capacity - self.position) as u64); - - // Create the actual DataSteam. - DataStream { - network: stream.get_ref().clone(), - stream: buffered.chain(BufReader::new(stream)), - } - } - - #[doc(hidden)] - pub fn from_hyp(mut h_body: HyperBodyReader) -> Result { - // FIXME: This is asolutely terrible, thanks to Hyper. - - // Retrieve the underlying HTTPStream from Hyper. - let mut stream = match h_body.get_ref().get_ref() - .downcast_ref::() { - Some(s) => { - let owned_stream = s.clone(); - let buf_len = h_body.get_ref().get_buf().len() as u64; - match h_body { - SizedReader(_, n) => SizedReader(owned_stream, n - buf_len), - EofReader(_) => EofReader(owned_stream), - EmptyReader(_) => EmptyReader(owned_stream), - ChunkedReader(_, n) => - ChunkedReader(owned_stream, n.map(|k| k - buf_len)), - } - }, - None => return Err("Stream is not an HTTP stream!"), - }; - - // Set the read timeout to 5 seconds. - stream.get_mut().set_read_timeout(Some(Duration::from_secs(5))).unwrap(); - - // Create the Data object from hyper's buffer. - let (vec, pos, cap) = h_body.get_mut().take_buf(); - Ok(Data::new(vec, pos, cap, stream)) - } - - #[inline(always)] - pub fn peek(&self) -> &[u8] { - &self.buffer[self.position..self.capacity] - } - - #[inline(always)] - pub fn peek_complete(&self) -> bool { - self.is_done - } - - #[inline(always)] - pub fn stream_to(self, writer: &mut W) -> io::Result { - io::copy(&mut self.open(), writer) - } - - #[inline(always)] - pub fn stream_to_file>(self, path: P) -> io::Result { - io::copy(&mut self.open(), &mut File::create(path)?) - } - - pub fn new(mut buf: Vec, - pos: usize, - mut cap: usize, - mut stream: StreamReader) - -> Data { - // TODO: Make sure we always try to get some number of bytes in the - // buffer so that peek actually does something. - - // Make sure the buffer is large enough for the bytes we want to peek. - const PEEK_BYTES: usize = 4096; - if buf.len() < PEEK_BYTES { - trace!("Resizing peek buffer from {} to {}.", buf.len(), PEEK_BYTES); - buf.resize(PEEK_BYTES, 0); - } - - trace!("Init buffer cap: {}", cap); - let buf_len = buf.len(); - let eof = match stream.read(&mut buf[cap..(buf_len - 1)]) { - Ok(n) if n == 0 => true, - Ok(n) => { - trace!("Filled peek buf with {} bytes.", n); - cap += n; - match stream.read(&mut buf[cap..(cap + 1)]) { - Ok(n) => { - cap += n; - n == 0 - } - Err(e) => { - error_!("Failed to check stream EOF status: {:?}", e); - false - } - } - } - Err(e) => { - error_!("Failed to read into peek buffer: {:?}", e); - false - } - }; - - trace!("Peek buffer size: {}, remaining: {}", buf_len, buf_len - cap); - Data { - buffer: buf, - stream: stream, - is_done: eof, - position: pos, - capacity: cap, - } - } -} - -impl Drop for Data { - fn drop(&mut self) { - // This is okay since the network stream expects to be shared mutably. - unsafe { - let stream: &mut StreamReader = transmute(self.stream.by_ref()); - kill_stream(stream, self.stream.get_mut()); - } - } -} - -impl FromData for Data { - type Error = (); - - fn from_data(_: &Request, data: Data) -> DataOutcome { - DataOutcome::success(data) - } -} +#[cfg(any(test, feature = "testing"))] pub use self::test_data::Data; +#[cfg(not(any(test, feature = "testing")))] pub use self::data::Data; diff --git a/lib/src/request/data/test_data.rs b/lib/src/request/data/test_data.rs new file mode 100644 index 00000000..ebeac66e --- /dev/null +++ b/lib/src/request/data/test_data.rs @@ -0,0 +1,52 @@ +use std::io::{self, BufRead, Write, Cursor, BufReader}; +use std::path::Path; +use std::fs::File; + +use http::hyper::HyperBodyReader; + +const PEEK_BYTES: usize = 4096; + +pub struct Data { + data: Vec, +} + +impl Data { + pub fn open(self) -> impl BufRead { + BufReader::new(Cursor::new(self.data)) + } + + #[inline(always)] + pub fn peek(&self) -> &[u8] { + &self.data[..::std::cmp::min(PEEK_BYTES, self.data.len())] + } + + #[inline(always)] + pub fn peek_complete(&self) -> bool { + self.data.len() <= PEEK_BYTES + } + + #[inline(always)] + pub fn stream_to(self, writer: &mut W) -> io::Result { + io::copy(&mut self.open(), writer) + } + + #[inline(always)] + pub fn stream_to_file>(self, path: P) -> io::Result { + io::copy(&mut self.open(), &mut File::create(path)?) + } + + #[doc(hidden)] + pub fn from_hyp(mut h_body: HyperBodyReader) -> Result { + let mut vec = Vec::new(); + if let Err(_) = io::copy(&mut h_body, &mut vec) { + return Err("Reading from body failed."); + }; + + Ok(Data::new(vec)) + } + + #[doc(hidden)] + pub fn new(data: Vec) -> Data { + Data { data: data } + } +} diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index 3d3f8141..75c67374 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -176,11 +176,16 @@ impl Request { } #[doc(hidden)] - #[cfg(test)] #[inline(always)] - pub fn set_content_type(&mut self, ct: ContentType) { - let hyper_ct = header::ContentType(ct.into()); - self.headers.set::(hyper_ct) + pub fn set_headers(&mut self, h_headers: HyperHeaders) { + let cookies = match h_headers.get::() { + // TODO: Retrieve key from config. + Some(cookie) => cookie.to_cookie_jar(&[]), + None => Cookies::new(&[]), + }; + + self.headers = h_headers; + self.cookies = cookies; } #[doc(hidden)] diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index ffd1cc7e..4329a887 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -42,10 +42,10 @@ impl HyperHandler for Rocket { let mut request = match Request::new(h_method, h_headers, h_uri) { Ok(req) => req, Err(ref reason) => { - let mock_request = Request::mock(Method::Get, uri.as_str()); - error!("{}: bad request ({}).", mock_request, reason); - return self.handle_error(StatusCode::InternalServerError, - &mock_request, res); + let mock = Request::mock(Method::Get, uri.as_str()); + error!("{}: bad request ({}).", mock, reason); + self.handle_error(StatusCode::InternalServerError, &mock, res); + return; } }; @@ -54,8 +54,8 @@ impl HyperHandler for Rocket { Ok(data) => data, Err(reason) => { error_!("Bad data in request: {}", reason); - return self.handle_error(StatusCode::InternalServerError, - &request, res); + self.handle_error(StatusCode::InternalServerError, &request, res); + return; } }; @@ -64,10 +64,12 @@ impl HyperHandler for Rocket { self.preprocess_request(&mut request, &data); // Now that we've Rocket-ized everything, actually dispath the request. - info!("{}:", request); let mut responder = match self.dispatch(&request, data) { Ok(responder) => responder, - Err(code) => return self.handle_error(code, &request, res) + Err(code) => { + self.handle_error(code, &request, res); + return; + } }; // We have a responder. Update the cookies in the header. @@ -83,15 +85,17 @@ impl HyperHandler for Rocket { // Check if the responder wants to forward to a catcher. If it doesn't, // it's a success or failure, so we can't do any more processing. if let Some((code, f_res)) = outcome.forwarded() { - return self.handle_error(code, &request, f_res); + self.handle_error(code, &request, f_res); } } } impl Rocket { - fn dispatch<'r>(&self, request: &'r Request, mut data: Data) + #[doc(hidden)] + pub fn dispatch<'r>(&self, request: &'r Request, mut data: Data) -> Result, StatusCode> { // Go through the list of matching routes until we fail or succeed. + info!("{}:", request); let matches = self.router.route(&request); for route in matches { // Retrieve and set the requests parameters. @@ -136,11 +140,12 @@ impl Rocket { } } - // Call when no route was found. - fn handle_error<'r>(&self, + // Call when no route was found. Returns true if there was a response. + #[doc(hidden)] + pub fn handle_error<'r>(&self, code: StatusCode, req: &'r Request, - response: FreshHyperResponse) { + response: FreshHyperResponse) -> bool { // Find the catcher or use the one for internal server errors. let catcher = self.catchers.get(&code.to_u16()).unwrap_or_else(|| { error_!("No catcher found for {}.", code); @@ -151,6 +156,7 @@ impl Rocket { if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() { if !responder.respond(response).is_success() { error_!("Catcher outcome was unsuccessul; aborting response."); + return false; } else { info_!("Responded with {} catcher.", White.paint(code)); } @@ -162,6 +168,8 @@ impl Rocket { let responder = catcher.handle(Error::Internal, req).responder(); responder.unwrap().respond(response).unwrap() } + + true } pub fn mount(mut self, base: &str, routes: Vec) -> Self { @@ -225,9 +233,11 @@ impl Rocket { pub fn ignite() -> Rocket { // Note: init() will exit the process under config errors. - let config = config::init(); + let (config, initted) = config::init(); + if initted { + logger::init(config.log_level); + } - logger::init(config.log_level); info!("🔧 Configured for {}.", config.env); info_!("listening: {}:{}", White.paint(&config.address), diff --git a/lib/src/testing.rs b/lib/src/testing.rs new file mode 100644 index 00000000..10093850 --- /dev/null +++ b/lib/src/testing.rs @@ -0,0 +1,80 @@ +use std::io::Cursor; +use outcome::Outcome::*; +use http::{hyper, Method}; +use request::{Request, Data}; +use Rocket; + +pub struct MockRequest { + request: Request, + data: Data +} + +impl MockRequest { + pub fn new>(method: Method, uri: S) -> Self { + MockRequest { + request: Request::mock(method, uri.as_ref()), + data: Data::new(vec![]) + } + } + + pub fn headers<'h, H: AsRef<[(&'h str, &'h str)]>>(mut self, headers: H) -> Self { + let mut hyp_headers = hyper::HyperHeaders::new(); + + for &(name, fields) in headers.as_ref() { + let mut vec_fields = vec![]; + for field in fields.split(";") { + vec_fields.push(field.as_bytes().to_vec()); + } + + hyp_headers.set_raw(name.to_string(), vec_fields); + } + + self.request.set_headers(hyp_headers); + self + } + + pub fn body>(mut self, body: S) -> Self { + self.data = Data::new(body.as_ref().as_bytes().into()); + self + } + + pub fn dispatch_with(mut self, rocket: &Rocket) -> Option { + let request = self.request; + let data = ::std::mem::replace(&mut self.data, Data::new(vec![])); + + let mut response = Cursor::new(vec![]); + + // Create a new scope so we can get the inner from response later. + let ok = { + let mut h_h = hyper::HyperHeaders::new(); + let res = hyper::FreshHyperResponse::new(&mut response, &mut h_h); + match rocket.dispatch(&request, data) { + Ok(mut responder) => { + match responder.respond(res) { + Success(_) => true, + Failure(_) => false, + Forward((code, r)) => rocket.handle_error(code, &request, r) + } + } + Err(code) => rocket.handle_error(code, &request, res) + } + }; + + if !ok { + return None; + } + + match String::from_utf8(response.into_inner()) { + Ok(string) => { + // TODO: Expose the full response (with headers) somewhow. + string.find("\r\n\r\n").map(|i| { + string[(i + 4)..].to_string() + }) + } + Err(e) => { + error_!("Could not create string from response: {:?}", e); + None + } + } + } +}