//! Server Requests //! //! 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 buffer::BufReader; use net::NetworkStream; 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 uri::RequestUri; /// 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>> } 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> { 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::() { match headers.get::() { Some(&ContentLength(len)) => SizedReader(stream, len), None => unreachable!() } } else if headers.has::() { 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. #[inline] pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { self.body.get_ref().get_ref().set_read_timeout(timeout) } /// Get a reference to the underlying `NetworkStream`. #[inline] pub fn downcast_ref(&self) -> Option<&T> { self.body.get_ref().get_ref().downcast_ref() } /// 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::>(); /// # } /// ``` #[inline] pub fn ssl(&self) -> Option<&T> { use ::net::HttpsStream; match self.downcast_ref() { Some(&HttpsStream::Https(ref s)) => Some(s), _ => None } } /// Deconstruct a Request into its constituent parts. #[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 { 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 { 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::() { Some(host) => { assert_eq!("example.domain", host.hostname); }, None => panic!("Host header expected!"), }; match req.headers.get::() { 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()); } }