summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@skypack.dev> 2022-09-14 07:58:00 -0400
committerGravatar GitHub <noreply@github.com> 2022-09-14 07:58:00 -0400
commitcf8a7e933d26125eee44ce8b4f84d1353cfed957 (patch)
tree0fda68f870be780abb60c32b8989225be45b2bf0
parentdc05bc04b1bc0e812dbb7881bfba574e35e28087 (diff)
downloadastro-cf8a7e933d26125eee44ce8b4f84d1353cfed957.tar.gz
astro-cf8a7e933d26125eee44ce8b4f84d1353cfed957.tar.zst
astro-cf8a7e933d26125eee44ce8b4f84d1353cfed957.zip
Properly handle multipart file uploads in the dev server (#4742)
* Properly allow file uploads in the dev server * Smaller image * movethe test over
-rw-r--r--.changeset/polite-melons-pump.md5
-rw-r--r--packages/astro/src/vite-plugin-astro-server/index.ts9
-rw-r--r--packages/astro/test/api-routes.test.js3
-rw-r--r--packages/astro/test/fixtures/api-routes/package.json3
-rw-r--r--packages/astro/test/fixtures/ssr-api-route/src/images/penguin.jpgbin0 -> 7231 bytes
-rw-r--r--packages/astro/test/fixtures/ssr-api-route/src/pages/binary.js32
-rw-r--r--packages/astro/test/ssr-api-route.test.js15
-rw-r--r--packages/astro/test/test-utils.js2
8 files changed, 64 insertions, 5 deletions
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 000000000..ae4daa548
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-api-route/src/images/penguin.jpg
Binary files differ
diff --git a/packages/astro/test/fixtures/ssr-api-route/src/pages/binary.js b/packages/astro/test/fixtures/ssr-api-route/src/pages/binary.js
new file mode 100644
index 000000000..3e1c70c81
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-api-route/src/pages/binary.js
@@ -0,0 +1,32 @@
+import fs from 'node:fs';
+
+export function get() {
+ return {
+ body: 'ok'
+ };
+}
+
+export async function post({ request }) {
+ const data = await request.formData();
+ const file = data.get('file');
+
+ if (file) {
+ const buffer = await file.arrayBuffer();
+ const realBuffer = await fs.promises.readFile(new URL('../images/penguin.jpg', import.meta.url));
+
+ if(buffersEqual(buffer, realBuffer)) {
+ return new Response('ok', { status: 200 });
+ }
+ }
+ return new Response(null, { status: 400 });
+}
+
+function buffersEqual(buf1, buf2) {
+ if (buf1.byteLength != buf2.byteLength) return false;
+ const dv1 = new Uint8Array(buf1);
+ const dv2 = new Uint8Array(buf2);
+ for (let i = 0; i !== buf1.byteLength; i++) {
+ if (dv1[i] != dv2[i]) return false;
+ }
+ return true;
+}
diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js
index 8b007391d..5394ead51 100644
--- a/packages/astro/test/ssr-api-route.test.js
+++ b/packages/astro/test/ssr-api-route.test.js
@@ -1,6 +1,7 @@
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js';
+import { FormData, File } from 'node-fetch';
describe('API routes in SSR', () => {
/** @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<Response>} fetch
+ * @property {(url: string, opts: Parameters<typeof fetch>[1]) => Promise<Response>} fetch
* @property {(path: string) => Promise<string>} readFile
* @property {(path: string, updater: (content: string) => string) => Promise<void>} writeFile
* @property {(path: string) => Promise<string[]>} readdir