Merge pull request #19 from reem/static-response-states
Statically track the status of a Response by using a Phantom Type
This commit is contained in:
		| @@ -1,3 +1,4 @@ | |||||||
|  | #![feature(macro_rules)] | ||||||
| extern crate curl; | extern crate curl; | ||||||
| extern crate http; | extern crate http; | ||||||
| extern crate hyper; | extern crate hyper; | ||||||
| @@ -13,10 +14,19 @@ fn listen() -> hyper::server::Listening { | |||||||
|     server.listen(handle).unwrap() |     server.listen(handle).unwrap() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | macro_rules! try_continue( | ||||||
|  |     ($e:expr) => {{ | ||||||
|  |         match $e { | ||||||
|  |             Ok(v) => v, | ||||||
|  |             Err(..) => continue | ||||||
|  |         } | ||||||
|  |     }}) | ||||||
|  |  | ||||||
| fn handle(mut incoming: Incoming) { | fn handle(mut incoming: Incoming) { | ||||||
|     for (_, mut res) in incoming { |     for (_, res) in incoming { | ||||||
|         res.write(b"Benchmarking hyper vs others!").unwrap(); |         let mut res = try_continue!(res.start()); | ||||||
|         res.end().unwrap(); |         try_continue!(res.write(b"Benchmarking hyper vs others!")) | ||||||
|  |         try_continue!(res.end()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,24 +29,26 @@ impl Handler for Echo { | |||||||
|                     (&Get, "/") | (&Get, "/echo") => { |                     (&Get, "/") | (&Get, "/echo") => { | ||||||
|                         let out = b"Try POST /echo"; |                         let out = b"Try POST /echo"; | ||||||
|  |  | ||||||
|                         res.headers.set(ContentLength(out.len())); |                         res.headers_mut().set(ContentLength(out.len())); | ||||||
|  |                         let mut res = try_continue!(res.start()); | ||||||
|                         try_continue!(res.write(out)); |                         try_continue!(res.write(out)); | ||||||
|                         try_continue!(res.end()); |                         try_continue!(res.end()); | ||||||
|                         continue; |                         continue; | ||||||
|                     }, |                     }, | ||||||
|                     (&Post, "/echo") => (), // fall through, fighting mutable borrows |                     (&Post, "/echo") => (), // fall through, fighting mutable borrows | ||||||
|                     _ => { |                     _ => { | ||||||
|                         res.status = hyper::status::NotFound; |                         *res.status_mut() = hyper::status::NotFound; | ||||||
|                         try_continue!(res.end()); |                         try_continue!(res.start().and_then(|res| res.end())); | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 _ => { |                 _ => { | ||||||
|                     try_continue!(res.end()); |                     try_continue!(res.start().and_then(|res| res.end())); | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|  |             let mut res = try_continue!(res.start()); | ||||||
|             try_continue!(copy(&mut req, &mut res)); |             try_continue!(copy(&mut req, &mut res)); | ||||||
|             try_continue!(res.end()); |             try_continue!(res.end()); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ use std::io::{Acceptor, Listener, IoResult, EndOfFile, IncomingConnections}; | |||||||
| use std::io::net::ip::{IpAddr, Port, SocketAddr}; | use std::io::net::ip::{IpAddr, Port, SocketAddr}; | ||||||
|  |  | ||||||
| pub use self::request::Request; | pub use self::request::Request; | ||||||
| pub use self::response::Response; | pub use self::response::{Response, Fresh, Streaming}; | ||||||
|  |  | ||||||
| pub mod request; | pub mod request; | ||||||
| pub mod response; | pub mod response; | ||||||
| @@ -55,8 +55,8 @@ pub struct Incoming<'a> { | |||||||
|     from: IncomingConnections<'a, TcpAcceptor> |     from: IncomingConnections<'a, TcpAcceptor> | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'a> Iterator<(Request, Response)> for Incoming<'a> { | impl<'a> Iterator<(Request, Response<Fresh>)> for Incoming<'a> { | ||||||
|     fn next(&mut self) -> Option<(Request, Response)> { |     fn next(&mut self) -> Option<(Request, Response<Fresh>)> { | ||||||
|         for conn in self.from { |         for conn in self.from { | ||||||
|             match conn { |             match conn { | ||||||
|                 Ok(stream) => { |                 Ok(stream) => { | ||||||
|   | |||||||
| @@ -13,39 +13,52 @@ use status; | |||||||
| use version; | use version; | ||||||
| use rfc7230::{CR, LF, LINE_ENDING}; | use rfc7230::{CR, LF, LINE_ENDING}; | ||||||
|  |  | ||||||
|  | /// Phantom type indicating Headers and StatusCode have not been written. | ||||||
|  | pub struct Fresh; | ||||||
|  |  | ||||||
|  | /// Phantom type indicating Headers and StatusCode have been written. | ||||||
|  | pub struct Streaming; | ||||||
|  |  | ||||||
|  | /// The status of a Response, indicating if the headers and status have been written. | ||||||
|  | pub trait WriteStatus {} | ||||||
|  |  | ||||||
|  | impl WriteStatus for Streaming {} | ||||||
|  | impl WriteStatus for Fresh {} | ||||||
|  |  | ||||||
| /// The outgoing half for a Tcp connection, created by a `Server` and given to a `Handler`. | /// The outgoing half for a Tcp connection, created by a `Server` and given to a `Handler`. | ||||||
| pub struct Response { | pub struct Response<W: WriteStatus> { | ||||||
|     /// 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. |     /// The HTTP version of this response. | ||||||
|     pub version: version::HttpVersion, |     pub version: version::HttpVersion, | ||||||
|  |     // Stream the Response is writing to, not accessible through UnwrittenResponse | ||||||
|     headers_written: bool, // TODO: can this check be moved to compile time? |  | ||||||
|     body: BufferedWriter<TcpStream>, // TODO: use a HttpWriter from rfc7230 |     body: BufferedWriter<TcpStream>, // TODO: use a HttpWriter from rfc7230 | ||||||
|  |     // The status code for the request. | ||||||
|  |     status: status::StatusCode, | ||||||
|  |     // The outgoing headers on this response. | ||||||
|  |     headers: header::Headers | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Response { | impl<W: WriteStatus> Response<W> { | ||||||
|  |     /// The status of this response. | ||||||
|  |     #[inline] | ||||||
|  |     pub fn status(&self) -> status::StatusCode { self.status } | ||||||
|  |  | ||||||
|  |     /// The headers of this response. | ||||||
|  |     pub fn headers(&self) -> &header::Headers { &self.headers } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Response<Fresh> { | ||||||
|     /// Creates a new Response that can be used to write to a network stream. |     /// Creates a new Response that can be used to write to a network stream. | ||||||
|     pub fn new(tcp: TcpStream) -> Response { |     pub fn new(tcp: TcpStream) -> Response<Fresh> { | ||||||
|         Response { |         Response { | ||||||
|             status: status::Ok, |             status: status::Ok, | ||||||
|             version: version::Http11, |             version: version::Http11, | ||||||
|             headers: header::Headers::new(), |             headers: header::Headers::new(), | ||||||
|             headers_written: false, |  | ||||||
|             body: BufferedWriter::new(tcp) |             body: BufferedWriter::new(tcp) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn write_head(&mut self) -> IoResult<()> { |     /// Consume this Response<Fresh>, writing the Headers and Status and creating a Response<Streaming> | ||||||
|         if self.headers_written { |     pub fn start(mut self) -> IoResult<Response<Streaming>> { | ||||||
|             debug!("headers previously written, nooping"); |  | ||||||
|             return Ok(()); |  | ||||||
|         } |  | ||||||
|         self.headers_written = true; |  | ||||||
|         debug!("writing head: {} {}", self.version, self.status); |         debug!("writing head: {} {}", self.version, self.status); | ||||||
|         try!(write!(self.body, "{} {}{}{}", self.version, self.status, CR as char, LF as char)); |         try!(write!(self.body, "{} {}{}{}", self.version, self.status, CR as char, LF as char)); | ||||||
|  |  | ||||||
| @@ -59,9 +72,26 @@ impl Response { | |||||||
|             try!(self.body.write(LINE_ENDING)); |             try!(self.body.write(LINE_ENDING)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.body.write(LINE_ENDING) |         try!(self.body.write(LINE_ENDING)); | ||||||
|  |  | ||||||
|  |         // "copy" to change the phantom type | ||||||
|  |         Ok(Response { | ||||||
|  |             version: self.version, | ||||||
|  |             body: self.body, | ||||||
|  |             status: self.status, | ||||||
|  |             headers: self.headers | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Get a mutable reference to the status. | ||||||
|  |     #[inline] | ||||||
|  |     pub fn status_mut(&mut self) -> &mut status::StatusCode { &mut self.status } | ||||||
|  |  | ||||||
|  |     /// Get a mutable reference to the Headers. | ||||||
|  |     pub fn headers_mut(&mut self) -> &mut header::Headers { &mut self.headers } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Response<Streaming> { | ||||||
|     /// Flushes all writing of a response to the client. |     /// Flushes all writing of a response to the client. | ||||||
|     pub fn end(mut self) -> IoResult<()> { |     pub fn end(mut self) -> IoResult<()> { | ||||||
|         debug!("ending"); |         debug!("ending"); | ||||||
| @@ -69,20 +99,14 @@ impl Response { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Writer for Response<Streaming> { | ||||||
| impl Writer for Response { |  | ||||||
|     fn write(&mut self, msg: &[u8]) -> IoResult<()> { |     fn write(&mut self, msg: &[u8]) -> IoResult<()> { | ||||||
|         if !self.headers_written { |  | ||||||
|             try!(self.write_head()); |  | ||||||
|         } |  | ||||||
|         debug!("write {:u} bytes", msg.len()); |         debug!("write {:u} bytes", msg.len()); | ||||||
|         self.body.write(msg) |         self.body.write(msg) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn flush(&mut self) -> IoResult<()> { |     fn flush(&mut self) -> IoResult<()> { | ||||||
|         if !self.headers_written { |  | ||||||
|             try!(self.write_head()); |  | ||||||
|         } |  | ||||||
|         self.body.flush() |         self.body.flush() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user