Conversation
Google Cloud Functions and Firebase HTTP functions read every request body before the function runs and keep the raw bytes on `req.rawBody`. `toStandardBody` returned their parsed `req.body` as-is, so FormData, files, event streams and octet streams arrived as a Buffer, URLSearchParams as a plain object, and text blobs as a string. Once the stream is consumed, the adapter now parses `req.rawBody` (a Uint8Array) the same way it parses the stream, falling back to `req.body` otherwise.
@standard-server/aws-lambda
@standard-server/core
@standard-server/fastify
@standard-server/fetch
@standard-server/node
@standard-server/peer
@standard-server/shared
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
ℹ️ Minor suggestions only — one behavioral edge case worth confirming.
Reviewed changes
- Raw-body preference in the node adapter —
toStandardBodynow synthesizes aReadablefromreq.rawBodywhen it is aUint8Arrayand the stream is already consumed, parsing those bytes with the normal hint logic instead of returning the framework'sreq.body. - Types and docs — adds
rawBody?: unknowntoNodeHttpRequestand documents the precedence inpackages/node/README.md. - GCF integration test — new
tests/google-cloud-functions.test.tsdrives all six body kinds through the real Functions Framework test server; unit tests cover raw-body precedence and the non-Uint8Arrayfallback. - Dependency — adds
@google-cloud/functions-frameworkas a root dev dependency.
I reproduced the regression coverage locally: against main's packages/node/src/body.ts, 5 of the 6 Functions Framework cases fail (json passes as the control) and the new precedence unit test fails; with the PR applied, packages/node plus the root tests suite (691 tests) pass.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏
| req: NodeHttpRequest, | ||
| options: ToStandardBodyOptions = {}, | ||
| ): Promise<StandardBody> { | ||
| const stream = !req.readable && req.rawBody instanceof Uint8Array ? Readable.from([req.rawBody]) : req |
There was a problem hiding this comment.
For an empty application/json request, the Functions Framework leaves req.body as {} while req.rawBody is a zero-length Buffer. Because rawBody is now preferred, _streamToString yields '' and parseEmptyableJSON('') returns undefined, so GCP handlers that used to receive {} now receive undefined. This edge is not covered by the PR's behavior-change notes.
Technical details
# Empty JSON body on GCF resolves to `undefined` instead of `{}`
## Affected sites
- `packages/node/src/body.ts:24` — `req.rawBody instanceof Uint8Array` is true for the framework's zero-length `Buffer`, so the raw path is taken.
- `packages/shared/src/json.ts:1` — `parseEmptyableJSON('')` returns `undefined` (by design).
## Evidence
- Probed through the real Functions Framework with `POST`, `content-type: application/json`, empty body: `req.body` is `{}`, `req.rawBody` is a zero-length `Buffer`, and `toStandardBody(req)` returns `undefined`.
- Before the change the same request returned `{}` via the `req.body !== undefined && !req.readable` branch.
## Required outcome
- Confirm whether this normalization is intended. If so, add it to the PR/README behavior-change notes; the new value matches the non-GCF path (`parseEmptyableJSON('')`), so it may be the desired behavior.
## Open questions for the human
- Preserve `{}` for an empty parsed body, or standardize on `undefined` across all adapters?
The node adapter now resolves request bodies correctly on Google Cloud Functions and Firebase HTTP functions. Their Functions Framework reads every body before the function runs and keeps the raw bytes on
req.rawBody. Before this change,toStandardBodyreturned the framework's ownreq.body, so 5 of the 6 body types tested reached the handler as the wrong type. Once the stream has been read, the adapter now parsesreq.rawBody(anyUint8Array) exactly as it would parse the stream. Otherwise it still falls back toreq.body.Fixes (Functions Framework v5)
FormData,File, async iterators andReadableStreaminstead of aBufferURLSearchParamsinstead of a plain objecttext/plainfiles arrive as aFileinstead of a stringBehavior change
req.rawBodyitself (e.g. Expressverifycallbacks for webhook signatures), JSON is now parsed from those bytes instead of using itsreq.body. The result is identical unless the app changesreq.bodyin between, for example with a JSONreviver.rawBodythat isn't aUint8Array(e.g. a string) is ignored.Platform limits (not fixable in the adapter)
"string",1,null) with a 400 before the adapter runs.Performance
rawBodybehave exactly as before.rawBodyin a stream costs about 5µs per request. We kept that instead of adding a separate raw-bytes branch for each body type.Testing
tests/google-cloud-functions.test.tssends json, url-search-params, file, form-data, event-stream and octet-stream bodies through the real Functions Framework. Five of the six fail againstmain. This adds@google-cloud/functions-frameworkas a root dev dependency.rawBodytaking precedence overreq.body, and arawBodythat isn't aUint8Arraybeing ignored.type:checkand eslint pass.