fix(server): improve detection of when a Response can have a body
By knowing if the incoming Request was a HEAD, or checking for 204 or 304 status codes, the server will do a better job of either adding or removing `Content-Length` and `Transfer-Encoding` headers. Closes #1257
This commit is contained in:
@@ -5,7 +5,8 @@ use httparse;
|
||||
use bytes::{BytesMut, Bytes};
|
||||
|
||||
use header::{self, Headers, ContentLength, TransferEncoding};
|
||||
use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, ServerTransaction, ClientTransaction, RequestLine};
|
||||
use http::{MessageHead, RawStatus, Http1Transaction, ParseResult,
|
||||
ServerTransaction, ClientTransaction, RequestLine, RequestHead};
|
||||
use http::h1::{Encoder, Decoder, date};
|
||||
use method::Method;
|
||||
use status::StatusCode;
|
||||
@@ -72,8 +73,11 @@ impl Http1Transaction for ServerTransaction {
|
||||
}, len)))
|
||||
}
|
||||
|
||||
fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<Decoder> {
|
||||
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<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)
|
||||
@@ -105,30 +109,11 @@ impl Http1Transaction for ServerTransaction {
|
||||
}
|
||||
|
||||
|
||||
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||
use ::header;
|
||||
trace!("writing head: {:?}", head);
|
||||
fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> Encoder {
|
||||
trace!("ServerTransaction::encode head={:?}, has_body={}, method={:?}",
|
||||
head, has_body, method);
|
||||
|
||||
let len = head.headers.get::<header::ContentLength>().map(|n| **n);
|
||||
|
||||
let body = if let Some(len) = len {
|
||||
Encoder::length(len)
|
||||
} else {
|
||||
let encodings = match head.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 {
|
||||
head.headers.set(header::TransferEncoding(vec![header::Encoding::Chunked]));
|
||||
}
|
||||
Encoder::chunked()
|
||||
};
|
||||
let body = ServerTransaction::set_length(&mut head, has_body, method.as_ref());
|
||||
debug!("encode headers = {:?}", head.headers);
|
||||
|
||||
let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE;
|
||||
@@ -150,16 +135,39 @@ impl Http1Transaction for ServerTransaction {
|
||||
extend(dst, b"\r\n");
|
||||
body
|
||||
}
|
||||
}
|
||||
|
||||
fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool {
|
||||
//TODO: pass method, check if method == HEAD
|
||||
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 doesnt live long enough
|
||||
const HEAD: Option<&'static Method> = Some(&Method::Head);
|
||||
const CONNECT: Option<&'static Method> = Some(&Method::Connect);
|
||||
|
||||
match head.subject {
|
||||
// TODO: support for 1xx codes needs improvement everywhere
|
||||
// would be 100...199 => false
|
||||
StatusCode::NoContent |
|
||||
StatusCode::NotModified => false,
|
||||
_ => true,
|
||||
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(&mut head.headers)
|
||||
} else {
|
||||
head.headers.remove::<TransferEncoding>();
|
||||
if can_have_body {
|
||||
head.headers.set(ContentLength(0));
|
||||
}
|
||||
Encoder::length(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,8 +221,7 @@ impl Http1Transaction for ClientTransaction {
|
||||
}, len)))
|
||||
}
|
||||
|
||||
fn decoder(inc: &MessageHead<Self::Incoming>) -> ::Result<Decoder> {
|
||||
use ::header;
|
||||
fn decoder(inc: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<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.
|
||||
@@ -224,7 +231,21 @@ impl Http1Transaction for ClientTransaction {
|
||||
// 6. (irrelevant to Response)
|
||||
// 7. Read till EOF.
|
||||
|
||||
//TODO: need a way to pass the Method that caused this Response
|
||||
match *method {
|
||||
Some(Method::Head) => {
|
||||
return Ok(Decoder::length(0));
|
||||
}
|
||||
Some(Method::Connect) => match inc.subject.0 {
|
||||
200...299 => {
|
||||
return Ok(Decoder::length(0));
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
Some(_) => {},
|
||||
None => {
|
||||
trace!("ClientTransaction::decoder is missing the Method");
|
||||
}
|
||||
}
|
||||
|
||||
match inc.subject.0 {
|
||||
100...199 |
|
||||
@@ -251,39 +272,14 @@ impl Http1Transaction for ClientTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||
trace!("writing head: {:?}", head);
|
||||
fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> Encoder {
|
||||
trace!("ClientTransaction::encode head={:?}, has_body={}, method={:?}",
|
||||
head, has_body, method);
|
||||
|
||||
|
||||
let mut body = Encoder::length(0);
|
||||
let expects_no_body = match head.subject.0 {
|
||||
Method::Head | Method::Get | Method::Connect => true,
|
||||
_ => false
|
||||
};
|
||||
let mut chunked = false;
|
||||
*method = Some(head.subject.0.clone());
|
||||
|
||||
if let Some(con_len) = head.headers.get::<ContentLength>() {
|
||||
body = Encoder::length(**con_len);
|
||||
} else {
|
||||
chunked = !expects_no_body;
|
||||
}
|
||||
|
||||
if chunked {
|
||||
body = Encoder::chunked();
|
||||
let encodings = match head.headers.get_mut::<TransferEncoding>() {
|
||||
Some(encodings) => {
|
||||
if !encodings.contains(&header::Encoding::Chunked) {
|
||||
encodings.push(header::Encoding::Chunked);
|
||||
}
|
||||
true
|
||||
},
|
||||
None => false
|
||||
};
|
||||
|
||||
if !encodings {
|
||||
head.headers.set(TransferEncoding(vec![header::Encoding::Chunked]));
|
||||
}
|
||||
}
|
||||
let body = ClientTransaction::set_length(&mut head, has_body);
|
||||
debug!("encode headers = {:?}", head.headers);
|
||||
|
||||
let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE;
|
||||
@@ -292,16 +288,43 @@ impl Http1Transaction for ClientTransaction {
|
||||
|
||||
body
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool {
|
||||
match &head.subject.0 {
|
||||
&Method::Get | &Method::Head => false,
|
||||
_ => true
|
||||
impl ClientTransaction {
|
||||
fn set_length(head: &mut RequestHead, has_body: bool) -> Encoder {
|
||||
if has_body {
|
||||
set_length(&mut head.headers)
|
||||
} else {
|
||||
head.headers.remove::<ContentLength>();
|
||||
head.headers.remove::<TransferEncoding>();
|
||||
Encoder::length(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_length(headers: &mut Headers) -> Encoder {
|
||||
let len = headers.get::<header::ContentLength>().map(|n| **n);
|
||||
|
||||
if let Some(len) = len {
|
||||
Encoder::length(len)
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct HeaderIndices {
|
||||
name: (usize, usize),
|
||||
@@ -421,63 +444,83 @@ mod tests {
|
||||
fn test_decoder_request() {
|
||||
use super::Decoder;
|
||||
|
||||
let method = &mut None;
|
||||
let mut head = MessageHead::<::http::RequestLine>::default();
|
||||
|
||||
head.subject.0 = ::Method::Get;
|
||||
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap());
|
||||
assert_eq!(*method, Some(::Method::Get));
|
||||
|
||||
head.subject.0 = ::Method::Post;
|
||||
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap());
|
||||
assert_eq!(*method, Some(::Method::Post));
|
||||
|
||||
head.headers.set(TransferEncoding::chunked());
|
||||
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap());
|
||||
// transfer-encoding and content-length = chunked
|
||||
head.headers.set(ContentLength(10));
|
||||
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
head.headers.remove::<TransferEncoding>();
|
||||
assert_eq!(Decoder::length(10), ServerTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(10), ServerTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]);
|
||||
assert_eq!(Decoder::length(5), ServerTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(5), ServerTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]);
|
||||
ServerTransaction::decoder(&head).unwrap_err();
|
||||
ServerTransaction::decoder(&head, method).unwrap_err();
|
||||
|
||||
head.headers.remove::<ContentLength>();
|
||||
|
||||
head.headers.set_raw("Transfer-Encoding", "gzip");
|
||||
ServerTransaction::decoder(&head).unwrap_err();
|
||||
ServerTransaction::decoder(&head, method).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decoder_response() {
|
||||
use super::Decoder;
|
||||
|
||||
let method = &mut Some(::Method::Get);
|
||||
let mut head = MessageHead::<::http::RawStatus>::default();
|
||||
|
||||
head.subject.0 = 204;
|
||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap());
|
||||
head.subject.0 = 304;
|
||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
head.subject.0 = 200;
|
||||
assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
*method = Some(::Method::Head);
|
||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
*method = Some(::Method::Connect);
|
||||
assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).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());
|
||||
head.headers.remove::<ContentLength>();
|
||||
|
||||
|
||||
*method = Some(::Method::Get);
|
||||
head.headers.set(TransferEncoding::chunked());
|
||||
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
// transfer-encoding and content-length = chunked
|
||||
head.headers.set(ContentLength(10));
|
||||
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
head.headers.remove::<TransferEncoding>();
|
||||
assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]);
|
||||
assert_eq!(Decoder::length(5), ClientTransaction::decoder(&head).unwrap());
|
||||
assert_eq!(Decoder::length(5), ClientTransaction::decoder(&head, method).unwrap());
|
||||
|
||||
head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]);
|
||||
ClientTransaction::decoder(&head).unwrap_err();
|
||||
ClientTransaction::decoder(&head, method).unwrap_err();
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
@@ -541,7 +584,7 @@ mod tests {
|
||||
|
||||
b.iter(|| {
|
||||
let mut vec = Vec::new();
|
||||
ServerTransaction::encode(head.clone(), &mut vec);
|
||||
ServerTransaction::encode(head.clone(), true, &mut None, &mut vec);
|
||||
assert_eq!(vec.len(), len);
|
||||
::test::black_box(vec);
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user