feat(lib): switch to non-blocking (asynchronous) IO
BREAKING CHANGE: This breaks a lot of the Client and Server APIs. Check the documentation for how Handlers can be used for asynchronous events.
This commit is contained in:
		| @@ -2,324 +2,75 @@ | ||||
| //! | ||||
| //! These are requests that a `hyper::Server` receives, and include its method, | ||||
| //! target URI, headers, and message body. | ||||
| use std::io::{self, Read}; | ||||
| use std::net::SocketAddr; | ||||
| use std::time::Duration; | ||||
| //use std::net::SocketAddr; | ||||
|  | ||||
| use buffer::BufReader; | ||||
| use net::NetworkStream; | ||||
| use version::{HttpVersion}; | ||||
| use version::HttpVersion; | ||||
| use method::Method; | ||||
| use header::{Headers, ContentLength, TransferEncoding}; | ||||
| use http::h1::{self, Incoming, HttpReader}; | ||||
| use http::h1::HttpReader::{SizedReader, ChunkedReader, EmptyReader}; | ||||
| use header::Headers; | ||||
| use http::{RequestHead, MessageHead, RequestLine}; | ||||
| use uri::RequestUri; | ||||
|  | ||||
| pub fn new(incoming: RequestHead) -> Request { | ||||
|     let MessageHead { version, subject: RequestLine(method, uri), headers } = incoming; | ||||
|     debug!("Request Line: {:?} {:?} {:?}", method, uri, version); | ||||
|     debug!("{:#?}", headers); | ||||
|  | ||||
|     Request { | ||||
|         //remote_addr: addr, | ||||
|         method: method, | ||||
|         uri: uri, | ||||
|         headers: headers, | ||||
|         version: version, | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A request bundles several parts of an incoming `NetworkStream`, given to a `Handler`. | ||||
| pub struct Request<'a, 'b: 'a> { | ||||
|     /// The IP address of the remote connection. | ||||
|     pub remote_addr: SocketAddr, | ||||
|     /// The `Method`, such as `Get`, `Post`, etc. | ||||
|     pub 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<&'a mut BufReader<&'b mut NetworkStream>> | ||||
| #[derive(Debug)] | ||||
| pub struct Request { | ||||
|     // The IP address of the remote connection. | ||||
|     //remote_addr: SocketAddr, | ||||
|     method: Method, | ||||
|     headers: Headers, | ||||
|     uri: RequestUri, | ||||
|     version: HttpVersion, | ||||
| } | ||||
|  | ||||
|  | ||||
| impl<'a, 'b: 'a> Request<'a, 'b> { | ||||
|     /// Create a new Request, reading the StartLine and Headers so they are | ||||
|     /// immediately useful. | ||||
|     pub fn new(mut stream: &'a mut BufReader<&'b mut NetworkStream>, addr: SocketAddr) | ||||
|         -> ::Result<Request<'a, 'b>> { | ||||
|  | ||||
|         let Incoming { version, subject: (method, uri), headers } = try!(h1::parse_request(stream)); | ||||
|         debug!("Request Line: {:?} {:?} {:?}", method, uri, version); | ||||
|         debug!("{:?}", headers); | ||||
|  | ||||
|         let body = if headers.has::<ContentLength>() { | ||||
|             match headers.get::<ContentLength>() { | ||||
|                 Some(&ContentLength(len)) => SizedReader(stream, len), | ||||
|                 None => unreachable!() | ||||
|             } | ||||
|         } else if headers.has::<TransferEncoding>() { | ||||
|             todo!("check for Transfer-Encoding: chunked"); | ||||
|             ChunkedReader(stream, None) | ||||
|         } else { | ||||
|             EmptyReader(stream) | ||||
|         }; | ||||
|  | ||||
|         Ok(Request { | ||||
|             remote_addr: addr, | ||||
|             method: method, | ||||
|             uri: uri, | ||||
|             headers: headers, | ||||
|             version: version, | ||||
|             body: body | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Set the read timeout of the underlying NetworkStream. | ||||
| impl Request { | ||||
|     /// The `Method`, such as `Get`, `Post`, etc. | ||||
|     #[inline] | ||||
|     pub fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> { | ||||
|         self.body.get_ref().get_ref().set_read_timeout(timeout) | ||||
|     } | ||||
|     pub fn method(&self) -> &Method { &self.method } | ||||
|  | ||||
|     /// Get a reference to the underlying `NetworkStream`. | ||||
|     /// The headers of the incoming request. | ||||
|     #[inline] | ||||
|     pub fn downcast_ref<T: NetworkStream>(&self) -> Option<&T> { | ||||
|         self.body.get_ref().get_ref().downcast_ref() | ||||
|     } | ||||
|     pub fn headers(&self) -> &Headers { &self.headers } | ||||
|  | ||||
|     /// Get a reference to the underlying Ssl stream, if connected | ||||
|     /// over HTTPS. | ||||
|     /// | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ```rust | ||||
|     /// # extern crate hyper; | ||||
|     /// # #[cfg(feature = "openssl")] | ||||
|     /// extern crate openssl; | ||||
|     /// # #[cfg(feature = "openssl")] | ||||
|     /// use openssl::ssl::SslStream; | ||||
|     /// use hyper::net::HttpStream; | ||||
|     /// # fn main() {} | ||||
|     /// # #[cfg(feature = "openssl")] | ||||
|     /// # fn doc_ssl(req: hyper::server::Request) { | ||||
|     /// let maybe_ssl = req.ssl::<SslStream<HttpStream>>(); | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     /// The target request-uri for this request. | ||||
|     #[inline] | ||||
|     pub fn ssl<T: NetworkStream>(&self) -> Option<&T> { | ||||
|         use ::net::HttpsStream; | ||||
|         match self.downcast_ref() { | ||||
|             Some(&HttpsStream::Https(ref s)) => Some(s), | ||||
|     pub fn uri(&self) -> &RequestUri { &self.uri } | ||||
|  | ||||
|     /// The version of HTTP for this request. | ||||
|     #[inline] | ||||
|     pub fn version(&self) -> &HttpVersion { &self.version } | ||||
|  | ||||
|     /* | ||||
|     /// The target path of this Request. | ||||
|     #[inline] | ||||
|     pub fn path(&self) -> Option<&str> { | ||||
|         match *self.uri { | ||||
|             RequestUri::AbsolutePath(ref s) => Some(s), | ||||
|             RequestUri::AbsoluteUri(ref url) => Some(&url[::url::Position::BeforePath..]), | ||||
|             _ => None | ||||
|         } | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     /// Deconstruct a Request into its constituent parts. | ||||
|     /// Deconstruct this Request into its pieces. | ||||
|     /// | ||||
|     /// Modifying these pieces will have no effect on how hyper behaves. | ||||
|     #[inline] | ||||
|     pub fn deconstruct(self) -> (SocketAddr, Method, Headers, | ||||
|                                  RequestUri, HttpVersion, | ||||
|                                  HttpReader<&'a mut BufReader<&'b mut NetworkStream>>) { | ||||
|         (self.remote_addr, self.method, self.headers, | ||||
|          self.uri, self.version, self.body) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a, 'b> Read for Request<'a, 'b> { | ||||
|     #[inline] | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         self.body.read(buf) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use buffer::BufReader; | ||||
|     use header::{Host, TransferEncoding, Encoding}; | ||||
|     use net::NetworkStream; | ||||
|     use mock::MockStream; | ||||
|     use super::Request; | ||||
|  | ||||
|     use std::io::{self, Read}; | ||||
|     use std::net::SocketAddr; | ||||
|  | ||||
|     fn sock(s: &str) -> SocketAddr { | ||||
|         s.parse().unwrap() | ||||
|     } | ||||
|  | ||||
|     fn read_to_string(mut req: Request) -> io::Result<String> { | ||||
|         let mut s = String::new(); | ||||
|         try!(req.read_to_string(&mut s)); | ||||
|         Ok(s) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_get_empty_body() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             GET / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             \r\n\ | ||||
|             I'm a bad request.\r\n\ | ||||
|         "); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|         assert_eq!(read_to_string(req).unwrap(), "".to_owned()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_get_with_body() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             GET / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Content-Length: 19\r\n\ | ||||
|             \r\n\ | ||||
|             I'm a good request.\r\n\ | ||||
|         "); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|         assert_eq!(read_to_string(req).unwrap(), "I'm a good request.".to_owned()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_head_empty_body() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             HEAD / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             \r\n\ | ||||
|             I'm a bad request.\r\n\ | ||||
|         "); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|         assert_eq!(read_to_string(req).unwrap(), "".to_owned()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_post_empty_body() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             POST / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             \r\n\ | ||||
|             I'm a bad request.\r\n\ | ||||
|         "); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|         assert_eq!(read_to_string(req).unwrap(), "".to_owned()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_chunked_request() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             POST / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
|             \r\n\ | ||||
|             1\r\n\ | ||||
|             q\r\n\ | ||||
|             2\r\n\ | ||||
|             we\r\n\ | ||||
|             2\r\n\ | ||||
|             rt\r\n\ | ||||
|             0\r\n\ | ||||
|             \r\n" | ||||
|         ); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|  | ||||
|         // The headers are correct? | ||||
|         match req.headers.get::<Host>() { | ||||
|             Some(host) => { | ||||
|                 assert_eq!("example.domain", host.hostname); | ||||
|             }, | ||||
|             None => panic!("Host header expected!"), | ||||
|         }; | ||||
|         match req.headers.get::<TransferEncoding>() { | ||||
|             Some(encodings) => { | ||||
|                 assert_eq!(1, encodings.len()); | ||||
|                 assert_eq!(Encoding::Chunked, encodings[0]); | ||||
|             } | ||||
|             None => panic!("Transfer-Encoding: chunked expected!"), | ||||
|         }; | ||||
|         // The content is correctly read? | ||||
|         assert_eq!(read_to_string(req).unwrap(), "qwert".to_owned()); | ||||
|     } | ||||
|  | ||||
|     /// Tests that when a chunk size is not a valid radix-16 number, an error | ||||
|     /// is returned. | ||||
|     #[test] | ||||
|     fn test_invalid_chunk_size_not_hex_digit() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             POST / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
|             \r\n\ | ||||
|             X\r\n\ | ||||
|             1\r\n\ | ||||
|             0\r\n\ | ||||
|             \r\n" | ||||
|         ); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|  | ||||
|         assert!(read_to_string(req).is_err()); | ||||
|     } | ||||
|  | ||||
|     /// Tests that when a chunk size contains an invalid extension, an error is | ||||
|     /// returned. | ||||
|     #[test] | ||||
|     fn test_invalid_chunk_size_extension() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             POST / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
|             \r\n\ | ||||
|             1 this is an invalid extension\r\n\ | ||||
|             1\r\n\ | ||||
|             0\r\n\ | ||||
|             \r\n" | ||||
|         ); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|  | ||||
|         assert!(read_to_string(req).is_err()); | ||||
|     } | ||||
|  | ||||
|     /// Tests that when a valid extension that contains a digit is appended to | ||||
|     /// the chunk size, the chunk is correctly read. | ||||
|     #[test] | ||||
|     fn test_chunk_size_with_extension() { | ||||
|         let mut mock = MockStream::with_input(b"\ | ||||
|             POST / HTTP/1.1\r\n\ | ||||
|             Host: example.domain\r\n\ | ||||
|             Transfer-Encoding: chunked\r\n\ | ||||
|             \r\n\ | ||||
|             1;this is an extension with a digit 1\r\n\ | ||||
|             1\r\n\ | ||||
|             0\r\n\ | ||||
|             \r\n" | ||||
|         ); | ||||
|  | ||||
|         // FIXME: Use Type ascription | ||||
|         let mock: &mut NetworkStream = &mut mock; | ||||
|         let mut stream = BufReader::new(mock); | ||||
|  | ||||
|         let req = Request::new(&mut stream, sock("127.0.0.1:80")).unwrap(); | ||||
|  | ||||
|         assert_eq!(read_to_string(req).unwrap(), "1".to_owned()); | ||||
|     pub fn deconstruct(self) -> (Method, RequestUri, HttpVersion, Headers) { | ||||
|         (self.method, self.uri, self.version, self.headers) | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user