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); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										784
									
								
								src/http/h2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										784
									
								
								src/http/h2.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,784 @@ | ||||
| //! Adapts the `solicit`-provided HTTP/2 implementation into the `HttpMessage` API. | ||||
|  | ||||
| use std::io::{self, Write, Read, Cursor}; | ||||
| use std::net::Shutdown; | ||||
| use std::ascii::AsciiExt; | ||||
| use std::mem; | ||||
|  | ||||
| use http::{ | ||||
|     Protocol, | ||||
|     HttpMessage, | ||||
|     RequestHead, | ||||
|     ResponseHead, | ||||
|     RawStatus, | ||||
| }; | ||||
| use net::{NetworkStream, NetworkConnector, ContextVerifier}; | ||||
| use net::{HttpConnector, HttpStream}; | ||||
| use url::Url; | ||||
| use header::Headers; | ||||
|  | ||||
| use header; | ||||
| use version; | ||||
|  | ||||
| use solicit::http::Header as Http2Header; | ||||
| use solicit::http::HttpScheme; | ||||
| use solicit::http::HttpError as Http2Error; | ||||
| use solicit::http::transport::TransportStream; | ||||
| use solicit::http::client::{ClientStream, HttpConnect, write_preface}; | ||||
| use solicit::client::SimpleClient; | ||||
|  | ||||
| use httparse; | ||||
|  | ||||
| /// A trait alias representing all types that are both `NetworkStream` and `Clone`. | ||||
| pub trait CloneableStream: NetworkStream + Clone {} | ||||
| impl<S: NetworkStream + Clone> CloneableStream for S {} | ||||
|  | ||||
| /// A newtype wrapping any `CloneableStream` in order to provide an implementation of a | ||||
| /// `TransportSream` trait for all types that are a `CloneableStream`. | ||||
| #[derive(Clone)] | ||||
| struct Http2Stream<S: CloneableStream>(S); | ||||
|  | ||||
| impl<S> Write for Http2Stream<S> where S: CloneableStream { | ||||
|     #[inline] | ||||
|     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
|         self.0.write(buf) | ||||
|     } | ||||
|     #[inline] | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         self.0.flush() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> Read for Http2Stream<S> where S: CloneableStream { | ||||
|     #[inline] | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         self.0.read(buf) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> TransportStream for Http2Stream<S> where S: CloneableStream { | ||||
|     fn try_split(&self) -> Result<Http2Stream<S>, io::Error> { | ||||
|         Ok(self.clone()) | ||||
|     } | ||||
|  | ||||
|     fn close(&mut self) -> Result<(), io::Error> { | ||||
|         self.0.close(Shutdown::Both) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A helper struct that implements the `HttpConnect` trait from the `solicit` crate. | ||||
| /// | ||||
| /// This is used by the `Http2Protocol` when it needs to create a new `SimpleClient`. | ||||
| struct Http2Connector<S> where S: CloneableStream { | ||||
|     stream: S, | ||||
|     scheme: HttpScheme, | ||||
|     host: String, | ||||
| } | ||||
|  | ||||
| impl<S> HttpConnect for Http2Connector<S> where S: CloneableStream { | ||||
|     /// The type of the underlying transport stream that the `HttpConnection`s | ||||
|     /// produced by this `HttpConnect` implementation will be based on. | ||||
|     type Stream = Http2Stream<S>; | ||||
|     /// The type of the error that can be produced by trying to establish the | ||||
|     /// connection (i.e. calling the `connect` method). | ||||
|     type Err = ::Error; | ||||
|  | ||||
|     /// Establishes a network connection that can be used by HTTP/2 connections. | ||||
|     fn connect(mut self) -> Result<ClientStream<Self::Stream>, Self::Err> { | ||||
|         try!(write_preface(&mut self.stream)); | ||||
|         Ok(ClientStream(Http2Stream(self.stream), self.scheme, self.host)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// The `Protocol` implementation that provides HTTP/2 messages (i.e. `Http2Message`). | ||||
| pub struct Http2Protocol<C, S> where C: NetworkConnector<Stream=S> + Send + 'static, | ||||
|                                  S: NetworkStream + Send + Clone { | ||||
|     connector: C, | ||||
| } | ||||
|  | ||||
| impl<C, S> Http2Protocol<C, S> where C: NetworkConnector<Stream=S> + Send + 'static, | ||||
|                                      S: NetworkStream + Send + Clone { | ||||
|     /// Create a new `Http2Protocol` that will use the given `NetworkConnector` to establish TCP | ||||
|     /// connections to the server. | ||||
|     pub fn with_connector(connector: C) -> Http2Protocol<C, S> { | ||||
|         Http2Protocol { | ||||
|             connector: connector, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// A private helper method that creates a new `SimpleClient` that will use the given | ||||
|     /// `NetworkStream` to communicate to the remote host. | ||||
|     fn new_client(&self, stream: S, host: String, scheme: HttpScheme) | ||||
|             -> ::Result<SimpleClient<Http2Stream<S>>> { | ||||
|         Ok(try!(SimpleClient::with_connector(Http2Connector { | ||||
|             stream: stream, | ||||
|             scheme: scheme, | ||||
|             host: host, | ||||
|         }))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<C, S> Protocol for Http2Protocol<C, S> where C: NetworkConnector<Stream=S> + Send + 'static, | ||||
|                                                   S: NetworkStream + Send + Clone { | ||||
|     fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result<Box<HttpMessage>> { | ||||
|         let stream = try!(self.connector.connect(host, port, scheme)).into(); | ||||
|  | ||||
|         let scheme = match scheme { | ||||
|             "http" => HttpScheme::Http, | ||||
|             "https" => HttpScheme::Https, | ||||
|             _ => return Err(From::from(Http2Error::from( | ||||
|                         io::Error::new(io::ErrorKind::Other, "Invalid scheme")))), | ||||
|         }; | ||||
|         let client = try!(self.new_client(stream, host.into(), scheme)); | ||||
|  | ||||
|         Ok(Box::new(Http2Message::with_client(client))) | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn set_ssl_verifier(&mut self, verifier: ContextVerifier) { | ||||
|         self.connector.set_ssl_verifier(verifier) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Represents an HTTP/2 request, described by a `RequestHead` and the body of the request. | ||||
| /// A convenience struct only in use by the `Http2Message`. | ||||
| #[derive(Clone, Debug)] | ||||
| struct Http2Request { | ||||
|     head: RequestHead, | ||||
|     body: Vec<u8>, | ||||
| } | ||||
|  | ||||
| /// Represents an HTTP/2 response. | ||||
| /// A convenience struct only in use by the `Http2Message`. | ||||
| #[derive(Clone, Debug)] | ||||
| struct Http2Response { | ||||
|     body: Cursor<Vec<u8>>, | ||||
| } | ||||
|  | ||||
| /// The enum tracks the state of the `Http2Message`. | ||||
| enum MessageState { | ||||
|     /// State corresponding to no message being set to outgoing yet. | ||||
|     Idle, | ||||
|     /// State corresponding to an outgoing message being written out. | ||||
|     Writing(Http2Request), | ||||
|     /// State corresponding to an incoming message being read. | ||||
|     Reading(Http2Response), | ||||
| } | ||||
|  | ||||
| impl MessageState { | ||||
|     fn take_request(&mut self) -> Option<Http2Request> { | ||||
|         match *self { | ||||
|             MessageState::Idle | MessageState::Reading(_) => return None, | ||||
|             MessageState::Writing(_) => {}, | ||||
|         } | ||||
|         let old = mem::replace(self, MessageState::Idle); | ||||
|  | ||||
|         match old { | ||||
|             // These states are effectively unreachable since we already know the state | ||||
|             MessageState::Idle | MessageState::Reading(_) => None, | ||||
|             MessageState::Writing(req) => Some(req), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// An implementation of the `HttpMessage` trait for HTTP/2. | ||||
| /// | ||||
| /// Relies on the `solicit::http::SimpleClient` for HTTP/2 communication. Adapts both outgoing and | ||||
| /// incoming messages to the API that `hyper` expects in order to be able to use the message in | ||||
| /// the `hyper::client` module. | ||||
| pub struct Http2Message<S> where S: CloneableStream { | ||||
|     client: SimpleClient<Http2Stream<S>>, | ||||
|     state: MessageState, | ||||
| } | ||||
|  | ||||
| impl<S> ::std::fmt::Debug for Http2Message<S> where S: CloneableStream { | ||||
|     fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { | ||||
|         write!(f, "<Http2Message>") | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> Http2Message<S> where S: CloneableStream { | ||||
|     /// Helper method that creates a new completely fresh `Http2Message`, which will use the given | ||||
|     /// `SimpleClient` for its HTTP/2 communication. | ||||
|     fn with_client(client: SimpleClient<Http2Stream<S>>) -> Http2Message<S> { | ||||
|         Http2Message { | ||||
|             client: client, | ||||
|             state: MessageState::Idle, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> Write for Http2Message<S> where S: CloneableStream { | ||||
|     #[inline] | ||||
|     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
|         if let MessageState::Writing(ref mut req) = self.state { | ||||
|             req.body.write(buf) | ||||
|         } else { | ||||
|             Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                "Not in a writable state")) | ||||
|         } | ||||
|     } | ||||
|     #[inline] | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         if let MessageState::Writing(ref mut req) = self.state { | ||||
|             req.body.flush() | ||||
|         } else { | ||||
|             Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                "Not in a writable state")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S> Read for Http2Message<S> where S: CloneableStream { | ||||
|     #[inline] | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         if let MessageState::Reading(ref mut res) = self.state { | ||||
|             res.body.read(buf) | ||||
|         } else { | ||||
|             Err(io::Error::new(io::ErrorKind::Other, | ||||
|                                "Not in a readable state")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A helper function that prepares the path of a request by extracting it from the given `Url`. | ||||
| fn prepare_path(url: Url) -> Vec<u8> { | ||||
|     let mut uri = url.serialize_path().unwrap(); | ||||
|     if let Some(ref q) = url.query { | ||||
|         uri.push('?'); | ||||
|         uri.push_str(&q[..]); | ||||
|     } | ||||
|     uri.into_bytes() | ||||
| } | ||||
|  | ||||
| /// A helper function that prepares the headers that should be sent in an HTTP/2 message. | ||||
| /// | ||||
| /// Adapts the `Headers` into a list of octet string pairs. | ||||
| fn prepare_headers(mut headers: Headers) -> Vec<Http2Header> { | ||||
|     if headers.remove::<header::Connection>() { | ||||
|         warn!("The `Connection` header is not valid for an HTTP/2 connection."); | ||||
|     } | ||||
|     let mut http2_headers: Vec<_> = headers.iter().filter_map(|h| { | ||||
|         if h.is::<header::SetCookie>() { | ||||
|             None | ||||
|         } else { | ||||
|             // HTTP/2 header names MUST be lowercase. | ||||
|             Some((h.name().to_ascii_lowercase().into_bytes(), h.value_string().into_bytes())) | ||||
|         } | ||||
|     }).collect(); | ||||
|  | ||||
|     // Now separately add the cookies, as `hyper` considers `Set-Cookie` to be only a single | ||||
|     // header, even in the face of multiple cookies being set. | ||||
|     if let Some(set_cookie) = headers.get::<header::SetCookie>() { | ||||
|         for cookie in set_cookie.iter() { | ||||
|             http2_headers.push((b"set-cookie".to_vec(), cookie.to_string().into_bytes())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     http2_headers | ||||
| } | ||||
|  | ||||
| /// A helper function that prepares the body for sending in an HTTP/2 request. | ||||
| #[inline] | ||||
| fn prepare_body(body: Vec<u8>) -> Option<Vec<u8>> { | ||||
|     if body.len() == 0 { | ||||
|         None | ||||
|     } else { | ||||
|         Some(body) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Parses a set of HTTP/2 headers into a `hyper::header::Headers` struct. | ||||
| fn parse_headers(http2_headers: Vec<Http2Header>) -> ::Result<Headers> { | ||||
|     // Adapt the header name from `Vec<u8>` to `String`, without making any copies. | ||||
|     let mut headers = Vec::new(); | ||||
|     for (name, value) in http2_headers.into_iter() { | ||||
|         let name = match String::from_utf8(name) { | ||||
|             Ok(name) => name, | ||||
|             Err(_) => return Err(From::from(Http2Error::MalformedResponse)), | ||||
|         }; | ||||
|         headers.push((name, value)); | ||||
|     } | ||||
|  | ||||
|     let mut raw_headers = Vec::new(); | ||||
|     for &(ref name, ref value) in headers.iter() { | ||||
|         raw_headers.push(httparse::Header { name: &name, value: &value }); | ||||
|     } | ||||
|  | ||||
|     Headers::from_raw(&raw_headers) | ||||
| } | ||||
|  | ||||
| /// Parses the response, as returned by `solicit`, into a `ResponseHead` and the full response | ||||
| /// body. | ||||
| /// | ||||
| /// Returns them as a two-tuple. | ||||
| fn parse_response(response: ::solicit::http::Response) -> ::Result<(ResponseHead, Vec<u8>)> { | ||||
|     let status = try!(response.status_code()); | ||||
|     let headers = try!(parse_headers(response.headers)); | ||||
|     Ok((ResponseHead { | ||||
|         headers: headers, | ||||
|         raw_status: RawStatus(status, "".into()), | ||||
|         version: version::HttpVersion::Http20, | ||||
|     }, response.body)) | ||||
| } | ||||
|  | ||||
| impl<S> HttpMessage for Http2Message<S> where S: CloneableStream { | ||||
|     fn set_outgoing(&mut self, head: RequestHead) -> ::Result<RequestHead> { | ||||
|         match self.state { | ||||
|             MessageState::Writing(_) | MessageState::Reading(_) => { | ||||
|                 return Err(From::from(Http2Error::from( | ||||
|                             io::Error::new(io::ErrorKind::Other, | ||||
|                                            "An outoging has already been set")))); | ||||
|             }, | ||||
|             MessageState::Idle => {}, | ||||
|         }; | ||||
|         self.state = MessageState::Writing(Http2Request { | ||||
|             head: head.clone(), | ||||
|             body: Vec::new(), | ||||
|         }); | ||||
|  | ||||
|         Ok(head) | ||||
|     } | ||||
|  | ||||
|     fn get_incoming(&mut self) -> ::Result<ResponseHead> { | ||||
|         // Prepare the request so that it can be passed off to the HTTP/2 client. | ||||
|         let request = match self.state.take_request() { | ||||
|             None => { | ||||
|                 return Err(From::from(Http2Error::from( | ||||
|                             io::Error::new(io::ErrorKind::Other, | ||||
|                                            "No request in progress")))); | ||||
|             }, | ||||
|             Some(req) => req, | ||||
|         }; | ||||
|         let (RequestHead { headers, method, url }, body) = (request.head, request.body); | ||||
|  | ||||
|         let method = method.as_ref().as_bytes(); | ||||
|         let path = prepare_path(url); | ||||
|         let extra_headers = prepare_headers(headers); | ||||
|         let body = prepare_body(body); | ||||
|  | ||||
|         // Finally, everything is ready and we issue the request. | ||||
|         let stream_id = try!(self.client.request(method, &path, &extra_headers, body)); | ||||
|  | ||||
|         // Wait for the response | ||||
|         let resp = try!(self.client.get_response(stream_id)); | ||||
|  | ||||
|         // Now that the response is back, adapt it to the structs that hyper expects/provides. | ||||
|         let (head, body) = try!(parse_response(resp)); | ||||
|  | ||||
|         // For now, since `solicit` has already read the full response, we just wrap the body into | ||||
|         // a `Cursor` to allow for the public interface to support `io::Read`. | ||||
|         let body = Cursor::new(body); | ||||
|  | ||||
|         // The body is saved so that it can be read out from the message. | ||||
|         self.state = MessageState::Reading(Http2Response { | ||||
|             body: body, | ||||
|         }); | ||||
|  | ||||
|         Ok(head) | ||||
|     } | ||||
|  | ||||
|     fn close_connection(&mut self) -> ::Result<()> { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A convenience method that creates a default `Http2Protocol` that uses a `net::HttpConnector` | ||||
| /// (which produces an `HttpStream` for the underlying transport layer). | ||||
| #[inline] | ||||
| pub fn new_protocol() -> Http2Protocol<HttpConnector, HttpStream> { | ||||
|     Http2Protocol::with_connector(HttpConnector(None)) | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::{Http2Protocol, prepare_headers, parse_headers, parse_response}; | ||||
|  | ||||
|     use std::io::{Read}; | ||||
|  | ||||
|     use mock::{MockHttp2Connector, MockStream}; | ||||
|     use http::{RequestHead, ResponseHead, Protocol}; | ||||
|  | ||||
|     use header::Headers; | ||||
|     use header; | ||||
|     use url::Url; | ||||
|     use method; | ||||
|     use cookie; | ||||
|     use version; | ||||
|  | ||||
|     use solicit::http::connection::{HttpFrame, ReceiveFrame}; | ||||
|  | ||||
|     /// Tests that the `Http2Message` correctly reads a response with no body. | ||||
|     #[test] | ||||
|     fn test_http2_response_no_body() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         mock_connector.new_response_stream(b"200", &Headers::new(), None); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|         message.set_outgoing(RequestHead { | ||||
|             headers: Headers::new(), | ||||
|             method: method::Method::Get, | ||||
|             url: Url::parse("http://127.0.0.1/hello").unwrap(), | ||||
|         }).unwrap(); | ||||
|         let resp = message.get_incoming().unwrap(); | ||||
|  | ||||
|         assert_eq!(resp.raw_status.0, 200); | ||||
|         let mut body = Vec::new(); | ||||
|         message.read_to_end(&mut body).unwrap(); | ||||
|         assert_eq!(body.len(), 0); | ||||
|     } | ||||
|  | ||||
|     /// Tests that the `Http2Message` correctly reads a response with a body. | ||||
|     #[test] | ||||
|     fn test_http2_response_with_body() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         mock_connector.new_response_stream(b"200", &Headers::new(), Some(vec![1, 2, 3])); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|         message.set_outgoing(RequestHead { | ||||
|             headers: Headers::new(), | ||||
|             method: method::Method::Get, | ||||
|             url: Url::parse("http://127.0.0.1/hello").unwrap(), | ||||
|         }).unwrap(); | ||||
|         let resp = message.get_incoming().unwrap(); | ||||
|  | ||||
|         assert_eq!(resp.raw_status.0, 200); | ||||
|         let mut body = Vec::new(); | ||||
|         message.read_to_end(&mut body).unwrap(); | ||||
|         assert_eq!(vec![1, 2, 3], body); | ||||
|     } | ||||
|  | ||||
|     /// Tests that the `Http2Message` correctly reads a response with an empty body. | ||||
|     #[test] | ||||
|     fn test_http2_response_empty_body() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         mock_connector.new_response_stream(b"200", &Headers::new(), Some(vec![])); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|         message.set_outgoing(RequestHead { | ||||
|             headers: Headers::new(), | ||||
|             method: method::Method::Get, | ||||
|             url: Url::parse("http://127.0.0.1/hello").unwrap(), | ||||
|         }).unwrap(); | ||||
|         let resp = message.get_incoming().unwrap(); | ||||
|  | ||||
|         assert_eq!(resp.raw_status.0, 200); | ||||
|         let mut body = Vec::new(); | ||||
|         message.read_to_end(&mut body).unwrap(); | ||||
|         assert_eq!(Vec::<u8>::new(), body); | ||||
|     } | ||||
|  | ||||
|     /// Tests that the `Http2Message` correctly parses out the headers into the `ResponseHead`. | ||||
|     #[test] | ||||
|     fn test_http2_response_headers() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         let mut headers = Headers::new(); | ||||
|         headers.set(header::ContentLength(3)); | ||||
|         headers.set(header::ETag(header::EntityTag::new(true, "tag".into()))); | ||||
|         mock_connector.new_response_stream(b"200", &headers, Some(vec![1, 2, 3])); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|         message.set_outgoing(RequestHead { | ||||
|             headers: Headers::new(), | ||||
|             method: method::Method::Get, | ||||
|             url: Url::parse("http://127.0.0.1/hello").unwrap(), | ||||
|         }).unwrap(); | ||||
|         let resp = message.get_incoming().unwrap(); | ||||
|  | ||||
|         assert_eq!(resp.raw_status.0, 200); | ||||
|         assert!(resp.headers.has::<header::ContentLength>()); | ||||
|         let &header::ContentLength(len) = resp.headers.get::<header::ContentLength>().unwrap(); | ||||
|         assert_eq!(3, len); | ||||
|         assert!(resp.headers.has::<header::ETag>()); | ||||
|         let &header::ETag(ref tag) = resp.headers.get::<header::ETag>().unwrap(); | ||||
|         assert_eq!(tag.tag(), "tag"); | ||||
|     } | ||||
|  | ||||
|     /// Tests that an error is returned when the `Http2Message` is not in a readable state. | ||||
|     #[test] | ||||
|     fn test_http2_message_not_readable() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         mock_connector.new_response_stream(b"200", &Headers::new(), None); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|  | ||||
|         // No outgoing set yet, so nothing can be read at this point. | ||||
|         assert!(message.read(&mut [0; 5]).is_err()); | ||||
|     } | ||||
|  | ||||
|     /// Tests that an error is returned when the `Http2Message` is not in a writable state. | ||||
|     #[test] | ||||
|     fn test_http2_message_not_writable() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         mock_connector.new_response_stream(b"200", &Headers::new(), None); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|         message.set_outgoing(RequestHead { | ||||
|             headers: Headers::new(), | ||||
|             method: method::Method::Get, | ||||
|             url: Url::parse("http://127.0.0.1/hello").unwrap(), | ||||
|         }).unwrap(); | ||||
|         let _ = message.get_incoming().unwrap(); | ||||
|         // Writes are invalid now | ||||
|         assert!(message.write(&[1]).is_err()); | ||||
|     } | ||||
|  | ||||
|     /// Asserts that the given stream contains the full expected client preface: the preface bytes, | ||||
|     /// settings frame, and settings ack frame. | ||||
|     fn assert_client_preface(server_stream: &mut MockStream) { | ||||
|         // Skip client preface | ||||
|         server_stream.read(&mut [0; 24]).unwrap(); | ||||
|         // The first frame are the settings | ||||
|         assert!(match server_stream.recv_frame().unwrap() { | ||||
|             HttpFrame::SettingsFrame(_) => true, | ||||
|             _ => false, | ||||
|         }); | ||||
|         // Now the ACK to the server's settings. | ||||
|         assert!(match server_stream.recv_frame().unwrap() { | ||||
|             HttpFrame::SettingsFrame(_) => true, | ||||
|             _ => false, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /// Tests that sending a request with no body works correctly. | ||||
|     #[test] | ||||
|     fn test_http2_request_no_body() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         let stream = mock_connector.new_response_stream(b"200", &Headers::new(), Some(vec![])); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|         message.set_outgoing(RequestHead { | ||||
|             headers: Headers::new(), | ||||
|             method: method::Method::Get, | ||||
|             url: Url::parse("http://127.0.0.1/hello").unwrap(), | ||||
|         }).unwrap(); | ||||
|         let _ = message.get_incoming().unwrap(); | ||||
|  | ||||
|         let stream = stream.inner.lock().unwrap(); | ||||
|         assert!(stream.write.len() > 0); | ||||
|         // The output stream of the client side gets flipped so that we can read the stream from | ||||
|         // the server's end. | ||||
|         let mut server_stream = MockStream::with_input(&stream.write); | ||||
|         assert_client_preface(&mut server_stream); | ||||
|         let frame = server_stream.recv_frame().unwrap(); | ||||
|         assert!(match frame { | ||||
|             HttpFrame::HeadersFrame(ref frame) => frame.is_end_of_stream(), | ||||
|             _ => false, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /// Tests that sending a request with a body works correctly. | ||||
|     #[test] | ||||
|     fn test_http2_request_with_body() { | ||||
|         let mut mock_connector = MockHttp2Connector::new(); | ||||
|         let stream = mock_connector.new_response_stream(b"200", &Headers::new(), None); | ||||
|         let protocol = Http2Protocol::with_connector(mock_connector); | ||||
|  | ||||
|         let mut message = protocol.new_message("127.0.0.1", 1337, "http").unwrap(); | ||||
|         message.set_outgoing(RequestHead { | ||||
|             headers: Headers::new(), | ||||
|             method: method::Method::Get, | ||||
|             url: Url::parse("http://127.0.0.1/hello").unwrap(), | ||||
|         }).unwrap(); | ||||
|         // Write a few things to the request in multiple writes. | ||||
|         message.write(&[1]).unwrap(); | ||||
|         message.write(&[2, 3]).unwrap(); | ||||
|         let _ = message.get_incoming().unwrap(); | ||||
|  | ||||
|         let stream = stream.inner.lock().unwrap(); | ||||
|         assert!(stream.write.len() > 0); | ||||
|         // The output stream of the client side gets flipped so that we can read the stream from | ||||
|         // the server's end. | ||||
|         let mut server_stream = MockStream::with_input(&stream.write); | ||||
|         assert_client_preface(&mut server_stream); | ||||
|         let frame = server_stream.recv_frame().unwrap(); | ||||
|         assert!(match frame { | ||||
|             HttpFrame::HeadersFrame(ref frame) => !frame.is_end_of_stream(), | ||||
|             _ => false, | ||||
|         }); | ||||
|         assert!(match server_stream.recv_frame().unwrap() { | ||||
|             HttpFrame::DataFrame(ref frame) => frame.data == vec![1, 2, 3], | ||||
|             _ => false, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /// Tests that headers are correctly prepared when they include a `Set-Cookie` header. | ||||
|     #[test] | ||||
|     fn test_http2_prepare_headers_with_set_cookie() { | ||||
|         let cookies = header::SetCookie(vec![ | ||||
|             cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), | ||||
|             cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) | ||||
|         ]); | ||||
|         let mut headers = Headers::new(); | ||||
|         headers.set(cookies); | ||||
|  | ||||
|         let h2headers = prepare_headers(headers); | ||||
|  | ||||
|         assert_eq!(vec![ | ||||
|             (b"set-cookie".to_vec(), b"foo=bar; Path=/".to_vec()), | ||||
|             (b"set-cookie".to_vec(), b"baz=quux; Path=/".to_vec()), | ||||
|         ], h2headers); | ||||
|     } | ||||
|  | ||||
|     /// Tests that headers are correctly prepared when they include a `Cookie` header. | ||||
|     #[test] | ||||
|     fn test_http2_prepapre_headers_with_cookie() { | ||||
|         let cookies = header::Cookie(vec![ | ||||
|             cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), | ||||
|             cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) | ||||
|         ]); | ||||
|         let mut headers = Headers::new(); | ||||
|         headers.set(cookies); | ||||
|  | ||||
|         let h2headers = prepare_headers(headers); | ||||
|  | ||||
|         assert_eq!(vec![ | ||||
|             (b"cookie".to_vec(), b"foo=bar; baz=quux".to_vec()), | ||||
|         ], h2headers); | ||||
|     } | ||||
|  | ||||
|     /// Tests that HTTP/2 headers are correctly prepared. | ||||
|     #[test] | ||||
|     fn test_http2_prepare_headers() { | ||||
|         let mut headers = Headers::new(); | ||||
|         headers.set(header::ContentLength(3)); | ||||
|         let expected = vec![ | ||||
|             (b"content-length".to_vec(), b"3".to_vec()), | ||||
|         ]; | ||||
|  | ||||
|         assert_eq!(expected, prepare_headers(headers)); | ||||
|     } | ||||
|  | ||||
|     /// Tests that the headers of a response are correctly parsed when they include a `Set-Cookie` | ||||
|     /// header. | ||||
|     #[test] | ||||
|     fn test_http2_parse_headers_with_set_cookie() { | ||||
|         let h2headers = vec![ | ||||
|             (b"set-cookie".to_vec(), b"foo=bar; Path=/".to_vec()), | ||||
|             (b"set-cookie".to_vec(), b"baz=quux; Path=/".to_vec()), | ||||
|         ]; | ||||
|         let expected = header::SetCookie(vec![ | ||||
|             cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), | ||||
|             cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) | ||||
|         ]); | ||||
|  | ||||
|         let headers = parse_headers(h2headers).unwrap(); | ||||
|  | ||||
|         assert!(headers.has::<header::SetCookie>()); | ||||
|         let set_cookie = headers.get::<header::SetCookie>().unwrap(); | ||||
|         assert_eq!(expected, *set_cookie); | ||||
|     } | ||||
|  | ||||
|     /// Tests that parsing HTTP/2 headers with `Cookie` headers works correctly. | ||||
|     #[test] | ||||
|     fn test_http2_parse_headers_with_cookie() { | ||||
|         let expected = header::Cookie(vec![ | ||||
|             cookie::Cookie::new("foo".to_owned(), "bar".to_owned()), | ||||
|             cookie::Cookie::new("baz".to_owned(), "quux".to_owned()) | ||||
|         ]); | ||||
|         // HTTP/2 allows the `Cookie` header to be split into multiple ones to facilitate better | ||||
|         // compression. | ||||
|         let h2headers = vec![ | ||||
|             (b"cookie".to_vec(), b"foo=bar".to_vec()), | ||||
|             (b"cookie".to_vec(), b"baz=quux".to_vec()), | ||||
|         ]; | ||||
|  | ||||
|         let headers = parse_headers(h2headers).unwrap(); | ||||
|  | ||||
|         assert!(headers.has::<header::Cookie>()); | ||||
|         assert_eq!(*headers.get::<header::Cookie>().unwrap(), expected); | ||||
|     } | ||||
|  | ||||
|     /// Tests that the headers of a response are correctly parsed. | ||||
|     #[test] | ||||
|     fn test_http2_parse_headers() { | ||||
|         let h2headers = vec![ | ||||
|             (b":status".to_vec(), b"200".to_vec()), | ||||
|             (b"content-length".to_vec(), b"3".to_vec()), | ||||
|         ]; | ||||
|  | ||||
|         let headers = parse_headers(h2headers).unwrap(); | ||||
|  | ||||
|         assert!(headers.has::<header::ContentLength>()); | ||||
|         let &header::ContentLength(len) = headers.get::<header::ContentLength>().unwrap(); | ||||
|         assert_eq!(3, len); | ||||
|     } | ||||
|  | ||||
|     /// Tests that if a header name is not a valid utf8 byte sequence, an error is returned. | ||||
|     #[test] | ||||
|     fn test_http2_parse_headers_invalid_name() { | ||||
|         let h2headers = vec![ | ||||
|             (vec![0xfe], vec![]), | ||||
|         ]; | ||||
|  | ||||
|         assert!(parse_headers(h2headers).is_err()); | ||||
|     } | ||||
|  | ||||
|     /// Tests that a response with no pseudo-header for status is considered invalid. | ||||
|     #[test] | ||||
|     fn test_http2_parse_response_no_status_code() { | ||||
|         let response = ::solicit::http::Response { | ||||
|             body: Vec::new(), | ||||
|             headers: vec![ | ||||
|                 (b"content-length".to_vec(), b"3".to_vec()), | ||||
|             ], | ||||
|             stream_id: 1, | ||||
|         }; | ||||
|  | ||||
|         assert!(parse_response(response).is_err()); | ||||
|     } | ||||
|  | ||||
|     /// Tests that an HTTP/2 response gets correctly parsed into a body and response head, when | ||||
|     /// the body is empty. | ||||
|     #[test] | ||||
|     fn test_http2_parse_response_no_body() { | ||||
|         let response = ::solicit::http::Response { | ||||
|             body: Vec::new(), | ||||
|             headers: vec![ | ||||
|                 (b":status".to_vec(), b"200".to_vec()), | ||||
|                 (b"content-length".to_vec(), b"0".to_vec()), | ||||
|             ], | ||||
|             stream_id: 1, | ||||
|         }; | ||||
|  | ||||
|         let (head, body) = parse_response(response).unwrap(); | ||||
|  | ||||
|         assert_eq!(body, vec![]); | ||||
|         let ResponseHead { headers, raw_status, version } = head; | ||||
|         assert_eq!(raw_status.0, 200); | ||||
|         assert_eq!(raw_status.1, ""); | ||||
|         assert!(headers.has::<header::ContentLength>()); | ||||
|         assert_eq!(version, version::HttpVersion::Http20); | ||||
|     } | ||||
|  | ||||
|     /// Tests that an HTTP/2 response gets correctly parsed into a body and response head, when | ||||
|     /// the body is not empty. | ||||
|     #[test] | ||||
|     fn test_http2_parse_response_with_body() { | ||||
|         let expected_body = vec![1, 2, 3]; | ||||
|         let response = ::solicit::http::Response { | ||||
|             body: expected_body.clone(), | ||||
|             headers: vec![ | ||||
|                 (b":status".to_vec(), b"200".to_vec()), | ||||
|                 (b"content-length".to_vec(), b"3".to_vec()), | ||||
|             ], | ||||
|             stream_id: 1, | ||||
|         }; | ||||
|  | ||||
|         let (head, body) = parse_response(response).unwrap(); | ||||
|  | ||||
|         assert_eq!(body, expected_body); | ||||
|         let ResponseHead { headers, raw_status, version } = head; | ||||
|         assert_eq!(raw_status.0, 200); | ||||
|         assert_eq!(raw_status.1, ""); | ||||
|         assert!(headers.has::<header::ContentLength>()); | ||||
|         assert_eq!(version, version::HttpVersion::Http20); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										124
									
								
								src/http/message.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/http/message.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| //! Defines the `HttpMessage` trait that serves to encapsulate the operations of a single | ||||
| //! request-response cycle on any HTTP connection. | ||||
|  | ||||
| use std::fmt::Debug; | ||||
| use std::any::{Any, TypeId}; | ||||
| use std::io::{Read, Write}; | ||||
|  | ||||
| use std::mem; | ||||
|  | ||||
| use typeable::Typeable; | ||||
|  | ||||
| use header::Headers; | ||||
| use http::RawStatus; | ||||
| use url::Url; | ||||
|  | ||||
| use method; | ||||
| use version; | ||||
| use traitobject; | ||||
| use net::ContextVerifier; | ||||
|  | ||||
| /// The trait provides an API for creating new `HttpMessage`s depending on the underlying HTTP | ||||
| /// protocol. | ||||
| pub trait Protocol { | ||||
|     /// Creates a fresh `HttpMessage` bound to the given host, based on the given protocol scheme. | ||||
|     fn new_message(&self, host: &str, port: u16, scheme: &str) -> ::Result<Box<HttpMessage>>; | ||||
|     /// Sets the SSL verifier that should be used when establishing TLS-protected connections. | ||||
|     fn set_ssl_verifier(&mut self, verifier: ContextVerifier); | ||||
| } | ||||
|  | ||||
| /// Describes a request. | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct RequestHead { | ||||
|     /// The headers of the request | ||||
|     pub headers: Headers, | ||||
|     /// The method of the request | ||||
|     pub method: method::Method, | ||||
|     /// The URL of the request | ||||
|     pub url: Url, | ||||
| } | ||||
|  | ||||
| /// Describes a response. | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct ResponseHead { | ||||
|     /// The headers of the reponse | ||||
|     pub headers: Headers, | ||||
|     /// The raw status line of the response | ||||
|     pub raw_status: RawStatus, | ||||
|     /// The HTTP/2 version which generated the response | ||||
|     pub version: version::HttpVersion, | ||||
| } | ||||
|  | ||||
| /// The trait provides an API for sending an receiving HTTP messages. | ||||
| pub trait HttpMessage: Write + Read + Send + Any + Typeable + Debug { | ||||
|     /// Initiates a new outgoing request. | ||||
|     /// | ||||
|     /// Only the request's head is provided (in terms of the `RequestHead` struct). | ||||
|     /// | ||||
|     /// After this, the `HttpMessage` instance can be used as an `io::Write` in order to write the | ||||
|     /// body of the request. | ||||
|     fn set_outgoing(&mut self, head: RequestHead) -> ::Result<RequestHead>; | ||||
|     /// Obtains the incoming response and returns its head (i.e. the `ResponseHead` struct) | ||||
|     /// | ||||
|     /// After this, the `HttpMessage` instance can be used as an `io::Read` in order to read out | ||||
|     /// the response body. | ||||
|     fn get_incoming(&mut self) -> ::Result<ResponseHead>; | ||||
|  | ||||
|     /// Closes the underlying HTTP connection. | ||||
|     fn close_connection(&mut self) -> ::Result<()>; | ||||
| } | ||||
|  | ||||
| impl HttpMessage { | ||||
|     unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T { | ||||
|         mem::transmute(traitobject::data(self)) | ||||
|     } | ||||
|  | ||||
|     unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T { | ||||
|         mem::transmute(traitobject::data_mut(self)) | ||||
|     } | ||||
|  | ||||
|     unsafe fn downcast_unchecked<T: 'static>(self: Box<HttpMessage>) -> Box<T>  { | ||||
|         let raw: *mut HttpMessage = mem::transmute(self); | ||||
|         mem::transmute(traitobject::data_mut(raw)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl HttpMessage { | ||||
|     /// Is the underlying type in this trait object a T? | ||||
|     #[inline] | ||||
|     pub fn is<T: Any>(&self) -> bool { | ||||
|         (*self).get_type() == TypeId::of::<T>() | ||||
|     } | ||||
|  | ||||
|     /// If the underlying type is T, get a reference to the contained data. | ||||
|     #[inline] | ||||
|     pub fn downcast_ref<T: Any>(&self) -> Option<&T> { | ||||
|         if self.is::<T>() { | ||||
|             Some(unsafe { self.downcast_ref_unchecked() }) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// If the underlying type is T, get a mutable reference to the contained | ||||
|     /// data. | ||||
|     #[inline] | ||||
|     pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> { | ||||
|         if self.is::<T>() { | ||||
|             Some(unsafe { self.downcast_mut_unchecked() }) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// If the underlying type is T, extract it. | ||||
|     #[inline] | ||||
|     pub fn downcast<T: Any>(self: Box<HttpMessage>) | ||||
|             -> Result<Box<T>, Box<HttpMessage>> { | ||||
|         if self.is::<T>() { | ||||
|             Ok(unsafe { self.downcast_unchecked() }) | ||||
|         } else { | ||||
|             Err(self) | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								src/http/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/http/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| //! Pieces pertaining to the HTTP message protocol. | ||||
| use std::borrow::Cow; | ||||
|  | ||||
| use header::Connection; | ||||
| use header::ConnectionOption::{KeepAlive, Close}; | ||||
| use header::Headers; | ||||
| use version::HttpVersion; | ||||
| use version::HttpVersion::{Http10, Http11}; | ||||
|  | ||||
| pub use self::message::{HttpMessage, RequestHead, ResponseHead, Protocol}; | ||||
|  | ||||
| pub mod h1; | ||||
| pub mod h2; | ||||
| pub mod message; | ||||
|  | ||||
| /// The raw status code and reason-phrase. | ||||
| #[derive(Clone, PartialEq, Debug)] | ||||
| pub struct RawStatus(pub u16, pub Cow<'static, str>); | ||||
|  | ||||
| /// Checks if a connection should be kept alive. | ||||
| #[inline] | ||||
| pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool { | ||||
|     match (version, headers.get::<Connection>()) { | ||||
|         (Http10, Some(conn)) if !conn.contains(&KeepAlive) => false, | ||||
|         (Http11, Some(conn)) if conn.contains(&Close)  => false, | ||||
|         _ => true | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user