fix(server): don't add implicit content-length to HEAD responses (#2836)
HEAD responses should not have content-length implicitly set by hyper. Co-authored-by: Jannes Timm <jannes@cloudflare.com>
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
|
||||||
use tokio::time::Instant;
|
|
||||||
#[cfg(any(test, feature = "server", feature = "ffi"))]
|
#[cfg(any(test, feature = "server", feature = "ffi"))]
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
@@ -10,6 +8,8 @@ use bytes::BytesMut;
|
|||||||
use http::header::ValueIter;
|
use http::header::ValueIter;
|
||||||
use http::header::{self, Entry, HeaderName, HeaderValue};
|
use http::header::{self, Entry, HeaderName, HeaderValue};
|
||||||
use http::{HeaderMap, Method, StatusCode, Version};
|
use http::{HeaderMap, Method, StatusCode, Version};
|
||||||
|
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||||
|
use tokio::time::Instant;
|
||||||
use tracing::{debug, error, trace, trace_span, warn};
|
use tracing::{debug, error, trace, trace_span, warn};
|
||||||
|
|
||||||
use crate::body::DecodedLength;
|
use crate::body::DecodedLength;
|
||||||
@@ -487,6 +487,10 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_have_implicit_zero_content_length(method: &Option<Method>, status: StatusCode) -> bool {
|
||||||
|
Server::can_have_content_length(method, status) && method != &Some(Method::HEAD)
|
||||||
|
}
|
||||||
|
|
||||||
fn encode_headers_with_lower_case(
|
fn encode_headers_with_lower_case(
|
||||||
msg: Encode<'_, StatusCode>,
|
msg: Encode<'_, StatusCode>,
|
||||||
dst: &mut Vec<u8>,
|
dst: &mut Vec<u8>,
|
||||||
@@ -839,7 +843,10 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None | Some(BodyLength::Known(0)) => {
|
None | Some(BodyLength::Known(0)) => {
|
||||||
if Server::can_have_content_length(msg.req_method, msg.head.subject) {
|
if Server::can_have_implicit_zero_content_length(
|
||||||
|
msg.req_method,
|
||||||
|
msg.head.subject,
|
||||||
|
) {
|
||||||
header_name_writer.write_full_header_line(
|
header_name_writer.write_full_header_line(
|
||||||
dst,
|
dst,
|
||||||
"content-length: 0\r\n",
|
"content-length: 0\r\n",
|
||||||
@@ -1033,7 +1040,7 @@ impl Http1Transaction for Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
if let Some(ref mut header_order) = header_order {
|
if let Some(ref mut header_order) = header_order {
|
||||||
header_order.append(&name);
|
header_order.append(&name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2421,6 +2421,26 @@ fn skips_content_length_and_body_for_304_responses() {
|
|||||||
assert_eq!(lines.next(), None);
|
assert_eq!(lines.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_implicit_zero_content_length_for_head_responses() {
|
||||||
|
let server = serve();
|
||||||
|
server.reply().status(hyper::StatusCode::OK).body([]);
|
||||||
|
let mut req = connect(server.addr());
|
||||||
|
req.write_all(
|
||||||
|
b"\
|
||||||
|
HEAD / HTTP/1.1\r\n\
|
||||||
|
Host: example.domain\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
req.read_to_string(&mut response).unwrap();
|
||||||
|
assert!(!response.contains("content-length:"));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn http2_keep_alive_detects_unresponsive_client() {
|
async fn http2_keep_alive_detects_unresponsive_client() {
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|||||||
Reference in New Issue
Block a user