From 9b2998bddc3c033e4fc4e6a9b7d18504339ded3f Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Tue, 8 Sep 2015 09:21:50 +1200 Subject: [PATCH] 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. --- src/server/response.rs | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/server/response.rs b/src/server/response.rs index d836b1e631..18c6168933 100644 --- a/src/server/response.rs +++ b/src/server/response.rs @@ -12,7 +12,7 @@ use time::now_utc; use header; 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 net::{Fresh, Streaming}; use version; @@ -88,11 +88,14 @@ impl<'a, W: Any> Response<'a, W> { self.headers.set(header::Date(header::HttpDate(now_utc()))); } - - let mut body_type = Body::Chunked; - - if let Some(cl) = self.headers.get::() { - body_type = Body::Sized(**cl); + let body_type = match self.status { + status::StatusCode::NoContent | status::StatusCode::NotModified => Body::Empty, + c if c.class() == status::StatusClass::Informational => Body::Empty, + _ => if let Some(cl) = self.headers.get::() { + Body::Sized(**cl) + } else { + Body::Chunked + } }; // 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 stream = match body_type { 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 @@ -227,6 +231,7 @@ impl<'a> Write for Response<'a, Streaming> { enum Body { Chunked, Sized(u64), + Empty, } 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() { Ok(Body::Chunked) => ChunkedWriter(self.body.get_mut()), Ok(Body::Sized(len)) => SizedWriter(self.body.get_mut(), len), + Ok(Body::Empty) => EmptyWriter(self.body.get_mut()), Err(e) => { debug!("error dropping request: {:?}", e); return; @@ -361,4 +367,23 @@ mod tests { "" // 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, + "" + } + } }