Skip to content

Commit dd4aaba

Browse files
authored
monkey-patch formData to enable multipart parsing (#5292)
* monkey-patch formData to enable multipart parsing * convert ReadableStream to stream.Readable without buffering * remove obsolete code * bypass turbo cache * oops, wrong one * belt and braces * well that wasnt it * trying something * install polyfills unconditionally * remove debugging code * add test * changeset
1 parent 4692d4b commit dd4aaba

File tree

9 files changed

+30
-12
lines changed

9 files changed

+30
-12
lines changed

.changeset/empty-roses-obey.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
Enable multipart formdata parsing with node-fetch

packages/kit/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"locate-character": "^2.0.5",
3434
"marked": "^4.0.16",
3535
"mime": "^3.0.0",
36+
"node-fetch": "^3.2.4",
3637
"port-authority": "^1.2.0",
3738
"rollup": "^2.75.3",
3839
"selfsigned": "^2.0.1",

packages/kit/src/node/index.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import * as set_cookie_parser from 'set-cookie-parser';
2+
import { Request as NodeFetchRequest } from 'node-fetch';
3+
import { Readable } from 'stream';
24

35
/** @param {import('http').IncomingMessage} req */
46
function get_raw_body(req) {
@@ -55,11 +57,22 @@ export async function getRequest(base, req) {
5557
delete headers[':scheme'];
5658
}
5759

58-
return new Request(base + req.url, {
60+
const request = new Request(base + req.url, {
5961
method: req.method,
6062
headers,
6163
body: get_raw_body(req)
6264
});
65+
66+
request.formData = async () => {
67+
return new NodeFetchRequest(request.url, {
68+
method: request.method,
69+
headers: request.headers,
70+
// @ts-expect-error TypeScript doesn't understand that ReadableStream implements Symbol.asyncIterator
71+
body: request.body && Readable.from(request.body)
72+
}).formData();
73+
};
74+
75+
return request;
6376
}
6477

6578
/** @type {import('@sveltejs/kit/node').setResponse} */

packages/kit/src/node/polyfills.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ const globals = {
1717
// exported for dev/preview and node environments
1818
export function installPolyfills() {
1919
for (const name in globals) {
20-
if (name in globalThis) continue;
21-
2220
Object.defineProperty(globalThis, name, {
2321
enumerable: true,
2422
configurable: true,

packages/kit/src/runtime/server/utils.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,13 @@ export function is_pojo(body) {
3838

3939
if (body) {
4040
if (body instanceof Uint8Array) return false;
41+
if (body instanceof ReadableStream) return false;
4142

4243
// if body is a node Readable, throw an error
4344
// TODO remove this for 1.0
4445
if (body._readableState && typeof body.pipe === 'function') {
4546
throw new Error('Node streams are no longer supported — use a ReadableStream instead');
4647
}
47-
48-
if (body instanceof ReadableStream) return false;
49-
50-
// similarly, it could be a web ReadableStream
51-
if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) return false;
5248
}
5349

5450
return true;

packages/kit/test/apps/basics/src/routes/shadowed/error-post.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ export function get() {
66
};
77
}
88

9-
export function post() {
9+
export async function post({ request }) {
10+
const data = await request.formData();
11+
1012
return {
1113
status: 400,
1214
body: {
13-
post_message: 'hello from post'
15+
post_message: `echo: ${data.get('message')}`
1416
}
1517
};
1618
}

packages/kit/test/apps/basics/src/routes/shadowed/index.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<button type="submit" id="redirect-post-with-cookie">redirect</button>
1515
</form>
1616

17-
<form action="/shadowed/error-post" method="post">
17+
<form action="/shadowed/error-post" method="post" enctype="multipart/form-data">
18+
<input name="message" value="posted data" />
1819
<button type="submit" id="error-post">error</button>
1920
</form>

packages/kit/test/apps/basics/test/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ test.describe.parallel('Shadowed pages', () => {
543543
test('Merges bodies for 4xx and 5xx responses from non-GET', async ({ page }) => {
544544
await page.goto('/shadowed');
545545
await Promise.all([page.waitForNavigation(), page.click('#error-post')]);
546-
expect(await page.textContent('h1')).toBe('hello from get / hello from post');
546+
expect(await page.textContent('h1')).toBe('hello from get / echo: posted data');
547547
});
548548

549549
test('Responds from endpoint if Accept includes application/json but not text/html', async ({

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)