refactor(proto): move more h1-specific pieces into h1 module
This commit is contained in:
		
							
								
								
									
										692
									
								
								src/proto/h1/role.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										692
									
								
								src/proto/h1/role.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,692 @@ | ||||
| use std::borrow::Cow; | ||||
| use std::fmt::{self, Write}; | ||||
|  | ||||
| use httparse; | ||||
| use bytes::{BytesMut, Bytes}; | ||||
|  | ||||
| use header::{self, Headers, ContentLength, TransferEncoding}; | ||||
| use proto::{MessageHead, RawStatus, Http1Transaction, ParseResult, | ||||
|            ServerTransaction, ClientTransaction, RequestLine, RequestHead}; | ||||
| use proto::h1::{Encoder, Decoder, date}; | ||||
| use method::Method; | ||||
| use status::StatusCode; | ||||
| use version::HttpVersion::{self, Http10, Http11}; | ||||
|  | ||||
| const MAX_HEADERS: usize = 100; | ||||
| const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific | ||||
|  | ||||
| impl Http1Transaction for ServerTransaction { | ||||
|     type Incoming = RequestLine; | ||||
|     type Outgoing = StatusCode; | ||||
|  | ||||
|     fn parse(buf: &mut BytesMut) -> ParseResult<RequestLine> { | ||||
|         if buf.len() == 0 { | ||||
|             return Ok(None); | ||||
|         } | ||||
|         let mut headers_indices = [HeaderIndices { | ||||
|             name: (0, 0), | ||||
|             value: (0, 0) | ||||
|         }; MAX_HEADERS]; | ||||
|         let (len, method, path, version, headers_len) = { | ||||
|             let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; | ||||
|             trace!("Request.parse([Header; {}], [u8; {}])", headers.len(), buf.len()); | ||||
|             let mut req = httparse::Request::new(&mut headers); | ||||
|             match try!(req.parse(&buf)) { | ||||
|                 httparse::Status::Complete(len) => { | ||||
|                     trace!("Request.parse Complete({})", len); | ||||
|                     let method = try!(req.method.unwrap().parse()); | ||||
|                     let path = req.path.unwrap(); | ||||
|                     let bytes_ptr = buf.as_ref().as_ptr() as usize; | ||||
|                     let path_start = path.as_ptr() as usize - bytes_ptr; | ||||
|                     let path_end = path_start + path.len(); | ||||
|                     let path = (path_start, path_end); | ||||
|                     let version = if req.version.unwrap() == 1 { Http11 } else { Http10 }; | ||||
|  | ||||
|                     record_header_indices(buf.as_ref(), &req.headers, &mut headers_indices); | ||||
|                     let headers_len = req.headers.len(); | ||||
|                     (len, method, path, version, headers_len) | ||||
|                 } | ||||
|                 httparse::Status::Partial => return Ok(None), | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let mut headers = Headers::with_capacity(headers_len); | ||||
|         let slice = buf.split_to(len).freeze(); | ||||
|         let path = slice.slice(path.0, path.1); | ||||
|         // path was found to be utf8 by httparse | ||||
|         let path = try!(unsafe { ::uri::from_utf8_unchecked(path) }); | ||||
|         let subject = RequestLine( | ||||
|             method, | ||||
|             path, | ||||
|         ); | ||||
|  | ||||
|         headers.extend(HeadersAsBytesIter { | ||||
|             headers: headers_indices[..headers_len].iter(), | ||||
|             slice: slice, | ||||
|         }); | ||||
|  | ||||
|         Ok(Some((MessageHead { | ||||
|             version: version, | ||||
|             subject: subject, | ||||
|             headers: headers, | ||||
|         }, len))) | ||||
|     } | ||||
|  | ||||
|     fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Option<Decoder>> { | ||||
|         use ::header; | ||||
|  | ||||
|         *method = Some(head.subject.0.clone()); | ||||
|  | ||||
|         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||
|         // 1. (irrelevant to Request) | ||||
|         // 2. (irrelevant to Request) | ||||
|         // 3. Transfer-Encoding: chunked has a chunked body. | ||||
|         // 4. If multiple differing Content-Length headers or invalid, close connection. | ||||
|         // 5. Content-Length header has a sized body. | ||||
|         // 6. Length 0. | ||||
|         // 7. (irrelevant to Request) | ||||
|  | ||||
|         if let Some(&header::TransferEncoding(ref encodings)) = head.headers.get() { | ||||
|             // https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||
|             // If Transfer-Encoding header is present, and 'chunked' is | ||||
|             // not the final encoding, and this is a Request, then it is | ||||
|             // mal-formed. A server should respond with 400 Bad Request. | ||||
|             if head.version == Http10 { | ||||
|                 debug!("HTTP/1.0 has Transfer-Encoding header"); | ||||
|                 Err(::Error::Header) | ||||
|             } else if encodings.last() == Some(&header::Encoding::Chunked) { | ||||
|                 Ok(Some(Decoder::chunked())) | ||||
|             } else { | ||||
|                 debug!("request with transfer-encoding header, but not chunked, bad request"); | ||||
|                 Err(::Error::Header) | ||||
|             } | ||||
|         } else if let Some(&header::ContentLength(len)) = head.headers.get() { | ||||
|             Ok(Some(Decoder::length(len))) | ||||
|         } else if head.headers.has::<header::ContentLength>() { | ||||
|             debug!("illegal Content-Length: {:?}", head.headers.get_raw("Content-Length")); | ||||
|             Err(::Error::Header) | ||||
|         } else { | ||||
|             Ok(Some(Decoder::length(0))) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> ::Result<Encoder> { | ||||
|         trace!("ServerTransaction::encode has_body={}, method={:?}", has_body, method); | ||||
|  | ||||
|         // hyper currently doesn't support returning 1xx status codes as a Response | ||||
|         // This is because Service only allows returning a single Response, and | ||||
|         // so if you try to reply with a e.g. 100 Continue, you have no way of | ||||
|         // replying with the latter status code response. | ||||
|         let ret = if head.subject.is_informational() { | ||||
|             error!("response with 1xx status code not supported"); | ||||
|             head = MessageHead::default(); | ||||
|             head.subject = ::StatusCode::InternalServerError; | ||||
|             head.headers.set(ContentLength(0)); | ||||
|             Err(::Error::Status) | ||||
|         } else { | ||||
|             Ok(ServerTransaction::set_length(&mut head, has_body, method.as_ref())) | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; | ||||
|         dst.reserve(init_cap); | ||||
|         if head.version == ::HttpVersion::Http11 && head.subject == ::StatusCode::Ok { | ||||
|             extend(dst, b"HTTP/1.1 200 OK\r\n"); | ||||
|             let _ = write!(FastWrite(dst), "{}", head.headers); | ||||
|         } else { | ||||
|             let _ = write!(FastWrite(dst), "{} {}\r\n{}", head.version, head.subject, head.headers); | ||||
|         } | ||||
|         // using http::h1::date is quite a lot faster than generating a unique Date header each time | ||||
|         // like req/s goes up about 10% | ||||
|         if !head.headers.has::<header::Date>() { | ||||
|             dst.reserve(date::DATE_VALUE_LENGTH + 8); | ||||
|             extend(dst, b"Date: "); | ||||
|             date::extend(dst); | ||||
|             extend(dst, b"\r\n"); | ||||
|         } | ||||
|         extend(dst, b"\r\n"); | ||||
|  | ||||
|         ret | ||||
|     } | ||||
|  | ||||
|     fn on_error(err: &::Error) -> Option<MessageHead<Self::Outgoing>> { | ||||
|         let status = match err { | ||||
|             &::Error::Method | | ||||
|             &::Error::Version | | ||||
|             &::Error::Header | | ||||
|             &::Error::Uri(_) => { | ||||
|                 StatusCode::BadRequest | ||||
|             }, | ||||
|             &::Error::TooLarge => { | ||||
|                 StatusCode::RequestHeaderFieldsTooLarge | ||||
|             } | ||||
|             _ => return None, | ||||
|         }; | ||||
|  | ||||
|         debug!("sending automatic response ({}) for parse error", status); | ||||
|         let mut msg = MessageHead::default(); | ||||
|         msg.subject = status; | ||||
|         Some(msg) | ||||
|     } | ||||
|  | ||||
|     fn should_error_on_parse_eof() -> bool { | ||||
|         false | ||||
|     } | ||||
|  | ||||
|     fn should_read_first() -> bool { | ||||
|         true | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ServerTransaction { | ||||
|     fn set_length(head: &mut MessageHead<StatusCode>, has_body: bool, method: Option<&Method>) -> Encoder { | ||||
|         // these are here thanks to borrowck | ||||
|         // `if method == Some(&Method::Get)` says the RHS doesn't live long enough | ||||
|         const HEAD: Option<&'static Method> = Some(&Method::Head); | ||||
|         const CONNECT: Option<&'static Method> = Some(&Method::Connect); | ||||
|  | ||||
|         let can_have_body = { | ||||
|             if method == HEAD { | ||||
|                 false | ||||
|             } else if method == CONNECT && head.subject.is_success() { | ||||
|                 false | ||||
|             } else { | ||||
|                 match head.subject { | ||||
|                     // TODO: support for 1xx codes needs improvement everywhere | ||||
|                     // would be 100...199 => false | ||||
|                     StatusCode::NoContent | | ||||
|                     StatusCode::NotModified => false, | ||||
|                     _ => true, | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         if has_body && can_have_body { | ||||
|             set_length(head.version, &mut head.headers) | ||||
|         } else { | ||||
|             head.headers.remove::<TransferEncoding>(); | ||||
|             if can_have_body { | ||||
|                 head.headers.set(ContentLength(0)); | ||||
|             } | ||||
|             Encoder::length(0) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Http1Transaction for ClientTransaction { | ||||
|     type Incoming = RawStatus; | ||||
|     type Outgoing = RequestLine; | ||||
|  | ||||
|     fn parse(buf: &mut BytesMut) -> ParseResult<RawStatus> { | ||||
|         if buf.len() == 0 { | ||||
|             return Ok(None); | ||||
|         } | ||||
|         let mut headers_indices = [HeaderIndices { | ||||
|             name: (0, 0), | ||||
|             value: (0, 0) | ||||
|         }; MAX_HEADERS]; | ||||
|         let (len, code, reason, version, headers_len) = { | ||||
|             let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; | ||||
|             trace!("Response.parse([Header; {}], [u8; {}])", headers.len(), buf.len()); | ||||
|             let mut res = httparse::Response::new(&mut headers); | ||||
|             let bytes = buf.as_ref(); | ||||
|             match try!(res.parse(bytes)) { | ||||
|                 httparse::Status::Complete(len) => { | ||||
|                     trace!("Response.parse Complete({})", len); | ||||
|                     let code = res.code.unwrap(); | ||||
|                     let status = try!(StatusCode::try_from(code).map_err(|_| ::Error::Status)); | ||||
|                     let reason = match status.canonical_reason() { | ||||
|                         Some(reason) if reason == res.reason.unwrap() => Cow::Borrowed(reason), | ||||
|                         _ => Cow::Owned(res.reason.unwrap().to_owned()) | ||||
|                     }; | ||||
|                     let version = if res.version.unwrap() == 1 { Http11 } else { Http10 }; | ||||
|                     record_header_indices(bytes, &res.headers, &mut headers_indices); | ||||
|                     let headers_len = res.headers.len(); | ||||
|                     (len, code, reason, version, headers_len) | ||||
|                 }, | ||||
|                 httparse::Status::Partial => return Ok(None), | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let mut headers = Headers::with_capacity(headers_len); | ||||
|         let slice = buf.split_to(len).freeze(); | ||||
|         headers.extend(HeadersAsBytesIter { | ||||
|             headers: headers_indices[..headers_len].iter(), | ||||
|             slice: slice, | ||||
|         }); | ||||
|         Ok(Some((MessageHead { | ||||
|             version: version, | ||||
|             subject: RawStatus(code, reason), | ||||
|             headers: headers, | ||||
|         }, len))) | ||||
|     } | ||||
|  | ||||
|     fn decoder(inc: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Option<Decoder>> { | ||||
|         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||
|         // 1. HEAD responses, and Status 1xx, 204, and 304 cannot have a body. | ||||
|         // 2. Status 2xx to a CONNECT cannot have a body. | ||||
|         // 3. Transfer-Encoding: chunked has a chunked body. | ||||
|         // 4. If multiple differing Content-Length headers or invalid, close connection. | ||||
|         // 5. Content-Length header has a sized body. | ||||
|         // 6. (irrelevant to Response) | ||||
|         // 7. Read till EOF. | ||||
|  | ||||
|         match inc.subject.0 { | ||||
|             101 => { | ||||
|                 debug!("received 101 upgrade response, not supported"); | ||||
|                 return Err(::Error::Upgrade); | ||||
|             }, | ||||
|             100...199 => { | ||||
|                 trace!("ignoring informational response: {}", inc.subject.0); | ||||
|                 return Ok(None); | ||||
|             }, | ||||
|             204 | | ||||
|             304 => return Ok(Some(Decoder::length(0))), | ||||
|             _ => (), | ||||
|         } | ||||
|         match *method { | ||||
|             Some(Method::Head) => { | ||||
|                 return Ok(Some(Decoder::length(0))); | ||||
|             } | ||||
|             Some(Method::Connect) => match inc.subject.0 { | ||||
|                 200...299 => { | ||||
|                     return Ok(Some(Decoder::length(0))); | ||||
|                 }, | ||||
|                 _ => {}, | ||||
|             }, | ||||
|             Some(_) => {}, | ||||
|             None => { | ||||
|                 trace!("ClientTransaction::decoder is missing the Method"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         if let Some(&header::TransferEncoding(ref codings)) = inc.headers.get() { | ||||
|             if inc.version == Http10 { | ||||
|                 debug!("HTTP/1.0 has Transfer-Encoding header"); | ||||
|                 Err(::Error::Header) | ||||
|             } else if codings.last() == Some(&header::Encoding::Chunked) { | ||||
|                 Ok(Some(Decoder::chunked())) | ||||
|             } else { | ||||
|                 trace!("not chunked. read till eof"); | ||||
|                 Ok(Some(Decoder::eof())) | ||||
|             } | ||||
|         } else if let Some(&header::ContentLength(len)) = inc.headers.get() { | ||||
|             Ok(Some(Decoder::length(len))) | ||||
|         } else if inc.headers.has::<header::ContentLength>() { | ||||
|             debug!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length")); | ||||
|             Err(::Error::Header) | ||||
|         } else { | ||||
|             trace!("neither Transfer-Encoding nor Content-Length"); | ||||
|             Ok(Some(Decoder::eof())) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> ::Result<Encoder> { | ||||
|         trace!("ClientTransaction::encode has_body={}, method={:?}", has_body, method); | ||||
|  | ||||
|         *method = Some(head.subject.0.clone()); | ||||
|  | ||||
|         let body = ClientTransaction::set_length(&mut head, has_body); | ||||
|  | ||||
|         let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; | ||||
|         dst.reserve(init_cap); | ||||
|         let _ = write!(FastWrite(dst), "{} {}\r\n{}\r\n", head.subject, head.version, head.headers); | ||||
|  | ||||
|         Ok(body) | ||||
|     } | ||||
|  | ||||
|     fn on_error(_err: &::Error) -> Option<MessageHead<Self::Outgoing>> { | ||||
|         // we can't tell the server about any errors it creates | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     fn should_error_on_parse_eof() -> bool { | ||||
|         true | ||||
|     } | ||||
|  | ||||
|     fn should_read_first() -> bool { | ||||
|         false | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ClientTransaction { | ||||
|     fn set_length(head: &mut RequestHead, has_body: bool) -> Encoder { | ||||
|         if has_body { | ||||
|             set_length(head.version, &mut head.headers) | ||||
|         } else { | ||||
|             head.headers.remove::<ContentLength>(); | ||||
|             head.headers.remove::<TransferEncoding>(); | ||||
|             Encoder::length(0) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn set_length(version: HttpVersion, headers: &mut Headers) -> Encoder { | ||||
|     let len = headers.get::<header::ContentLength>().map(|n| **n); | ||||
|  | ||||
|     if let Some(len) = len { | ||||
|         Encoder::length(len) | ||||
|     } else if version == Http11 { | ||||
|         let encodings = match headers.get_mut::<header::TransferEncoding>() { | ||||
|             Some(&mut header::TransferEncoding(ref mut encodings)) => { | ||||
|                 if encodings.last() != Some(&header::Encoding::Chunked) { | ||||
|                     encodings.push(header::Encoding::Chunked); | ||||
|                 } | ||||
|                 false | ||||
|             }, | ||||
|             None => true | ||||
|         }; | ||||
|  | ||||
|         if encodings { | ||||
|             headers.set(header::TransferEncoding(vec![header::Encoding::Chunked])); | ||||
|         } | ||||
|         Encoder::chunked() | ||||
|     } else { | ||||
|         headers.remove::<TransferEncoding>(); | ||||
|         Encoder::eof() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| struct HeaderIndices { | ||||
|     name: (usize, usize), | ||||
|     value: (usize, usize), | ||||
| } | ||||
|  | ||||
| fn record_header_indices(bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndices]) { | ||||
|     let bytes_ptr = bytes.as_ptr() as usize; | ||||
|     for (header, indices) in headers.iter().zip(indices.iter_mut()) { | ||||
|         let name_start = header.name.as_ptr() as usize - bytes_ptr; | ||||
|         let name_end = name_start + header.name.len(); | ||||
|         indices.name = (name_start, name_end); | ||||
|         let value_start = header.value.as_ptr() as usize - bytes_ptr; | ||||
|         let value_end = value_start + header.value.len(); | ||||
|         indices.value = (value_start, value_end); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct HeadersAsBytesIter<'a> { | ||||
|     headers: ::std::slice::Iter<'a, HeaderIndices>, | ||||
|     slice: Bytes, | ||||
| } | ||||
|  | ||||
| impl<'a> Iterator for HeadersAsBytesIter<'a> { | ||||
|     type Item = (&'a str, Bytes); | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         self.headers.next().map(|header| { | ||||
|             let name = unsafe { | ||||
|                 let bytes = ::std::slice::from_raw_parts( | ||||
|                     self.slice.as_ref().as_ptr().offset(header.name.0 as isize), | ||||
|                     header.name.1 - header.name.0 | ||||
|                 ); | ||||
|                 ::std::str::from_utf8_unchecked(bytes) | ||||
|             }; | ||||
|             (name, self.slice.slice(header.value.0, header.value.1)) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct FastWrite<'a>(&'a mut Vec<u8>); | ||||
|  | ||||
| impl<'a> fmt::Write for FastWrite<'a> { | ||||
|     #[inline] | ||||
|     fn write_str(&mut self, s: &str) -> fmt::Result { | ||||
|         extend(self.0, s.as_bytes()); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { | ||||
|         fmt::write(self, args) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| fn extend(dst: &mut Vec<u8>, data: &[u8]) { | ||||
|     dst.extend_from_slice(data); | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use bytes::BytesMut; | ||||
|  | ||||
|     use proto::{MessageHead, ServerTransaction, ClientTransaction, Http1Transaction}; | ||||
|     use header::{ContentLength, TransferEncoding}; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_request() { | ||||
|         extern crate pretty_env_logger; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         let mut raw = BytesMut::from(b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n".to_vec()); | ||||
|         let expected_len = raw.len(); | ||||
|         let (req, len) = ServerTransaction::parse(&mut raw).unwrap().unwrap(); | ||||
|         assert_eq!(len, expected_len); | ||||
|         assert_eq!(req.subject.0, ::Method::Get); | ||||
|         assert_eq!(req.subject.1, "/echo"); | ||||
|         assert_eq!(req.version, ::HttpVersion::Http11); | ||||
|         assert_eq!(req.headers.len(), 1); | ||||
|         assert_eq!(req.headers.get_raw("Host").map(|raw| &raw[0]), Some(b"hyper.rs".as_ref())); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_response() { | ||||
|         extern crate pretty_env_logger; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         let mut raw = BytesMut::from(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".to_vec()); | ||||
|         let expected_len = raw.len(); | ||||
|         let (req, len) = ClientTransaction::parse(&mut raw).unwrap().unwrap(); | ||||
|         assert_eq!(len, expected_len); | ||||
|         assert_eq!(req.subject.0, 200); | ||||
|         assert_eq!(req.subject.1, "OK"); | ||||
|         assert_eq!(req.version, ::HttpVersion::Http11); | ||||
|         assert_eq!(req.headers.len(), 1); | ||||
|         assert_eq!(req.headers.get_raw("Content-Length").map(|raw| &raw[0]), Some(b"0".as_ref())); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_request_errors() { | ||||
|         let mut raw = BytesMut::from(b"GET htt:p// HTTP/1.1\r\nHost: hyper.rs\r\n\r\n".to_vec()); | ||||
|         ServerTransaction::parse(&mut raw).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_raw_status() { | ||||
|         let mut raw = BytesMut::from(b"HTTP/1.1 200 OK\r\n\r\n".to_vec()); | ||||
|         let (res, _) = ClientTransaction::parse(&mut raw).unwrap().unwrap(); | ||||
|         assert_eq!(res.subject.1, "OK"); | ||||
|  | ||||
|         let mut raw = BytesMut::from(b"HTTP/1.1 200 Howdy\r\n\r\n".to_vec()); | ||||
|         let (res, _) = ClientTransaction::parse(&mut raw).unwrap().unwrap(); | ||||
|         assert_eq!(res.subject.1, "Howdy"); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     #[test] | ||||
|     fn test_decoder_request() { | ||||
|         use super::Decoder; | ||||
|  | ||||
|         let method = &mut None; | ||||
|         let mut head = MessageHead::<::proto::RequestLine>::default(); | ||||
|  | ||||
|         head.subject.0 = ::Method::Get; | ||||
|         assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|         assert_eq!(*method, Some(::Method::Get)); | ||||
|  | ||||
|         head.subject.0 = ::Method::Post; | ||||
|         assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|         assert_eq!(*method, Some(::Method::Post)); | ||||
|  | ||||
|         head.headers.set(TransferEncoding::chunked()); | ||||
|         assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|         // transfer-encoding and content-length = chunked | ||||
|         head.headers.set(ContentLength(10)); | ||||
|         assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.remove::<TransferEncoding>(); | ||||
|         assert_eq!(Decoder::length(10), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); | ||||
|         assert_eq!(Decoder::length(5), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); | ||||
|         ServerTransaction::decoder(&head, method).unwrap_err(); | ||||
|  | ||||
|         head.headers.remove::<ContentLength>(); | ||||
|  | ||||
|         head.headers.set_raw("Transfer-Encoding", "gzip"); | ||||
|         ServerTransaction::decoder(&head, method).unwrap_err(); | ||||
|  | ||||
|  | ||||
|         // http/1.0 | ||||
|         head.version = ::HttpVersion::Http10; | ||||
|         head.headers.clear(); | ||||
|  | ||||
|         // 1.0 requests can only have bodies if content-length is set | ||||
|         assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.set(TransferEncoding::chunked()); | ||||
|         ServerTransaction::decoder(&head, method).unwrap_err(); | ||||
|         head.headers.remove::<TransferEncoding>(); | ||||
|  | ||||
|         head.headers.set(ContentLength(15)); | ||||
|         assert_eq!(Decoder::length(15), ServerTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_decoder_response() { | ||||
|         use super::Decoder; | ||||
|  | ||||
|         let method = &mut Some(::Method::Get); | ||||
|         let mut head = MessageHead::<::proto::RawStatus>::default(); | ||||
|  | ||||
|         head.subject.0 = 204; | ||||
|         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|         head.subject.0 = 304; | ||||
|         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.subject.0 = 200; | ||||
|         assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         *method = Some(::Method::Head); | ||||
|         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         *method = Some(::Method::Connect); | ||||
|         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|  | ||||
|         // CONNECT receiving non 200 can have a body | ||||
|         head.subject.0 = 404; | ||||
|         head.headers.set(ContentLength(10)); | ||||
|         assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|         head.headers.remove::<ContentLength>(); | ||||
|  | ||||
|  | ||||
|         *method = Some(::Method::Get); | ||||
|         head.headers.set(TransferEncoding::chunked()); | ||||
|         assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         // transfer-encoding and content-length = chunked | ||||
|         head.headers.set(ContentLength(10)); | ||||
|         assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.remove::<TransferEncoding>(); | ||||
|         assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); | ||||
|         assert_eq!(Decoder::length(5), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); | ||||
|         ClientTransaction::decoder(&head, method).unwrap_err(); | ||||
|         head.headers.clear(); | ||||
|  | ||||
|         // 1xx status codes | ||||
|         head.subject.0 = 100; | ||||
|         assert!(ClientTransaction::decoder(&head, method).unwrap().is_none()); | ||||
|  | ||||
|         head.subject.0 = 103; | ||||
|         assert!(ClientTransaction::decoder(&head, method).unwrap().is_none()); | ||||
|  | ||||
|         // 101 upgrade not supported yet | ||||
|         head.subject.0 = 101; | ||||
|         ClientTransaction::decoder(&head, method).unwrap_err(); | ||||
|         head.subject.0 = 200; | ||||
|  | ||||
|         // http/1.0 | ||||
|         head.version = ::HttpVersion::Http10; | ||||
|  | ||||
|         assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head, method).unwrap().unwrap()); | ||||
|  | ||||
|         head.headers.set(TransferEncoding::chunked()); | ||||
|         ClientTransaction::decoder(&head, method).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     use test::Bencher; | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     #[bench] | ||||
|     fn bench_parse_incoming(b: &mut Bencher) { | ||||
|         let mut raw = BytesMut::from( | ||||
|             b"GET /super_long_uri/and_whatever?what_should_we_talk_about/\ | ||||
|             I_wonder/Hard_to_write_in_an_uri_after_all/you_have_to_make\ | ||||
|             _up_the_punctuation_yourself/how_fun_is_that?test=foo&test1=\ | ||||
|             foo1&test2=foo2&test3=foo3&test4=foo4 HTTP/1.1\r\nHost: \ | ||||
|             hyper.rs\r\nAccept: a lot of things\r\nAccept-Charset: \ | ||||
|             utf8\r\nAccept-Encoding: *\r\nAccess-Control-Allow-\ | ||||
|             Credentials: None\r\nAccess-Control-Allow-Origin: None\r\n\ | ||||
|             Access-Control-Allow-Methods: None\r\nAccess-Control-Allow-\ | ||||
|             Headers: None\r\nContent-Encoding: utf8\r\nContent-Security-\ | ||||
|             Policy: None\r\nContent-Type: text/html\r\nOrigin: hyper\ | ||||
|             \r\nSec-Websocket-Extensions: It looks super important!\r\n\ | ||||
|             Sec-Websocket-Origin: hyper\r\nSec-Websocket-Version: 4.3\r\ | ||||
|             \nStrict-Transport-Security: None\r\nUser-Agent: hyper\r\n\ | ||||
|             X-Content-Duration: None\r\nX-Content-Security-Policy: None\ | ||||
|             \r\nX-DNSPrefetch-Control: None\r\nX-Frame-Options: \ | ||||
|             Something important obviously\r\nX-Requested-With: Nothing\ | ||||
|             \r\n\r\n".to_vec() | ||||
|         ); | ||||
|         let len = raw.len(); | ||||
|  | ||||
|         b.bytes = len as u64; | ||||
|         b.iter(|| { | ||||
|             ServerTransaction::parse(&mut raw).unwrap(); | ||||
|             restart(&mut raw, len); | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         fn restart(b: &mut BytesMut, len: usize) { | ||||
|             b.reserve(1); | ||||
|             unsafe { | ||||
|                 b.set_len(len); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "nightly")] | ||||
|     #[bench] | ||||
|     fn bench_server_transaction_encode(b: &mut Bencher) { | ||||
|         use header::{Headers, ContentLength, ContentType}; | ||||
|         use ::{StatusCode, HttpVersion}; | ||||
|  | ||||
|         let len = 108; | ||||
|         b.bytes = len as u64; | ||||
|  | ||||
|         let mut head = MessageHead { | ||||
|             subject: StatusCode::Ok, | ||||
|             headers: Headers::new(), | ||||
|             version: HttpVersion::Http11, | ||||
|         }; | ||||
|         head.headers.set(ContentLength(10)); | ||||
|         head.headers.set(ContentType::json()); | ||||
|  | ||||
|         b.iter(|| { | ||||
|             let mut vec = Vec::new(); | ||||
|             ServerTransaction::encode(head.clone(), true, &mut None, &mut vec).unwrap(); | ||||
|             assert_eq!(vec.len(), len); | ||||
|             ::test::black_box(vec); | ||||
|         }) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user