fix(http): fix sending responses for upgraded ones on WS (#4117)

This commit is contained in:
Yuki Okushi 2026-06-22 20:34:45 +09:00 committed by GitHub
parent a928601f31
commit a945e09da8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 83 additions and 8 deletions

2
Cargo.lock generated
View File

@ -49,7 +49,7 @@ dependencies = [
[[package]]
name = "actix-http"
version = "3.13.0"
version = "3.13.1"
dependencies = [
"actix-codec",
"actix-http-test",

View File

@ -2,6 +2,12 @@
## Unreleased
## 3.13.1
- Fix HTTP/1 WebSocket upgrade responses being overwritten with `Connection: close` when the upgraded request payload remains open. [#4115]
[#4115]: https://github.com/actix/actix-web/issues/4115
## 3.13.0
- When configured, gracefully close HTTP/1 connections after early responses to unread request bodies. [#3967]

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.13.0"
version = "3.13.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
description = "HTTP types and services for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"]

View File

@ -5,11 +5,11 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.12.0)](https://docs.rs/actix-http/3.12.0)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.13.1)](https://docs.rs/actix-http/3.13.1)
![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.12.0/status.svg)](https://deps.rs/crate/actix-http/3.12.0)
[![dependency status](https://deps.rs/crate/actix-http/3.13.1/status.svg)](https://deps.rs/crate/actix-http/3.13.1)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -451,7 +451,7 @@ where
mut res: Response<()>,
body: B,
) -> Result<(), DispatchError> {
let close_after_response = {
let close_after_response = !res.upgrade() && {
let this = self.as_mut().project();
should_close_after_response(this.payload.as_ref(), *this.payload_drainable)
};
@ -492,7 +492,7 @@ where
mut res: Response<()>,
body: BoxBody,
) -> Result<(), DispatchError> {
let close_after_response = {
let close_after_response = !res.upgrade() && {
let this = self.as_mut().project();
should_close_after_response(this.payload.as_ref(), *this.payload_drainable)
};

View File

@ -228,6 +228,17 @@ fn ready_chunk_body_service(
})
}
fn upgrade_response_service(
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
fn_service(|_req: Request| {
ready(Ok::<_, Error>(
Response::build(StatusCode::SWITCHING_PROTOCOLS)
.upgrade("websocket")
.finish(),
))
})
}
#[actix_rt::test]
async fn late_request() {
let mut buf = TestBuffer::empty();
@ -1133,6 +1144,64 @@ async fn upgrade_handling() {
.await;
}
#[actix_rt::test]
async fn upgrade_response_does_not_close_unfinished_payload() {
let buf = TestSeqBuffer::new(http_msg(
r"
GET /ws HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
",
));
let services = HttpFlow::new(
upgrade_response_service(),
ExpectHandler,
None::<UpgradeHandler>,
);
let h1 = Dispatcher::new(
buf.clone(),
services,
ServiceConfig::default(),
None,
OnConnectData::default(),
);
pin!(h1);
lazy(|cx| {
assert!(h1.as_mut().poll(cx).is_pending());
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
stabilize_date_header(&mut res);
let res = &res[..];
let exp = http_msg(
r"
HTTP/1.1 101 Switching Protocols
connection: upgrade
upgrade: websocket
date: Thu, 01 Jan 1970 12:34:56 UTC
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
})
.await;
}
// fix in #2624 reverted temporarily
// complete fix tracked in #2745
#[ignore]

View File

@ -137,7 +137,7 @@ actix-service = "2"
actix-tls = { version = "3.4", default-features = false, optional = true }
actix-utils = "3"
actix-http = "3.13.0"
actix-http = "3.13.1"
actix-router = { version = "0.5.4", default-features = false, features = ["http"] }
actix-web-codegen = { version = "4.3", optional = true, default-features = false }

View File

@ -98,7 +98,7 @@ dangerous-h2c = []
[dependencies]
actix-codec = "0.5"
actix-http = { version = "3.13.0", features = ["http2", "ws"] }
actix-http = { version = "3.13.1", features = ["http2", "ws"] }
actix-rt = { version = "2.1", default-features = false }
actix-service = "2"
actix-tls = { version = "3.4", features = ["connect", "uri"] }