fix(http): advertise keep-alive on HTTP/1.0 responses we keep open
1.0 defaults to close: honoring 'Connection: Keep-Alive' without echoing it back left spec-following clients waiting for an EOF that never came (ab -k deadlocked to its poll timeout on response 1). As agreed: - serialise_response echoes 'connection: keep-alive' when keep_alive is on AND version is 1.0. 1.1 keep-alive stays implicit. - Error statuses (>=400) on 1.0 force keep_alive off in the conn actor (joins the existing 1.0-stream force-close): we don't advertise reuse on a 4xx/5xx to a protocol generation where reuse is opt-in. - HEAD needs nothing: the echo is framing-neutral. - Parse-error responses already carried 'connection: close'. Tests: unit pair (1.0 echo / 1.1 implicit) + integration pair (socket actually reused on 1.0; 404 forces close + EOF). Validated against the original repro: ab -k -n 5000 -c 50 GET /users -> 5000/5000 keep-alive, 0 failed, ~63k rps (~18.9k without keep-alive).
This commit is contained in:
+5
-2
@@ -248,10 +248,13 @@ pub fn run_connection(
|
||||
// ----- 4. Write the response. -----
|
||||
// A Stream body on HTTP/1.0 has no chunked framing: the body is
|
||||
// delimited by EOF, so keep-alive is forced off for this response
|
||||
// (and `connection: close` goes on the wire).
|
||||
// (and `connection: close` goes on the wire). Error statuses on
|
||||
// 1.0 also force close: we don't advertise keep-alive on a
|
||||
// 4xx/5xx to a protocol generation where reuse is opt-in.
|
||||
let is_stream = matches!(response_conn.resp_body, RespBody::Stream(_));
|
||||
let is_error = response_conn.status.unwrap_or(200) >= 400;
|
||||
let keep_alive = keep_alive
|
||||
&& !(is_stream && version == HttpVersion::Http10);
|
||||
&& !(version == HttpVersion::Http10 && (is_stream || is_error));
|
||||
|
||||
let head_bytes = parser::serialise_response(&response_conn, keep_alive);
|
||||
if write_all(raw, &head_bytes, Instant::now() + limits.write_timeout).is_err() {
|
||||
|
||||
Reference in New Issue
Block a user