refactor(http): move h1 and h2 into http module
This commit is contained in:
		
							
								
								
									
										872
									
								
								src/http/h1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										872
									
								
								src/http/h1.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,872 @@ | ||||
| //! Adapts the HTTP/1.1 implementation into the `HttpMessage` API. | ||||
| use std::borrow::Cow; | ||||
| use std::cmp::min; | ||||
| use std::fmt; | ||||
| use std::io::{self, Write, BufWriter, BufRead, Read}; | ||||
| use std::net::Shutdown; | ||||
|  | ||||
| use httparse; | ||||
|  | ||||
| use buffer::BufReader; | ||||
| use Error; | ||||
| use header::{Headers, ContentLength, TransferEncoding}; | ||||
| use header::Encoding::Chunked; | ||||
| use method::{Method}; | ||||
| use net::{NetworkConnector, NetworkStream, ContextVerifier}; | ||||
| use status::StatusCode; | ||||
| use version::HttpVersion; | ||||
| use version::HttpVersion::{Http10, Http11}; | ||||
| use uri::RequestUri; | ||||
|  | ||||
| use self::HttpReader::{SizedReader, ChunkedReader, EofReader, EmptyReader}; | ||||
| use self::HttpWriter::{ChunkedWriter, SizedWriter, EmptyWriter, ThroughWriter}; | ||||
|  | ||||
| use http::{ | ||||
|     RawStatus, | ||||
|     Protocol, | ||||
|     HttpMessage, | ||||
|     RequestHead, | ||||
|     ResponseHead, | ||||
| }; | ||||
| use header; | ||||
| use version; | ||||
|  | ||||
| /// An implementation of the `HttpMessage` trait for HTTP/1.1. | ||||
| #[derive(Debug)] | ||||
| pub struct Http11Message { | ||||
|     stream: Option<Box<NetworkStream + Send>>, | ||||
|     writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>, | ||||
|     reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>, | ||||
| } | ||||
|  | ||||
| impl Write for Http11Message { | ||||
|     #[inline] | ||||
|     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
|         match self.writer { | ||||
|             None => Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                           "Not in a writable state")), | ||||
|             Some(ref mut writer) => writer.write(buf), | ||||
|         } | ||||
|     } | ||||
|     #[inline] | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         match self.writer { | ||||
|             None => Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                           "Not in a writable state")), | ||||
|             Some(ref mut writer) => writer.flush(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Read for Http11Message { | ||||
|     #[inline] | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         match self.reader { | ||||
|             None => Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                           "Not in a readable state")), | ||||
|             Some(ref mut reader) => reader.read(buf), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl HttpMessage for Http11Message { | ||||
|     fn set_outgoing(&mut self, mut head: RequestHead) -> ::Result<RequestHead> { | ||||
|         if self.stream.is_none() { | ||||
|             return Err(From::from(io::Error::new( | ||||
|                         io::ErrorKind::Other, | ||||
|                         "Message not idle, cannot start new outgoing"))); | ||||
|         } | ||||
|         let mut stream = BufWriter::new(self.stream.take().unwrap()); | ||||
|  | ||||
|         let mut uri = head.url.serialize_path().unwrap(); | ||||
|         if let Some(ref q) = head.url.query { | ||||
|             uri.push('?'); | ||||
|             uri.push_str(&q[..]); | ||||
|         } | ||||
|  | ||||
|         let version = version::HttpVersion::Http11; | ||||
|         debug!("request line: {:?} {:?} {:?}", head.method, uri, version); | ||||
|         try!(write!(&mut stream, "{} {} {}{}", | ||||
|                     head.method, uri, version, LINE_ENDING)); | ||||
|  | ||||
|         let stream = match head.method { | ||||
|             Method::Get | Method::Head => { | ||||
|                 debug!("headers={:?}", head.headers); | ||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); | ||||
|                 EmptyWriter(stream) | ||||
|             }, | ||||
|             _ => { | ||||
|                 let mut chunked = true; | ||||
|                 let mut len = 0; | ||||
|  | ||||
|                 match head.headers.get::<header::ContentLength>() { | ||||
|                     Some(cl) => { | ||||
|                         chunked = false; | ||||
|                         len = **cl; | ||||
|                     }, | ||||
|                     None => () | ||||
|                 }; | ||||
|  | ||||
|                 // can't do in match above, thanks borrowck | ||||
|                 if chunked { | ||||
|                     let encodings = match head.headers.get_mut::<header::TransferEncoding>() { | ||||
|                         Some(&mut header::TransferEncoding(ref mut encodings)) => { | ||||
|                             //TODO: check if chunked is already in encodings. use HashSet? | ||||
|                             encodings.push(header::Encoding::Chunked); | ||||
|                             false | ||||
|                         }, | ||||
|                         None => true | ||||
|                     }; | ||||
|  | ||||
|                     if encodings { | ||||
|                         head.headers.set::<header::TransferEncoding>( | ||||
|                             header::TransferEncoding(vec![header::Encoding::Chunked])) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 debug!("headers={:?}", head.headers); | ||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); | ||||
|  | ||||
|                 if chunked { | ||||
|                     ChunkedWriter(stream) | ||||
|                 } else { | ||||
|                     SizedWriter(stream, len) | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         self.writer = Some(stream); | ||||
|  | ||||
|         Ok(head) | ||||
|     } | ||||
|  | ||||
|     fn get_incoming(&mut self) -> ::Result<ResponseHead> { | ||||
|         try!(self.flush_outgoing()); | ||||
|         if self.stream.is_none() { | ||||
|             // The message was already in the reading state... | ||||
|             // TODO Decide what happens in case we try to get a new incoming at that point | ||||
|             return Err(From::from( | ||||
|                     io::Error::new(io::ErrorKind::Other, | ||||
|                     "Read already in progress"))); | ||||
|         } | ||||
|  | ||||
|         let stream = self.stream.take().unwrap(); | ||||
|         let mut stream = BufReader::new(stream); | ||||
|  | ||||
|         let head = try!(parse_response(&mut stream)); | ||||
|         let raw_status = head.subject; | ||||
|         let headers = head.headers; | ||||
|  | ||||
|         let body = if headers.has::<TransferEncoding>() { | ||||
|             match headers.get::<TransferEncoding>() { | ||||
|                 Some(&TransferEncoding(ref codings)) => { | ||||
|                     if codings.len() > 1 { | ||||
|                         trace!("TODO: #2 handle other codings: {:?}", codings); | ||||
|                     }; | ||||
|  | ||||
|                     if codings.contains(&Chunked) { | ||||
|                         ChunkedReader(stream, None) | ||||
|                     } else { | ||||
|                         trace!("not chuncked. read till eof"); | ||||
|                         EofReader(stream) | ||||
|                     } | ||||
|                 } | ||||
|                 None => unreachable!() | ||||
|             } | ||||
|         } else if headers.has::<ContentLength>() { | ||||
|             match headers.get::<ContentLength>() { | ||||
|                 Some(&ContentLength(len)) => SizedReader(stream, len), | ||||
|                 None => unreachable!() | ||||
|             } | ||||
|         } else { | ||||
|             trace!("neither Transfer-Encoding nor Content-Length"); | ||||
|             EofReader(stream) | ||||
|         }; | ||||
|  | ||||
|         self.reader = Some(body); | ||||
|  | ||||
|         Ok(ResponseHead { | ||||
|             headers: headers, | ||||
|             raw_status: raw_status, | ||||
|             version: head.version, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn close_connection(&mut self) -> ::Result<()> { | ||||
|         try!(self.get_mut().close(Shutdown::Both)); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Http11Message { | ||||
|     /// Consumes the `Http11Message` and returns the underlying `NetworkStream`. | ||||
|     pub fn into_inner(mut self) -> Box<NetworkStream + Send> { | ||||
|         if self.stream.is_some() { | ||||
|             self.stream.take().unwrap() | ||||
|         } else if self.writer.is_some() { | ||||
|             self.writer.take().unwrap().into_inner().into_inner().unwrap() | ||||
|         } else if self.reader.is_some() { | ||||
|             self.reader.take().unwrap().into_inner().into_inner() | ||||
|         } else { | ||||
|             panic!("Http11Message lost its underlying stream somehow"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Gets a mutable reference to the underlying `NetworkStream`, regardless of the state of the | ||||
|     /// `Http11Message`. | ||||
|     pub fn get_mut(&mut self) -> &mut Box<NetworkStream + Send> { | ||||
|         if self.stream.is_some() { | ||||
|             self.stream.as_mut().unwrap() | ||||
|         } else if self.writer.is_some() { | ||||
|             self.writer.as_mut().unwrap().get_mut().get_mut() | ||||
|         } else if self.reader.is_some() { | ||||
|             self.reader.as_mut().unwrap().get_mut().get_mut() | ||||
|         } else { | ||||
|             panic!("Http11Message lost its underlying stream somehow"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Creates a new `Http11Message` that will use the given `NetworkStream` for communicating to | ||||
|     /// the peer. | ||||
|     pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message { | ||||
|         Http11Message { | ||||
|             stream: Some(stream), | ||||
|             writer: None, | ||||
|             reader: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Flushes the current outgoing content and moves the stream into the `stream` property. | ||||
|     /// | ||||
|     /// TODO It might be sensible to lift this up to the `HttpMessage` trait itself... | ||||
|     pub fn flush_outgoing(&mut self) -> ::Result<()> { | ||||
|         match self.writer { | ||||
|             None => return Ok(()), | ||||
|             Some(_) => {}, | ||||
|         }; | ||||
|  | ||||
|         let writer = self.writer.take().unwrap(); | ||||
|         let raw = try!(writer.end()).into_inner().unwrap(); // end() already flushes | ||||
|         self.stream = Some(raw); | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// The `Protocol` implementation provides HTTP/1.1 messages. | ||||
| pub struct Http11Protocol { | ||||
|     connector: Connector, | ||||
| } | ||||
|  | ||||
| impl Protocol for Http11Protocol { | ||||
|     fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result<Box<HttpMessage>> { | ||||
|         let stream = try!(self.connector.connect(host, port, scheme)).into(); | ||||
|  | ||||
|         Ok(Box::new(Http11Message::with_stream(stream))) | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { | ||||
|         self.connector.set_ssl_verifier(verifier); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Http11Protocol { | ||||
|     /// Creates a new `Http11Protocol` instance that will use the given `NetworkConnector` for | ||||
|     /// establishing HTTP connections. | ||||
|     pub fn with_connector<C, S>(c: C) -> Http11Protocol | ||||
|             where C: NetworkConnector<Stream=S> + Send + 'static, | ||||
|                   S: NetworkStream + Send { | ||||
|         Http11Protocol { | ||||
|             connector: Connector(Box::new(ConnAdapter(c))), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ConnAdapter<C: NetworkConnector + Send>(C); | ||||
|  | ||||
| impl<C: NetworkConnector<Stream=S> + Send, S: NetworkStream + Send> NetworkConnector for ConnAdapter<C> { | ||||
|     type Stream = Box<NetworkStream + Send>; | ||||
|     #[inline] | ||||
|     fn connect(&self, host: &str, port: u16, scheme: &str) | ||||
|         -> ::Result<Box<NetworkStream + Send>> { | ||||
|         Ok(try!(self.0.connect(host, port, scheme)).into()) | ||||
|     } | ||||
|     #[inline] | ||||
|     fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { | ||||
|         self.0.set_ssl_verifier(verifier); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct Connector(Box<NetworkConnector<Stream=Box<NetworkStream + Send>> + Send>); | ||||
|  | ||||
| impl NetworkConnector for Connector { | ||||
|     type Stream = Box<NetworkStream + Send>; | ||||
|     #[inline] | ||||
|     fn connect(&self, host: &str, port: u16, scheme: &str) | ||||
|         -> ::Result<Box<NetworkStream + Send>> { | ||||
|         Ok(try!(self.0.connect(host, port, scheme)).into()) | ||||
|     } | ||||
|     #[inline] | ||||
|     fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { | ||||
|         self.0.set_ssl_verifier(verifier); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Readers to handle different Transfer-Encodings. | ||||
| /// | ||||
| /// If a message body does not include a Transfer-Encoding, it *should* | ||||
| /// include a Content-Length header. | ||||
| pub enum HttpReader<R> { | ||||
|     /// A Reader used when a Content-Length header is passed with a positive integer. | ||||
|     SizedReader(R, u64), | ||||
|     /// A Reader used when Transfer-Encoding is `chunked`. | ||||
|     ChunkedReader(R, Option<u64>), | ||||
|     /// A Reader used for responses that don't indicate a length or chunked. | ||||
|     /// | ||||
|     /// Note: This should only used for `Response`s. It is illegal for a | ||||
|     /// `Request` to be made with both `Content-Length` and | ||||
|     /// `Transfer-Encoding: chunked` missing, as explained from the spec: | ||||
|     /// | ||||
|     /// > If a Transfer-Encoding header field is present in a response and | ||||
|     /// > the chunked transfer coding is not the final encoding, the | ||||
|     /// > message body length is determined by reading the connection until | ||||
|     /// > it is closed by the server.  If a Transfer-Encoding header field | ||||
|     /// > is present in a request and the chunked transfer coding is not | ||||
|     /// > the final encoding, the message body length cannot be determined | ||||
|     /// > reliably; the server MUST respond with the 400 (Bad Request) | ||||
|     /// > status code and then close the connection. | ||||
|     EofReader(R), | ||||
|     /// A Reader used for messages that should never have a body. | ||||
|     /// | ||||
|     /// See https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||
|     EmptyReader(R), | ||||
| } | ||||
|  | ||||
| impl<R: Read> HttpReader<R> { | ||||
|  | ||||
|     /// Unwraps this HttpReader and returns the underlying Reader. | ||||
|     pub fn into_inner(self) -> R { | ||||
|         match self { | ||||
|             SizedReader(r, _) => r, | ||||
|             ChunkedReader(r, _) => r, | ||||
|             EofReader(r) => r, | ||||
|             EmptyReader(r) => r, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Gets a mutable reference to the underlying Reader. | ||||
|     pub fn get_mut(&mut self) -> &mut R { | ||||
|         match *self { | ||||
|             SizedReader(ref mut r, _) => r, | ||||
|             ChunkedReader(ref mut r, _) => r, | ||||
|             EofReader(ref mut r) => r, | ||||
|             EmptyReader(ref mut r) => r, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<R> fmt::Debug for HttpReader<R> { | ||||
|     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         match *self { | ||||
|             SizedReader(_,rem) => write!(fmt, "SizedReader(remaining={:?})", rem), | ||||
|             ChunkedReader(_, None) => write!(fmt, "ChunkedReader(chunk_remaining=unknown)"), | ||||
|             ChunkedReader(_, Some(rem)) => write!(fmt, "ChunkedReader(chunk_remaining={:?})", rem), | ||||
|             EofReader(_) => write!(fmt, "EofReader"), | ||||
|             EmptyReader(_) => write!(fmt, "EmptyReader"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<R: Read> Read for HttpReader<R> { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         match *self { | ||||
|             SizedReader(ref mut body, ref mut remaining) => { | ||||
|                 trace!("Sized read, remaining={:?}", remaining); | ||||
|                 if *remaining == 0 { | ||||
|                     Ok(0) | ||||
|                 } else { | ||||
|                     let num = try!(body.read(buf)) as u64; | ||||
|                     if num > *remaining { | ||||
|                         *remaining = 0; | ||||
|                     } else { | ||||
|                         *remaining -= num; | ||||
|                     } | ||||
|                     Ok(num as usize) | ||||
|                 } | ||||
|             }, | ||||
|             ChunkedReader(ref mut body, ref mut opt_remaining) => { | ||||
|                 let mut rem = match *opt_remaining { | ||||
|                     Some(ref rem) => *rem, | ||||
|                     // None means we don't know the size of the next chunk | ||||
|                     None => try!(read_chunk_size(body)) | ||||
|                 }; | ||||
|                 trace!("Chunked read, remaining={:?}", rem); | ||||
|  | ||||
|                 if rem == 0 { | ||||
|                     *opt_remaining = Some(0); | ||||
|  | ||||
|                     // chunk of size 0 signals the end of the chunked stream | ||||
|                     // if the 0 digit was missing from the stream, it would | ||||
|                     // be an InvalidInput error instead. | ||||
|                     trace!("end of chunked"); | ||||
|                     return Ok(0) | ||||
|                 } | ||||
|  | ||||
|                 let to_read = min(rem as usize, buf.len()); | ||||
|                 let count = try!(body.read(&mut buf[..to_read])) as u64; | ||||
|  | ||||
|                 rem -= count; | ||||
|                 *opt_remaining = if rem > 0 { | ||||
|                     Some(rem) | ||||
|                 } else { | ||||
|                     try!(eat(body, LINE_ENDING.as_bytes())); | ||||
|                     None | ||||
|                 }; | ||||
|                 Ok(count as usize) | ||||
|             }, | ||||
|             EofReader(ref mut body) => { | ||||
|                 let r = body.read(buf); | ||||
|                 trace!("eofread: {:?}", r); | ||||
|                 r | ||||
|             }, | ||||
|             EmptyReader(_) => Ok(0) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn eat<R: Read>(rdr: &mut R, bytes: &[u8]) -> io::Result<()> { | ||||
|     let mut buf = [0]; | ||||
|     for &b in bytes.iter() { | ||||
|         match try!(rdr.read(&mut buf)) { | ||||
|             1 if buf[0] == b => (), | ||||
|             _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, | ||||
|                                           "Invalid characters found")), | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Chunked chunks start with 1*HEXDIGIT, indicating the size of the chunk. | ||||
| fn read_chunk_size<R: Read>(rdr: &mut R) -> io::Result<u64> { | ||||
|     macro_rules! byte ( | ||||
|         ($rdr:ident) => ({ | ||||
|             let mut buf = [0]; | ||||
|             match try!($rdr.read(&mut buf)) { | ||||
|                 1 => buf[0], | ||||
|                 _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, | ||||
|                                                   "Invalid chunk size line")), | ||||
|  | ||||
|             } | ||||
|         }) | ||||
|     ); | ||||
|     let mut size = 0u64; | ||||
|     let radix = 16; | ||||
|     let mut in_ext = false; | ||||
|     let mut in_chunk_size = true; | ||||
|     loop { | ||||
|         match byte!(rdr) { | ||||
|             b@b'0'...b'9' if in_chunk_size => { | ||||
|                 size *= radix; | ||||
|                 size += (b - b'0') as u64; | ||||
|             }, | ||||
|             b@b'a'...b'f' if in_chunk_size => { | ||||
|                 size *= radix; | ||||
|                 size += (b + 10 - b'a') as u64; | ||||
|             }, | ||||
|             b@b'A'...b'F' if in_chunk_size => { | ||||
|                 size *= radix; | ||||
|                 size += (b + 10 - b'A') as u64; | ||||
|             }, | ||||
|             CR => { | ||||
|                 match byte!(rdr) { | ||||
|                     LF => break, | ||||
|                     _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, | ||||
|                                                   "Invalid chunk size line")) | ||||
|  | ||||
|                 } | ||||
|             }, | ||||
|             // If we weren't in the extension yet, the ";" signals its start | ||||
|             b';' if !in_ext => { | ||||
|                 in_ext = true; | ||||
|                 in_chunk_size = false; | ||||
|             }, | ||||
|             // "Linear white space" is ignored between the chunk size and the | ||||
|             // extension separator token (";") due to the "implied *LWS rule". | ||||
|             b'\t' | b' ' if !in_ext & !in_chunk_size => {}, | ||||
|             // LWS can follow the chunk size, but no more digits can come | ||||
|             b'\t' | b' ' if in_chunk_size => in_chunk_size = false, | ||||
|             // We allow any arbitrary octet once we are in the extension, since | ||||
|             // they all get ignored anyway. According to the HTTP spec, valid | ||||
|             // extensions would have a more strict syntax: | ||||
|             //     (token ["=" (token | quoted-string)]) | ||||
|             // but we gain nothing by rejecting an otherwise valid chunk size. | ||||
|             ext if in_ext => { | ||||
|                 todo!("chunk extension byte={}", ext); | ||||
|             }, | ||||
|             // Finally, if we aren't in the extension and we're reading any | ||||
|             // other octet, the chunk size line is invalid! | ||||
|             _ => { | ||||
|                 return Err(io::Error::new(io::ErrorKind::InvalidInput, | ||||
|                                          "Invalid chunk size line")); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     trace!("chunk size={:?}", size); | ||||
|     Ok(size) | ||||
| } | ||||
|  | ||||
| /// Writers to handle different Transfer-Encodings. | ||||
| pub enum HttpWriter<W: Write> { | ||||
|     /// A no-op Writer, used initially before Transfer-Encoding is determined. | ||||
|     ThroughWriter(W), | ||||
|     /// A Writer for when Transfer-Encoding includes `chunked`. | ||||
|     ChunkedWriter(W), | ||||
|     /// A Writer for when Content-Length is set. | ||||
|     /// | ||||
|     /// Enforces that the body is not longer than the Content-Length header. | ||||
|     SizedWriter(W, u64), | ||||
|     /// A writer that should not write any body. | ||||
|     EmptyWriter(W), | ||||
| } | ||||
|  | ||||
| impl<W: Write> HttpWriter<W> { | ||||
|     /// Unwraps the HttpWriter and returns the underlying Writer. | ||||
|     #[inline] | ||||
|     pub fn into_inner(self) -> W { | ||||
|         match self { | ||||
|             ThroughWriter(w) => w, | ||||
|             ChunkedWriter(w) => w, | ||||
|             SizedWriter(w, _) => w, | ||||
|             EmptyWriter(w) => w, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Access the inner Writer. | ||||
|     #[inline] | ||||
|     pub fn get_ref<'a>(&'a self) -> &'a W { | ||||
|         match *self { | ||||
|             ThroughWriter(ref w) => w, | ||||
|             ChunkedWriter(ref w) => w, | ||||
|             SizedWriter(ref w, _) => w, | ||||
|             EmptyWriter(ref w) => w, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Access the inner Writer mutably. | ||||
|     /// | ||||
|     /// Warning: You should not write to this directly, as you can corrupt | ||||
|     /// the state. | ||||
|     #[inline] | ||||
|     pub fn get_mut<'a>(&'a mut self) -> &'a mut W { | ||||
|         match *self { | ||||
|             ThroughWriter(ref mut w) => w, | ||||
|             ChunkedWriter(ref mut w) => w, | ||||
|             SizedWriter(ref mut w, _) => w, | ||||
|             EmptyWriter(ref mut w) => w, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Ends the HttpWriter, and returns the underlying Writer. | ||||
|     /// | ||||
|     /// A final `write_all()` is called with an empty message, and then flushed. | ||||
|     /// The ChunkedWriter variant will use this to write the 0-sized last-chunk. | ||||
|     #[inline] | ||||
|     pub fn end(mut self) -> io::Result<W> { | ||||
|         try!(self.write(&[])); | ||||
|         try!(self.flush()); | ||||
|         Ok(self.into_inner()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<W: Write> Write for HttpWriter<W> { | ||||
|     #[inline] | ||||
|     fn write(&mut self, msg: &[u8]) -> io::Result<usize> { | ||||
|         match *self { | ||||
|             ThroughWriter(ref mut w) => w.write(msg), | ||||
|             ChunkedWriter(ref mut w) => { | ||||
|                 let chunk_size = msg.len(); | ||||
|                 trace!("chunked write, size = {:?}", chunk_size); | ||||
|                 try!(write!(w, "{:X}{}", chunk_size, LINE_ENDING)); | ||||
|                 try!(w.write_all(msg)); | ||||
|                 try!(w.write_all(LINE_ENDING.as_bytes())); | ||||
|                 Ok(msg.len()) | ||||
|             }, | ||||
|             SizedWriter(ref mut w, ref mut remaining) => { | ||||
|                 let len = msg.len() as u64; | ||||
|                 if len > *remaining { | ||||
|                     let len = *remaining; | ||||
|                     *remaining = 0; | ||||
|                     try!(w.write_all(&msg[..len as usize])); | ||||
|                     Ok(len as usize) | ||||
|                 } else { | ||||
|                     *remaining -= len; | ||||
|                     try!(w.write_all(msg)); | ||||
|                     Ok(len as usize) | ||||
|                 } | ||||
|             }, | ||||
|             EmptyWriter(..) => { | ||||
|                 if !msg.is_empty() { | ||||
|                     error!("Cannot include a body with this kind of message"); | ||||
|                 } | ||||
|                 Ok(0) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         match *self { | ||||
|             ThroughWriter(ref mut w) => w.flush(), | ||||
|             ChunkedWriter(ref mut w) => w.flush(), | ||||
|             SizedWriter(ref mut w, _) => w.flush(), | ||||
|             EmptyWriter(ref mut w) => w.flush(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<W: Write> fmt::Debug for HttpWriter<W> { | ||||
|     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         match *self { | ||||
|             ThroughWriter(_) => write!(fmt, "ThroughWriter"), | ||||
|             ChunkedWriter(_) => write!(fmt, "ChunkedWriter"), | ||||
|             SizedWriter(_, rem) => write!(fmt, "SizedWriter(remaining={:?})", rem), | ||||
|             EmptyWriter(_) => write!(fmt, "EmptyWriter"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| const MAX_HEADERS: usize = 100; | ||||
|  | ||||
| /// Parses a request into an Incoming message head. | ||||
| #[inline] | ||||
| pub fn parse_request<R: Read>(buf: &mut BufReader<R>) -> ::Result<Incoming<(Method, RequestUri)>> { | ||||
|     parse::<R, httparse::Request, (Method, RequestUri)>(buf) | ||||
| } | ||||
|  | ||||
| /// Parses a response into an Incoming message head. | ||||
| #[inline] | ||||
| pub fn parse_response<R: Read>(buf: &mut BufReader<R>) -> ::Result<Incoming<RawStatus>> { | ||||
|     parse::<R, httparse::Response, RawStatus>(buf) | ||||
| } | ||||
|  | ||||
| fn parse<R: Read, T: TryParse<Subject=I>, I>(rdr: &mut BufReader<R>) -> ::Result<Incoming<I>> { | ||||
|     loop { | ||||
|         match try!(try_parse::<R, T, I>(rdr)) { | ||||
|             httparse::Status::Complete((inc, len)) => { | ||||
|                 rdr.consume(len); | ||||
|                 return Ok(inc); | ||||
|             }, | ||||
|             _partial => () | ||||
|         } | ||||
|         match try!(rdr.read_into_buf()) { | ||||
|             0 if rdr.get_buf().is_empty() => { | ||||
|                 return Err(Error::Io(io::Error::new( | ||||
|                     io::ErrorKind::ConnectionAborted, | ||||
|                     "Connection closed" | ||||
|                 ))) | ||||
|             }, | ||||
|             0 => return Err(Error::TooLarge), | ||||
|             _ => () | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn try_parse<R: Read, T: TryParse<Subject=I>, I>(rdr: &mut BufReader<R>) -> TryParseResult<I> { | ||||
|     let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; | ||||
|     <T as TryParse>::try_parse(&mut headers, rdr.get_buf()) | ||||
| } | ||||
|  | ||||
| #[doc(hidden)] | ||||
| trait TryParse { | ||||
|     type Subject; | ||||
|     fn try_parse<'a>(headers: &'a mut [httparse::Header<'a>], buf: &'a [u8]) -> TryParseResult<Self::Subject>; | ||||
| } | ||||
|  | ||||
| type TryParseResult<T> = Result<httparse::Status<(Incoming<T>, usize)>, Error>; | ||||
|  | ||||
| impl<'a> TryParse for httparse::Request<'a, 'a> { | ||||
|     type Subject = (Method, RequestUri); | ||||
|  | ||||
|     fn try_parse<'b>(headers: &'b mut [httparse::Header<'b>], buf: &'b [u8]) -> TryParseResult<(Method, RequestUri)> { | ||||
|         let mut req = httparse::Request::new(headers); | ||||
|         Ok(match try!(req.parse(buf)) { | ||||
|             httparse::Status::Complete(len) => { | ||||
|                 httparse::Status::Complete((Incoming { | ||||
|                     version: if req.version.unwrap() == 1 { Http11 } else { Http10 }, | ||||
|                     subject: ( | ||||
|                         try!(req.method.unwrap().parse()), | ||||
|                         try!(req.path.unwrap().parse()) | ||||
|                     ), | ||||
|                     headers: try!(Headers::from_raw(req.headers)) | ||||
|                 }, len)) | ||||
|             }, | ||||
|             httparse::Status::Partial => httparse::Status::Partial | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> TryParse for httparse::Response<'a, 'a> { | ||||
|     type Subject = RawStatus; | ||||
|  | ||||
|     fn try_parse<'b>(headers: &'b mut [httparse::Header<'b>], buf: &'b [u8]) -> TryParseResult<RawStatus> { | ||||
|         let mut res = httparse::Response::new(headers); | ||||
|         Ok(match try!(res.parse(buf)) { | ||||
|             httparse::Status::Complete(len) => { | ||||
|                 let code = res.code.unwrap(); | ||||
|                 let reason = match StatusCode::from_u16(code).canonical_reason() { | ||||
|                     Some(reason) if reason == res.reason.unwrap() => Cow::Borrowed(reason), | ||||
|                     _ => Cow::Owned(res.reason.unwrap().to_owned()) | ||||
|                 }; | ||||
|                 httparse::Status::Complete((Incoming { | ||||
|                     version: if res.version.unwrap() == 1 { Http11 } else { Http10 }, | ||||
|                     subject: RawStatus(code, reason), | ||||
|                     headers: try!(Headers::from_raw(res.headers)) | ||||
|                 }, len)) | ||||
|             }, | ||||
|             httparse::Status::Partial => httparse::Status::Partial | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// An Incoming Message head. Includes request/status line, and headers. | ||||
| #[derive(Debug)] | ||||
| pub struct Incoming<S> { | ||||
|     /// HTTP version of the message. | ||||
|     pub version: HttpVersion, | ||||
|     /// Subject (request line or status line) of Incoming message. | ||||
|     pub subject: S, | ||||
|     /// Headers of the Incoming message. | ||||
|     pub headers: Headers | ||||
| } | ||||
|  | ||||
| pub const SP: u8 = b' '; | ||||
| pub const CR: u8 = b'\r'; | ||||
| pub const LF: u8 = b'\n'; | ||||
| pub const STAR: u8 = b'*'; | ||||
| pub const LINE_ENDING: &'static str = "\r\n"; | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use std::io::{self, Write}; | ||||
|  | ||||
|     use buffer::BufReader; | ||||
|     use mock::MockStream; | ||||
|  | ||||
|     use super::{read_chunk_size, parse_request, parse_response}; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_write_chunked() { | ||||
|         use std::str::from_utf8; | ||||
|         let mut w = super::HttpWriter::ChunkedWriter(Vec::new()); | ||||
|         w.write_all(b"foo bar").unwrap(); | ||||
|         w.write_all(b"baz quux herp").unwrap(); | ||||
|         let buf = w.end().unwrap(); | ||||
|         let s = from_utf8(buf.as_ref()).unwrap(); | ||||
|         assert_eq!(s, "7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n0\r\n\r\n"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_write_sized() { | ||||
|         use std::str::from_utf8; | ||||
|         let mut w = super::HttpWriter::SizedWriter(Vec::new(), 8); | ||||
|         w.write_all(b"foo bar").unwrap(); | ||||
|         assert_eq!(w.write(b"baz").unwrap(), 1); | ||||
|  | ||||
|         let buf = w.end().unwrap(); | ||||
|         let s = from_utf8(buf.as_ref()).unwrap(); | ||||
|         assert_eq!(s, "foo barb"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_read_chunk_size() { | ||||
|         fn read(s: &str, result: u64) { | ||||
|             assert_eq!(read_chunk_size(&mut s.as_bytes()).unwrap(), result); | ||||
|         } | ||||
|  | ||||
|         fn read_err(s: &str) { | ||||
|             assert_eq!(read_chunk_size(&mut s.as_bytes()).unwrap_err().kind(), io::ErrorKind::InvalidInput); | ||||
|         } | ||||
|  | ||||
|         read("1\r\n", 1); | ||||
|         read("01\r\n", 1); | ||||
|         read("0\r\n", 0); | ||||
|         read("00\r\n", 0); | ||||
|         read("A\r\n", 10); | ||||
|         read("a\r\n", 10); | ||||
|         read("Ff\r\n", 255); | ||||
|         read("Ff   \r\n", 255); | ||||
|         // Missing LF or CRLF | ||||
|         read_err("F\rF"); | ||||
|         read_err("F"); | ||||
|         // Invalid hex digit | ||||
|         read_err("X\r\n"); | ||||
|         read_err("1X\r\n"); | ||||
|         read_err("-\r\n"); | ||||
|         read_err("-1\r\n"); | ||||
|         // Acceptable (if not fully valid) extensions do not influence the size | ||||
|         read("1;extension\r\n", 1); | ||||
|         read("a;ext name=value\r\n", 10); | ||||
|         read("1;extension;extension2\r\n", 1); | ||||
|         read("1;;;  ;\r\n", 1); | ||||
|         read("2; extension...\r\n", 2); | ||||
|         read("3   ; extension=123\r\n", 3); | ||||
|         read("3   ;\r\n", 3); | ||||
|         read("3   ;   \r\n", 3); | ||||
|         // Invalid extensions cause an error | ||||
|         read_err("1 invalid extension\r\n"); | ||||
|         read_err("1 A\r\n"); | ||||
|         read_err("1;no CRLF"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_incoming() { | ||||
|         let mut raw = MockStream::with_input(b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n"); | ||||
|         let mut buf = BufReader::new(&mut raw); | ||||
|         parse_request(&mut buf).unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_raw_status() { | ||||
|         let mut raw = MockStream::with_input(b"HTTP/1.1 200 OK\r\n\r\n"); | ||||
|         let mut buf = BufReader::new(&mut raw); | ||||
|         let res = parse_response(&mut buf).unwrap(); | ||||
|  | ||||
|         assert_eq!(res.subject.1, "OK"); | ||||
|  | ||||
|         let mut raw = MockStream::with_input(b"HTTP/1.1 200 Howdy\r\n\r\n"); | ||||
|         let mut buf = BufReader::new(&mut raw); | ||||
|         let res = parse_response(&mut buf).unwrap(); | ||||
|  | ||||
|         assert_eq!(res.subject.1, "Howdy"); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_tcp_closed() { | ||||
|         use std::io::ErrorKind; | ||||
|         use error::Error; | ||||
|  | ||||
|         let mut empty = MockStream::new(); | ||||
|         let mut buf = BufReader::new(&mut empty); | ||||
|         match parse_request(&mut buf) { | ||||
|             Err(Error::Io(ref e)) if e.kind() == ErrorKind::ConnectionAborted => (), | ||||
|             other => panic!("unexpected result: {:?}", other) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     use test::Bencher; | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     #[bench] | ||||
|     fn bench_parse_incoming(b: &mut Bencher) { | ||||
|         let mut raw = MockStream::with_input(b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n"); | ||||
|         let mut buf = BufReader::new(&mut raw); | ||||
|         b.iter(|| { | ||||
|             parse_request(&mut buf).unwrap(); | ||||
|             buf.get_mut().read.set_position(0); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user