mirror of
https://github.com/lexmount/moli.git
synced 2026-09-30 08:01:36 +00:00
perf(fetch): avoid copying response bytes for module consumers
Use byte-only decomposition for Response and NavigationResponse instead of discarding the text returned by into_parts(). Module, dedicated worker, and service worker consumers now transfer the existing exact byte allocation. Verify storage transfer for UTF-8 and non-UTF-8 payloads.
This commit is contained in:
@@ -419,6 +419,16 @@ impl Response {
|
||||
(head, body)
|
||||
}
|
||||
|
||||
/// Consumes a materialized response and transfers its exact byte payload
|
||||
/// without retaining the text view or copying UTF-8 text storage.
|
||||
pub fn into_byte_parts(self) -> (ResponseHead, Vec<u8>) {
|
||||
let (head, body) = self.into_body();
|
||||
let bytes = body
|
||||
.try_into_materialized_bytes()
|
||||
.expect("Response body should remain materialized");
|
||||
(head, bytes)
|
||||
}
|
||||
|
||||
pub fn into_body(self) -> (ResponseHead, ResponseBody) {
|
||||
let head = ResponseHead {
|
||||
final_url: self.final_url,
|
||||
|
||||
@@ -288,6 +288,35 @@ fn response_body_keeps_invalid_utf8_bytes_beside_lossy_text() {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_byte_parts_transfer_utf8_storage_without_copying() {
|
||||
let head = sample_response_head();
|
||||
let bytes = b"module source".to_vec();
|
||||
let storage = bytes.as_ptr();
|
||||
let response = Response::from_head_and_lossy_body_bytes(head.clone(), bytes);
|
||||
|
||||
let (split_head, bytes) = response.into_byte_parts();
|
||||
|
||||
assert_eq!(split_head.final_url, head.final_url);
|
||||
assert_eq!(split_head.status, head.status);
|
||||
assert_eq!(split_head.headers, head.headers);
|
||||
assert_eq!(bytes, b"module source");
|
||||
assert_eq!(bytes.as_ptr(), storage, "UTF-8 storage must be transferred");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_byte_parts_preserve_exact_non_utf8_storage() {
|
||||
let bytes = vec![b'a', 0xff, b'b'];
|
||||
let storage = bytes.as_ptr();
|
||||
let response = Response::from_head_and_lossy_body_bytes(sample_response_head(), bytes);
|
||||
assert_eq!(response.body_text(), "a\u{fffd}b");
|
||||
|
||||
let (_, bytes) = response.into_byte_parts();
|
||||
|
||||
assert_eq!(bytes, [b'a', 0xff, b'b']);
|
||||
assert_eq!(bytes.as_ptr(), storage, "exact bytes must be transferred");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn response_body_materializes_streaming_text_source() {
|
||||
let (body_tx, body_rx) = mpsc::unbounded_channel();
|
||||
|
||||
@@ -492,6 +492,16 @@ impl NavigationResponse {
|
||||
(head, body)
|
||||
}
|
||||
|
||||
/// Consumes a materialized response and transfers its exact byte payload
|
||||
/// without retaining the text view or copying UTF-8 text storage.
|
||||
pub fn into_byte_parts(self) -> (ResponseHead, Vec<u8>) {
|
||||
let (head, body) = self.into_body();
|
||||
let bytes = body
|
||||
.try_into_materialized_bytes()
|
||||
.expect("NavigationResponse body should remain materialized");
|
||||
(head, bytes)
|
||||
}
|
||||
|
||||
pub fn into_body(self) -> (ResponseHead, ResponseBody) {
|
||||
let head = ResponseHead {
|
||||
final_url: self.final_url,
|
||||
@@ -3419,6 +3429,26 @@ mod tests {
|
||||
Url::parse(&format!("https://example.test{path}")).expect("test URL should parse")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn navigation_response_byte_parts_transfer_exact_storage() {
|
||||
let template =
|
||||
NavigationResponse::from_text_body(test_url("/module"), 200, Vec::new(), String::new());
|
||||
for bytes in [b"module source".to_vec(), vec![b'a', 0xff, b'b']] {
|
||||
let expected = bytes.clone();
|
||||
let storage = bytes.as_ptr();
|
||||
let response = NavigationResponse::from_head_and_materialized_body(
|
||||
template.head(),
|
||||
ResponseBody::lossy_text_from_bytes(bytes),
|
||||
);
|
||||
|
||||
let (head, bytes) = response.into_byte_parts();
|
||||
|
||||
assert_eq!(head.final_url, template.final_url);
|
||||
assert_eq!(bytes, expected);
|
||||
assert_eq!(bytes.as_ptr(), storage, "exact bytes must be transferred");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_execution_report_exposes_globals_snapshot_freshness() {
|
||||
let mut report = ScriptExecutionReport::default();
|
||||
|
||||
@@ -468,7 +468,7 @@ impl NativeModuleGraphFetchRequest {
|
||||
crate::referrer_policy::response_referrer_policy_from_headers(
|
||||
&response.headers,
|
||||
);
|
||||
let (head, _, body_bytes) = response.into_parts();
|
||||
let (head, body_bytes) = response.into_byte_parts();
|
||||
crate::network_host::validate_cors_response_chain(
|
||||
&initiator_url,
|
||||
&head,
|
||||
|
||||
@@ -359,7 +359,7 @@ fn load_imported_script_resource_for_update_check(
|
||||
moli_fetch::ensure_http_status_success(response.final_url.as_str(), response.status, false)
|
||||
.map_err(|error| error.to_string())?;
|
||||
ensure_imported_script_resource_mime(kind, &response)?;
|
||||
let (head, _body, body_bytes) = response.into_parts();
|
||||
let (head, body_bytes) = response.into_byte_parts();
|
||||
let mut resource = ServiceWorkerScriptResource::from_response_parts(
|
||||
request_url_without_fragment,
|
||||
&head,
|
||||
|
||||
@@ -757,7 +757,7 @@ fn start_worker_module_graph_fetch(
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
.min(u64::MAX as u128) as u64;
|
||||
let (head, _body, body_bytes) = response.into_parts();
|
||||
let (head, body_bytes) = response.into_byte_parts();
|
||||
let response_referrer_policy =
|
||||
crate::referrer_policy::response_referrer_policy_from_headers(&head.headers);
|
||||
let resource = WorkerScriptResource::from_response_parts(
|
||||
|
||||
Reference in New Issue
Block a user