fix(server): use EmptyWriter for status codes that have no body

Previously, hyper was defaulting to Chunked which adds a Transfer-Encoding
header, whenever there was no Content-Length header. RFC 7230 section 3.3.1
reads:

   ...
   A server MUST NOT send a Transfer-Encoding header field in any
   response with a status code of 1xx (Informational) or 204 (No
   Content). A server MUST NOT send a Transfer-Encoding header field in
   any 2xx (Successful) response to a CONNECT request
   ...

This commit fixes the cases of 1xx (Informational), 204 (No Content) by
using the EmptyWriter. It also uses EmptyWriter for 304 (NotModified) which
should not have a body.

It does NOT address the case of responses to CONNECT requests, or to HEAD
requests which do not send a body.  These cases cannot be determined using
the data available in the response, and are left for future work.
This commit is contained in:
Mike Dilger
2015-09-08 09:21:50 +12:00
parent 1b869c4457
commit 9b2998bddc

View File

@@ -12,7 +12,7 @@ use time::now_utc;
use header; use header;
use http::h1::{CR, LF, LINE_ENDING, HttpWriter}; use http::h1::{CR, LF, LINE_ENDING, HttpWriter};
use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter}; use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter};
use status; use status;
use net::{Fresh, Streaming}; use net::{Fresh, Streaming};
use version; use version;
@@ -88,11 +88,14 @@ impl<'a, W: Any> Response<'a, W> {
self.headers.set(header::Date(header::HttpDate(now_utc()))); self.headers.set(header::Date(header::HttpDate(now_utc())));
} }
let body_type = match self.status {
let mut body_type = Body::Chunked; status::StatusCode::NoContent | status::StatusCode::NotModified => Body::Empty,
c if c.class() == status::StatusClass::Informational => Body::Empty,
if let Some(cl) = self.headers.get::<header::ContentLength>() { _ => if let Some(cl) = self.headers.get::<header::ContentLength>() {
body_type = Body::Sized(**cl); Body::Sized(**cl)
} else {
Body::Chunked
}
}; };
// can't do in match above, thanks borrowck // can't do in match above, thanks borrowck
@@ -177,7 +180,8 @@ impl<'a> Response<'a, Fresh> {
let (version, body, status, headers) = self.deconstruct(); let (version, body, status, headers) = self.deconstruct();
let stream = match body_type { let stream = match body_type {
Body::Chunked => ChunkedWriter(body.into_inner()), Body::Chunked => ChunkedWriter(body.into_inner()),
Body::Sized(len) => SizedWriter(body.into_inner(), len) Body::Sized(len) => SizedWriter(body.into_inner(), len),
Body::Empty => EmptyWriter(body.into_inner()),
}; };
// "copy" to change the phantom type // "copy" to change the phantom type
@@ -227,6 +231,7 @@ impl<'a> Write for Response<'a, Streaming> {
enum Body { enum Body {
Chunked, Chunked,
Sized(u64), Sized(u64),
Empty,
} }
impl<'a, T: Any> Drop for Response<'a, T> { impl<'a, T: Any> Drop for Response<'a, T> {
@@ -235,6 +240,7 @@ impl<'a, T: Any> Drop for Response<'a, T> {
let mut body = match self.write_head() { let mut body = match self.write_head() {
Ok(Body::Chunked) => ChunkedWriter(self.body.get_mut()), Ok(Body::Chunked) => ChunkedWriter(self.body.get_mut()),
Ok(Body::Sized(len)) => SizedWriter(self.body.get_mut(), len), Ok(Body::Sized(len)) => SizedWriter(self.body.get_mut(), len),
Ok(Body::Empty) => EmptyWriter(self.body.get_mut()),
Err(e) => { Err(e) => {
debug!("error dropping request: {:?}", e); debug!("error dropping request: {:?}", e);
return; return;
@@ -361,4 +367,23 @@ mod tests {
"" // empty zero body "" // empty zero body
} }
} }
#[test]
fn test_no_content() {
use std::io::Write;
use status::StatusCode;
let mut headers = Headers::new();
let mut stream = MockStream::new();
{
let mut res = Response::new(&mut stream, &mut headers);
*res.status_mut() = StatusCode::NoContent;
res.start().unwrap();
}
lines! { stream =
"HTTP/1.1 204 No Content",
_date,
""
}
}
} }