fix(fetch): classify 101 as final in response timing

Reuse the existing interim status predicate in buffered, HTML and raw
collectors so Switching Protocols starts the final response timeline.
Cover 101 alone and after 103 across all collectors, plus HTML header completion.
This commit is contained in:
ldm0
2026-10-19 03:00:24 +08:00
committed by Donough Liu
parent 703765dcd7
commit 5fc2edc671
2 changed files with 59 additions and 5 deletions
+5 -5
View File
@@ -1078,7 +1078,7 @@ impl Handler for ResponseCollector {
self.status_text = Some(parsed.status_text);
self.body.clear();
if let Some(progress) = &self.response_progress {
if parsed.status < 200 {
if is_interim_response_status(parsed.status) {
progress.mark_interim_response_started();
} else {
progress.mark_response_started();
@@ -1155,7 +1155,7 @@ impl Handler for StreamingResponseCollector {
self.declared_identity_body_length = None;
self.response_too_large = false;
self.cookie_set_reports.clear();
if self.status < 200 {
if is_interim_response_status(self.status) {
self.response_progress.mark_interim_response_started();
} else {
self.response_progress.mark_response_started();
@@ -1233,10 +1233,10 @@ impl Handler for RawStreamingResponseCollector {
self.response_bytes_received = 0;
self.response_too_large = false;
self.cookie_set_reports.clear();
if self.status >= 200 {
self.response_progress.mark_response_started();
} else {
if is_interim_response_status(self.status) {
self.response_progress.mark_interim_response_started();
} else {
self.response_progress.mark_response_started();
}
return true;
}
@@ -177,6 +177,7 @@ async fn html_response_timing_records_terminal_failures() -> Result<()> {
#[tokio::test]
async fn html_response_timing_finishes_bodyless_responses_at_headers() -> Result<()> {
for (method, headers) in [
("GET", "HTTP/1.1 101 Switching Protocols\r\n"),
("GET", "HTTP/1.1 204 No Content\r\n"),
("GET", "HTTP/1.1 205 Reset Content\r\n"),
("GET", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"),
@@ -328,3 +329,56 @@ async fn html_declared_body_completion_precedes_delivery_of_the_last_chunk() ->
}
Ok(())
}
#[test]
fn switching_protocols_timing_is_final_in_every_collector() {
for interim in [false, true] {
for mode in ["buffered", "html", "raw"] {
let cancel = FetchCancelHandle::new();
let mut handler = match mode {
"buffered" => {
FetchTransferHandler::new_buffered(ResponseCollector::new(Some(cancel.clone())))
}
"html" => {
let (start_tx, _start_rx) = oneshot::channel();
let (body_tx, _body_rx) = mpsc::unbounded_channel();
FetchTransferHandler::new_streaming(StreamingResponseCollector::new(
new_shared_browser_cookie_store(),
start_tx,
body_tx,
cancel.clone(),
))
}
"raw" => {
let (start_tx, _start_rx) = oneshot::channel();
let (body_tx, _body_rx) = mpsc::unbounded_channel();
FetchTransferHandler::new_raw_streaming(RawStreamingResponseCollector::new(
new_shared_browser_cookie_store(),
start_tx,
body_tx,
cancel.clone(),
))
}
_ => unreachable!(),
};
if interim {
assert!(handler.header(b"HTTP/1.1 103 Early Hints\r\n"));
assert!(cancel.response_timing().response_started_at().is_none());
}
assert!(handler.header(b"HTTP/1.1 101 Switching Protocols\r\n"));
let timing = cancel.response_timing();
assert!(
timing.response_started_at().is_some(),
"{mode}: 101 must be final"
);
assert_eq!(
timing.first_interim_response_started_at().is_some(),
interim,
"{mode}"
);
if let Some(first_interim) = timing.first_interim_response_started_at() {
assert!(first_interim <= timing.response_started_at().unwrap());
}
}
}
}