fix(client): error on unsupport 101 responses, ignore other 1xx codes
This commit is contained in:
18
src/error.rs
18
src/error.rs
@@ -16,6 +16,7 @@ use self::Error::{
|
|||||||
Header,
|
Header,
|
||||||
Status,
|
Status,
|
||||||
Timeout,
|
Timeout,
|
||||||
|
Upgrade,
|
||||||
Io,
|
Io,
|
||||||
TooLarge,
|
TooLarge,
|
||||||
Incomplete,
|
Incomplete,
|
||||||
@@ -44,6 +45,8 @@ pub enum Error {
|
|||||||
Status,
|
Status,
|
||||||
/// A timeout occurred waiting for an IO event.
|
/// A timeout occurred waiting for an IO event.
|
||||||
Timeout,
|
Timeout,
|
||||||
|
/// A protocol upgrade was encountered, but not yet supported in hyper.
|
||||||
|
Upgrade,
|
||||||
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
/// Parsing a field as string failed
|
/// Parsing a field as string failed
|
||||||
@@ -76,13 +79,14 @@ impl fmt::Display for Error {
|
|||||||
impl StdError for Error {
|
impl StdError for Error {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
Method => "Invalid Method specified",
|
Method => "invalid Method specified",
|
||||||
Version => "Invalid HTTP version specified",
|
Version => "invalid HTTP version specified",
|
||||||
Header => "Invalid Header provided",
|
Header => "invalid Header provided",
|
||||||
TooLarge => "Message head is too large",
|
TooLarge => "message head is too large",
|
||||||
Status => "Invalid Status provided",
|
Status => "invalid Status provided",
|
||||||
Incomplete => "Message is incomplete",
|
Incomplete => "message is incomplete",
|
||||||
Timeout => "Timeout",
|
Timeout => "timeout",
|
||||||
|
Upgrade => "unsupported protocol upgrade",
|
||||||
Uri(ref e) => e.description(),
|
Uri(ref e) => e.description(),
|
||||||
Io(ref e) => e.description(),
|
Io(ref e) => e.description(),
|
||||||
Utf8(ref e) => e.description(),
|
Utf8(ref e) => e.description(),
|
||||||
|
|||||||
@@ -171,65 +171,71 @@ where I: AsyncRead + AsyncWrite,
|
|||||||
debug_assert!(self.can_read_head());
|
debug_assert!(self.can_read_head());
|
||||||
trace!("Conn::read_head");
|
trace!("Conn::read_head");
|
||||||
|
|
||||||
let (version, head) = match self.io.parse::<T>() {
|
loop {
|
||||||
Ok(Async::Ready(head)) => (head.version, head),
|
let (version, head) = match self.io.parse::<T>() {
|
||||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
Ok(Async::Ready(head)) => (head.version, head),
|
||||||
Err(e) => {
|
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||||
// If we are currently waiting on a message, then an empty
|
Err(e) => {
|
||||||
// message should be reported as an error. If not, it is just
|
// If we are currently waiting on a message, then an empty
|
||||||
// the connection closing gracefully.
|
// message should be reported as an error. If not, it is just
|
||||||
let must_error = self.should_error_on_eof();
|
// the connection closing gracefully.
|
||||||
self.state.close_read();
|
let must_error = self.should_error_on_eof();
|
||||||
self.io.consume_leading_lines();
|
self.state.close_read();
|
||||||
let was_mid_parse = !self.io.read_buf().is_empty();
|
self.io.consume_leading_lines();
|
||||||
return if was_mid_parse || must_error {
|
let was_mid_parse = !self.io.read_buf().is_empty();
|
||||||
debug!("parse error ({}) with {} bytes", e, self.io.read_buf().len());
|
return if was_mid_parse || must_error {
|
||||||
Err(e)
|
debug!("parse error ({}) with {} bytes", e, self.io.read_buf().len());
|
||||||
} else {
|
Err(e)
|
||||||
debug!("read eof");
|
} else {
|
||||||
Ok(Async::Ready(None))
|
debug!("read eof");
|
||||||
};
|
Ok(Async::Ready(None))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.state.version = match version {
|
||||||
|
HttpVersion::Http10 => Version::Http10,
|
||||||
|
HttpVersion::Http11 => Version::Http11,
|
||||||
|
_ => {
|
||||||
|
error!("unimplemented HTTP Version = {:?}", version);
|
||||||
|
self.state.close_read();
|
||||||
|
return Err(::Error::Version);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let decoder = match T::decoder(&head, &mut self.state.method) {
|
||||||
|
Ok(Some(d)) => d,
|
||||||
|
Ok(None) => {
|
||||||
|
// likely a 1xx message that we can ignore
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("decoder error = {:?}", e);
|
||||||
|
self.state.close_read();
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("incoming body is {}", decoder);
|
||||||
|
|
||||||
|
self.state.busy();
|
||||||
|
if head.expecting_continue() {
|
||||||
|
let msg = b"HTTP/1.1 100 Continue\r\n\r\n";
|
||||||
|
self.state.writing = Writing::Continue(Cursor::new(msg));
|
||||||
}
|
}
|
||||||
};
|
let wants_keep_alive = head.should_keep_alive();
|
||||||
|
self.state.keep_alive &= wants_keep_alive;
|
||||||
self.state.version = match version {
|
let (body, reading) = if decoder.is_eof() {
|
||||||
HttpVersion::Http10 => Version::Http10,
|
(false, Reading::KeepAlive)
|
||||||
HttpVersion::Http11 => Version::Http11,
|
} else {
|
||||||
_ => {
|
(true, Reading::Body(decoder))
|
||||||
error!("unimplemented HTTP Version = {:?}", version);
|
};
|
||||||
self.state.close_read();
|
self.state.reading = reading;
|
||||||
return Err(::Error::Version);
|
if !body {
|
||||||
|
self.try_keep_alive();
|
||||||
}
|
}
|
||||||
};
|
return Ok(Async::Ready(Some((head, body))));
|
||||||
|
|
||||||
let decoder = match T::decoder(&head, &mut self.state.method) {
|
|
||||||
Ok(d) => d,
|
|
||||||
Err(e) => {
|
|
||||||
debug!("decoder error = {:?}", e);
|
|
||||||
self.state.close_read();
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("incoming body is {}", decoder);
|
|
||||||
|
|
||||||
self.state.busy();
|
|
||||||
if head.expecting_continue() {
|
|
||||||
let msg = b"HTTP/1.1 100 Continue\r\n\r\n";
|
|
||||||
self.state.writing = Writing::Continue(Cursor::new(msg));
|
|
||||||
}
|
}
|
||||||
let wants_keep_alive = head.should_keep_alive();
|
|
||||||
self.state.keep_alive &= wants_keep_alive;
|
|
||||||
let (body, reading) = if decoder.is_eof() {
|
|
||||||
(false, Reading::KeepAlive)
|
|
||||||
} else {
|
|
||||||
(true, Reading::Body(decoder))
|
|
||||||
};
|
|
||||||
self.state.reading = reading;
|
|
||||||
if !body {
|
|
||||||
self.try_keep_alive();
|
|
||||||
}
|
|
||||||
Ok(Async::Ready(Some((head, body))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_body(&mut self) -> Poll<Option<super::Chunk>, io::Error> {
|
pub fn read_body(&mut self) -> Poll<Option<super::Chunk>, io::Error> {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ impl Http1Transaction for ServerTransaction {
|
|||||||
}, len)))
|
}, len)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Decoder> {
|
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Option<Decoder>> {
|
||||||
use ::header;
|
use ::header;
|
||||||
|
|
||||||
*method = Some(head.subject.0.clone());
|
*method = Some(head.subject.0.clone());
|
||||||
@@ -91,19 +91,22 @@ impl Http1Transaction for ServerTransaction {
|
|||||||
// If Transfer-Encoding header is present, and 'chunked' is
|
// If Transfer-Encoding header is present, and 'chunked' is
|
||||||
// not the final encoding, and this is a Request, then it is
|
// not the final encoding, and this is a Request, then it is
|
||||||
// mal-formed. A server should respond with 400 Bad Request.
|
// mal-formed. A server should respond with 400 Bad Request.
|
||||||
if encodings.last() == Some(&header::Encoding::Chunked) {
|
if head.version == Http10 {
|
||||||
Ok(Decoder::chunked())
|
debug!("HTTP/1.0 has Transfer-Encoding header");
|
||||||
|
Err(::Error::Header)
|
||||||
|
} else if encodings.last() == Some(&header::Encoding::Chunked) {
|
||||||
|
Ok(Some(Decoder::chunked()))
|
||||||
} else {
|
} else {
|
||||||
debug!("request with transfer-encoding header, but not chunked, bad request");
|
debug!("request with transfer-encoding header, but not chunked, bad request");
|
||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
}
|
}
|
||||||
} else if let Some(&header::ContentLength(len)) = head.headers.get() {
|
} else if let Some(&header::ContentLength(len)) = head.headers.get() {
|
||||||
Ok(Decoder::length(len))
|
Ok(Some(Decoder::length(len)))
|
||||||
} else if head.headers.has::<header::ContentLength>() {
|
} else if head.headers.has::<header::ContentLength>() {
|
||||||
debug!("illegal Content-Length: {:?}", head.headers.get_raw("Content-Length"));
|
debug!("illegal Content-Length: {:?}", head.headers.get_raw("Content-Length"));
|
||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
} else {
|
} else {
|
||||||
Ok(Decoder::length(0))
|
Ok(Some(Decoder::length(0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +228,7 @@ impl Http1Transaction for ClientTransaction {
|
|||||||
}, len)))
|
}, len)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decoder(inc: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Decoder> {
|
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
|
// 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.
|
// 1. HEAD responses, and Status 1xx, 204, and 304 cannot have a body.
|
||||||
// 2. Status 2xx to a CONNECT cannot have a body.
|
// 2. Status 2xx to a CONNECT cannot have a body.
|
||||||
@@ -235,13 +238,26 @@ impl Http1Transaction for ClientTransaction {
|
|||||||
// 6. (irrelevant to Response)
|
// 6. (irrelevant to Response)
|
||||||
// 7. Read till EOF.
|
// 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 {
|
match *method {
|
||||||
Some(Method::Head) => {
|
Some(Method::Head) => {
|
||||||
return Ok(Decoder::length(0));
|
return Ok(Some(Decoder::length(0)));
|
||||||
}
|
}
|
||||||
Some(Method::Connect) => match inc.subject.0 {
|
Some(Method::Connect) => match inc.subject.0 {
|
||||||
200...299 => {
|
200...299 => {
|
||||||
return Ok(Decoder::length(0));
|
return Ok(Some(Decoder::length(0)));
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
},
|
},
|
||||||
@@ -251,28 +267,25 @@ impl Http1Transaction for ClientTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match inc.subject.0 {
|
|
||||||
100...199 |
|
|
||||||
204 |
|
|
||||||
304 => return Ok(Decoder::length(0)),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(&header::TransferEncoding(ref codings)) = inc.headers.get() {
|
if let Some(&header::TransferEncoding(ref codings)) = inc.headers.get() {
|
||||||
if codings.last() == Some(&header::Encoding::Chunked) {
|
if inc.version == Http10 {
|
||||||
Ok(Decoder::chunked())
|
debug!("HTTP/1.0 has Transfer-Encoding header");
|
||||||
|
Err(::Error::Header)
|
||||||
|
} else if codings.last() == Some(&header::Encoding::Chunked) {
|
||||||
|
Ok(Some(Decoder::chunked()))
|
||||||
} else {
|
} else {
|
||||||
trace!("not chunked. read till eof");
|
trace!("not chunked. read till eof");
|
||||||
Ok(Decoder::eof())
|
Ok(Some(Decoder::eof()))
|
||||||
}
|
}
|
||||||
} else if let Some(&header::ContentLength(len)) = inc.headers.get() {
|
} else if let Some(&header::ContentLength(len)) = inc.headers.get() {
|
||||||
Ok(Decoder::length(len))
|
Ok(Some(Decoder::length(len)))
|
||||||
} else if inc.headers.has::<header::ContentLength>() {
|
} else if inc.headers.has::<header::ContentLength>() {
|
||||||
debug!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length"));
|
debug!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length"));
|
||||||
Err(::Error::Header)
|
Err(::Error::Header)
|
||||||
} else {
|
} else {
|
||||||
trace!("neither Transfer-Encoding nor Content-Length");
|
trace!("neither Transfer-Encoding nor Content-Length");
|
||||||
Ok(Decoder::eof())
|
Ok(Some(Decoder::eof()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,24 +473,24 @@ mod tests {
|
|||||||
let mut head = MessageHead::<::proto::RequestLine>::default();
|
let mut head = MessageHead::<::proto::RequestLine>::default();
|
||||||
|
|
||||||
head.subject.0 = ::Method::Get;
|
head.subject.0 = ::Method::Get;
|
||||||
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
assert_eq!(*method, Some(::Method::Get));
|
assert_eq!(*method, Some(::Method::Get));
|
||||||
|
|
||||||
head.subject.0 = ::Method::Post;
|
head.subject.0 = ::Method::Post;
|
||||||
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
assert_eq!(*method, Some(::Method::Post));
|
assert_eq!(*method, Some(::Method::Post));
|
||||||
|
|
||||||
head.headers.set(TransferEncoding::chunked());
|
head.headers.set(TransferEncoding::chunked());
|
||||||
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
// transfer-encoding and content-length = chunked
|
// transfer-encoding and content-length = chunked
|
||||||
head.headers.set(ContentLength(10));
|
head.headers.set(ContentLength(10));
|
||||||
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
|
|
||||||
head.headers.remove::<TransferEncoding>();
|
head.headers.remove::<TransferEncoding>();
|
||||||
assert_eq!(Decoder::length(10), ServerTransaction::decoder(&head, method).unwrap());
|
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()]);
|
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());
|
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()]);
|
head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]);
|
||||||
ServerTransaction::decoder(&head, method).unwrap_err();
|
ServerTransaction::decoder(&head, method).unwrap_err();
|
||||||
@@ -486,6 +499,21 @@ mod tests {
|
|||||||
|
|
||||||
head.headers.set_raw("Transfer-Encoding", "gzip");
|
head.headers.set_raw("Transfer-Encoding", "gzip");
|
||||||
ServerTransaction::decoder(&head, method).unwrap_err();
|
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]
|
#[test]
|
||||||
@@ -496,43 +524,64 @@ mod tests {
|
|||||||
let mut head = MessageHead::<::proto::RawStatus>::default();
|
let mut head = MessageHead::<::proto::RawStatus>::default();
|
||||||
|
|
||||||
head.subject.0 = 204;
|
head.subject.0 = 204;
|
||||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
head.subject.0 = 304;
|
head.subject.0 = 304;
|
||||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
|
|
||||||
head.subject.0 = 200;
|
head.subject.0 = 200;
|
||||||
assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
|
|
||||||
*method = Some(::Method::Head);
|
*method = Some(::Method::Head);
|
||||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
|
|
||||||
*method = Some(::Method::Connect);
|
*method = Some(::Method::Connect);
|
||||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
|
|
||||||
|
|
||||||
// CONNECT receiving non 200 can have a body
|
// CONNECT receiving non 200 can have a body
|
||||||
head.subject.0 = 404;
|
head.subject.0 = 404;
|
||||||
head.headers.set(ContentLength(10));
|
head.headers.set(ContentLength(10));
|
||||||
assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
head.headers.remove::<ContentLength>();
|
head.headers.remove::<ContentLength>();
|
||||||
|
|
||||||
|
|
||||||
*method = Some(::Method::Get);
|
*method = Some(::Method::Get);
|
||||||
head.headers.set(TransferEncoding::chunked());
|
head.headers.set(TransferEncoding::chunked());
|
||||||
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
|
|
||||||
// transfer-encoding and content-length = chunked
|
// transfer-encoding and content-length = chunked
|
||||||
head.headers.set(ContentLength(10));
|
head.headers.set(ContentLength(10));
|
||||||
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap());
|
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap().unwrap());
|
||||||
|
|
||||||
head.headers.remove::<TransferEncoding>();
|
head.headers.remove::<TransferEncoding>();
|
||||||
assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap());
|
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()]);
|
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());
|
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()]);
|
head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]);
|
||||||
ClientTransaction::decoder(&head, method).unwrap_err();
|
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")]
|
#[cfg(feature = "nightly")]
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ pub trait Http1Transaction {
|
|||||||
type Incoming;
|
type Incoming;
|
||||||
type Outgoing: Default;
|
type Outgoing: Default;
|
||||||
fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>;
|
fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>;
|
||||||
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<::Method>) -> ::Result<h1::Decoder>;
|
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<::Method>) -> ::Result<Option<h1::Decoder>>;
|
||||||
fn encode(head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> h1::Encoder;
|
fn encode(head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> h1::Encoder;
|
||||||
|
|
||||||
fn should_error_on_parse_eof() -> bool;
|
fn should_error_on_parse_eof() -> bool;
|
||||||
|
|||||||
@@ -458,6 +458,72 @@ test! {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: client_100_continue,
|
||||||
|
|
||||||
|
server:
|
||||||
|
expected: "\
|
||||||
|
POST /continue HTTP/1.1\r\n\
|
||||||
|
Host: {addr}\r\n\
|
||||||
|
Content-Length: 7\r\n\
|
||||||
|
\r\n\
|
||||||
|
foo bar\
|
||||||
|
",
|
||||||
|
reply: "\
|
||||||
|
HTTP/1.1 100 Continue\r\n\
|
||||||
|
\r\n\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
Content-Length: 0\r\n\
|
||||||
|
\r\n\
|
||||||
|
",
|
||||||
|
|
||||||
|
client:
|
||||||
|
request:
|
||||||
|
method: Post,
|
||||||
|
url: "http://{addr}/continue",
|
||||||
|
headers: [
|
||||||
|
ContentLength(7),
|
||||||
|
],
|
||||||
|
body: Some("foo bar"),
|
||||||
|
proxy: false,
|
||||||
|
response:
|
||||||
|
status: Ok,
|
||||||
|
headers: [],
|
||||||
|
body: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: client_101_upgrade,
|
||||||
|
|
||||||
|
server:
|
||||||
|
expected: "\
|
||||||
|
GET /upgrade HTTP/1.1\r\n\
|
||||||
|
Host: {addr}\r\n\
|
||||||
|
\r\n\
|
||||||
|
",
|
||||||
|
reply: "\
|
||||||
|
HTTP/1.1 101 Switching Protocols\r\n\
|
||||||
|
Upgrade: websocket\r\n\
|
||||||
|
Connection: upgrade\r\n\
|
||||||
|
\r\n\
|
||||||
|
",
|
||||||
|
|
||||||
|
client:
|
||||||
|
request:
|
||||||
|
method: Get,
|
||||||
|
url: "http://{addr}/upgrade",
|
||||||
|
headers: [],
|
||||||
|
body: None,
|
||||||
|
proxy: false,
|
||||||
|
error: |err| match err {
|
||||||
|
&hyper::Error::Upgrade => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn client_keep_alive() {
|
fn client_keep_alive() {
|
||||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user