From cf8a7e933d26125eee44ce8b4f84d1353cfed957 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 14 Sep 2022 07:58:00 -0400 Subject: [PATCH] Properly handle multipart file uploads in the dev server (#4742) * Properly allow file uploads in the dev server * Smaller image * movethe test over --- .changeset/polite-melons-pump.md | 5 +++ .../src/vite-plugin-astro-server/index.ts | 9 ++--- packages/astro/test/api-routes.test.js | 3 ++ .../test/fixtures/api-routes/package.json | 3 ++ .../ssr-api-route/src/images/penguin.jpg | Bin 0 -> 7231 bytes .../ssr-api-route/src/pages/binary.js | 32 ++++++++++++++++++ packages/astro/test/ssr-api-route.test.js | 15 ++++++++ packages/astro/test/test-utils.js | 2 +- 8 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 .changeset/polite-melons-pump.md create mode 100644 packages/astro/test/fixtures/ssr-api-route/src/images/penguin.jpg create mode 100644 packages/astro/test/fixtures/ssr-api-route/src/pages/binary.js diff --git a/.changeset/polite-melons-pump.md b/.changeset/polite-melons-pump.md new file mode 100644 index 000000000..db34f8408 --- /dev/null +++ b/.changeset/polite-melons-pump.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Allow file uploads in dev server diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts index 5d46d463c..05e82a226 100644 --- a/packages/astro/src/vite-plugin-astro-server/index.ts +++ b/packages/astro/src/vite-plugin-astro-server/index.ts @@ -215,13 +215,14 @@ async function handleRequest( let body: ArrayBuffer | undefined = undefined; if (!(req.method === 'GET' || req.method === 'HEAD')) { - let bytes: string[] = []; + let bytes: Uint8Array[] = []; await new Promise((resolve) => { - req.setEncoding('utf-8'); - req.on('data', (bts) => bytes.push(bts)); + req.on('data', part => { + bytes.push(part); + }); req.on('end', resolve); }); - body = new TextEncoder().encode(bytes.join('')).buffer; + body = Buffer.concat(bytes); } // Headers are only available when using SSR. diff --git a/packages/astro/test/api-routes.test.js b/packages/astro/test/api-routes.test.js index 80fb0970d..15d79d7f7 100644 --- a/packages/astro/test/api-routes.test.js +++ b/packages/astro/test/api-routes.test.js @@ -1,8 +1,11 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; +import * as fs from 'fs'; +import { FormData, File } from 'node-fetch' describe('API routes', () => { + /** @type {import('./test-utils').Fixture} */ let fixture; before(async () => { diff --git a/packages/astro/test/fixtures/api-routes/package.json b/packages/astro/test/fixtures/api-routes/package.json index 0f7052df4..aa8c0adee 100644 --- a/packages/astro/test/fixtures/api-routes/package.json +++ b/packages/astro/test/fixtures/api-routes/package.json @@ -4,5 +4,8 @@ "private": true, "dependencies": { "astro": "workspace:*" + }, + "scripts": { + "dev": "astro dev" } } diff --git a/packages/astro/test/fixtures/ssr-api-route/src/images/penguin.jpg b/packages/astro/test/fixtures/ssr-api-route/src/images/penguin.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae4daa5488d29d137c9fb9a9ecbb5bc4bb5cd7f0 GIT binary patch literal 7231 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 { /** @type {import('./test-utils').Fixture} */ @@ -54,6 +55,20 @@ describe('API routes in SSR', () => { expect(text).to.equal(`ok`); }); + it('Can be passed binary data from multipart formdata', async () => { + const formData = new FormData(); + const raw = await fs.promises.readFile(new URL('./fixtures/ssr-api-route/src/images/penguin.jpg', import.meta.url)); + const file = new File([raw], 'penguin.jpg', { type: 'text/jpg' }); + formData.set('file', file, 'penguin.jpg'); + + const res = await fixture.fetch('/binary', { + method: 'POST', + body: formData + }); + + expect(res.status).to.equal(200); + }); + it('Infer content type with charset for { body } shorthand', async () => { const response = await fixture.fetch('/food.json', { method: 'GET', diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 59a925314..e1e4f73e7 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -27,7 +27,7 @@ polyfill(globalThis, { * @typedef {Object} Fixture * @property {typeof build} build * @property {(url: string) => string} resolveUrl - * @property {(url: string, opts: any) => Promise} fetch + * @property {(url: string, opts: Parameters[1]) => Promise} fetch * @property {(path: string) => Promise} readFile * @property {(path: string, updater: (content: string) => string) => Promise} writeFile * @property {(path: string) => Promise} readdir