Skip to content

Commit 62c65db

Browse files
authored
fix(teeny-request): set Content-Length for buffered request bodies in Bun (#9748)
## Description Sets `Content-Length` for `string`, `Buffer`, and `Uint8Array` bodies in `bunFetch`. ## Impact Prevents unexpected chunked transfer encoding on buffered requests under Bun. ## Changes - **Stack Overview** (for `b/578555176`, replacing #9741): 1. **#9748 (this PR — kept as small as possible)**: `fix/teeny-request-bun-part-1` $\to$ `main` — Set `Content-Length` for buffered request bodies (`string`, `Buffer`, `Uint8Array`) in `bunFetch`. 2. #9749: `fix/teeny-request-bun-part-2` $\to$ `fix/teeny-request-bun-part-1` — Default `Content-Length: '0'` for bodyless `POST` and `PUT` requests in `bunFetch`. 3. #9750: `fix/teeny-request-bun-part-3` $\to$ `fix/teeny-request-bun-part-2` — Lazily initialize `zlib` decompressor in `bunFetch` so `0`-byte compressed responses do not throw `Z_BUF_ERROR`. 4. #9751: `fix/teeny-request-bun-part-4` $\to$ `fix/teeny-request-bun-part-3` — Auto-detect RFC 1950 vs. raw RFC 1951 `deflate` streams in `bunFetch`. 5. #9752: `fix/teeny-request-bun-style-maintenance` $\to$ `fix/teeny-request-bun-part-4` — `[Style Maintenance]` JSDoc documentation and `CONTRIBUTING.md` / `gts` alignment. - Compute `headers['Content-Length'] = String(Buffer.byteLength(init.body))` in `bunFetch` (`core/packages/teeny-request/src/index.ts`) for `string`, `Buffer`, and `Uint8Array` request bodies when neither `Content-Length` nor `Transfer-Encoding` is already set. ## Testing - Added unit test in `core/packages/teeny-request/test/index.ts` verifying `Content-Length: 5` on `POST` requests with a string body under `bunFetch`. - **Steps to Reproduce (in terminal)**: - Direct terminal command: Run `pnpm --dir core/packages/teeny-request run compile && node bin/run-test.cjs --no-c8 core/packages/teeny-request/build/test/index.js` against `main` (fails `matchHeader('content-length', '5')`), then re-run on `fix/teeny-request-bun-part-1` to confirm it passes. - Or via shortened reproduction Gist ([repro.mjs](https://gist.github.com/danieljbruce/990f5fdb7806eb3454cccea3c1ceb3bd)): `gh gist view https://gist.github.com/danieljbruce/990f5fdb7806eb3454cccea3c1ceb3bd --raw | node --input-type=module -` ## Alternatives - Relying on Node's `http.request` default chunked encoding for buffered bodies was considered and rejected because `node-fetch` sets `Content-Length` on in-memory request payloads and downstream Google Cloud endpoints expect `Content-Length`. - Not merging this PR was considered, but doing so leaves `bunFetch` omitting `Content-Length` on buffered request bodies under Bun.
1 parent 71972b8 commit 62c65db

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

‎core/packages/teeny-request/src/index.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,21 @@ function bunFetch(
8383
}
8484
}
8585

86+
const hasContentLength = Object.keys(headers).some(
87+
k =>
88+
k.toLowerCase() === 'content-length' ||
89+
k.toLowerCase() === 'transfer-encoding',
90+
);
91+
if (!hasContentLength) {
92+
if (
93+
typeof init.body === 'string' ||
94+
Buffer.isBuffer(init.body) ||
95+
init.body instanceof Uint8Array
96+
) {
97+
headers['Content-Length'] = String(Buffer.byteLength(init.body));
98+
}
99+
}
100+
86101
const reqOptions: https.RequestOptions & {proto?: string} = {
87102
protocol: parsedUrl.protocol,
88103
proto: isHttps ? 'https' : 'http',

‎core/packages/teeny-request/test/index.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,27 @@ describe('teeny', () => {
706706
await new Promise<void>(resolve => server.close(() => resolve()));
707707
nock.disableNetConnect();
708708
}
709+
710+
const contentLengthScope = nock(uri)
711+
.post('/content-length', 'hello')
712+
.matchHeader('content-length', '5')
713+
.reply(200, 'ok');
714+
await new Promise<void>((resolve, reject) => {
715+
teenyRequest(
716+
{uri: `${uri}/content-length`, method: 'POST', body: 'hello'},
717+
(err, res, body) => {
718+
if (err) return reject(err);
719+
try {
720+
assert.strictEqual(res.statusCode, 200);
721+
assert.strictEqual(body, 'ok');
722+
contentLengthScope.done();
723+
resolve();
724+
} catch (e) {
725+
reject(e);
726+
}
727+
},
728+
);
729+
});
709730
} finally {
710731
if (!hadBun) {
711732
delete globalRecord.Bun;

0 commit comments

Comments
 (0)