Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/bun-types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2034,6 +2034,37 @@ interface BunFetchRequestInit extends RequestInit {
*/
decompress?: boolean;

/**
* Automatically compress the request body before sending and set the
* `Content-Encoding` request header accordingly.
*
* - `true` is equivalent to `"gzip"`.
* - A string selects the encoding with its default level.
* - An object selects the encoding and an explicit compression `level`.
*
* Only buffered bodies (string, `ArrayBuffer`/`TypedArray`, `Blob`) are
* compressed; `ReadableStream` bodies are sent as-is. If the request
* already has a `Content-Encoding` header, the body is left unchanged.
* This is a custom property that is not part of the Fetch API specification.
*
* @default false
* @example
* ```js
* await fetch("https://example.com/upload", {
* method: "POST",
* body: JSON.stringify(bigPayload),
* compress: "gzip",
* });
* ```
*/
compress?:
| boolean
| "gzip"
| "deflate"
| "br"
| "zstd"
| { encoding: "gzip" | "deflate" | "br" | "zstd"; level?: number };

/**
* The maximum number of redirects to follow when `redirect` is `"follow"`.
* If the response chain redirects more than this many times, the request
Expand Down
61 changes: 61 additions & 0 deletions src/runtime/webcore/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub(crate) const FETCH_TYPE_ERROR_STRINGS: [&str; 8] = FETCH_TYPE_ERROR_STRING_V
#[path = "fetch/FetchTasklet.rs"]
pub mod fetch_tasklet;

#[path = "fetch/compress_body.rs"]
pub mod compress_body;

// ──────────────────────────────────────────────────────────────────────────
// fetch() implementation
// ──────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -420,6 +423,7 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
let mut disable_timeout = false;
let mut disable_keepalive = false;
let mut disable_decompression = false;
let mut compress: Option<compress_body::CompressOption> = None;
let mut max_redirects: Option<u8> = None;
let mut verbose: http::HTTPVerboseLevel = if vm
.log_ref()
Expand Down Expand Up @@ -664,6 +668,34 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
return Ok(JSValue::ZERO);
}

// "compress: boolean | string | { encoding, level? }"
'extract_compress: {
let objects_to_try = [
options_object.unwrap_or(JSValue::ZERO),
request_init_object.unwrap_or(JSValue::ZERO),
];

for obj in objects_to_try {
if !obj.is_empty() {
if let Some(compress_value) = obj.get(global_this, "compress")? {
if !compress_value.is_undefined() {
compress =
compress_body::CompressOption::from_js(global_this, compress_value)?;
break 'extract_compress;
}
}

if global_this.has_exception() {
return Ok(JSValue::ZERO);
}
}
}
}

if global_this.has_exception() {
return Ok(JSValue::ZERO);
}

// "maxRedirects: number"
'extract_max_redirects: {
let objects_to_try = [
Expand Down Expand Up @@ -1731,6 +1763,35 @@ fn fetch_impl<const ALLOW_GET_BODY: bool>(
}
}

// Automatic request-body compression. Only buffered bodies (Blob bytes,
// ArrayBuffer/TypedArray, string) are handled; ReadableStream and sendfile
// are skipped. S3 destinations replace the header set with a signed one,
// so compression is skipped there too.
if let Some(compress_opt) = compress
&& let HTTPRequestBody::AnyBlob(_) = &body
&& !url.is_s3()
{
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
let already_has_encoding = headers
.as_ref()
.and_then(|h| h.get_content_encoding())
.is_some();
if !already_has_encoding {
let input = body.slice();
if !input.is_empty() {
let compressed =
compress_body::compress_request_body(global_this, input, &compress_opt)?;
let mut old = core::mem::replace(
&mut body,
HTTPRequestBody::AnyBlob(blob::Any::from_owned_slice(compressed)),
);
old.detach();
headers
.get_or_insert_default()
.append(b"Content-Encoding", compress_opt.encoding.header_value());
}
}
}

if url.is_s3() {
// get ENV config — `Transpiler::env_mut` is the safe accessor for the
// process-singleton dotenv loader (set during init).
Expand Down
Loading
Loading