Properly handle multipart file uploads in the dev server (#4742)
* Properly allow file uploads in the dev server * Smaller image * movethe test over
This commit is contained in:
parent
dc05bc04b1
commit
cf8a7e933d
8 changed files with 64 additions and 5 deletions
5
.changeset/polite-melons-pump.md
Normal file
5
.changeset/polite-melons-pump.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Allow file uploads in dev server
|
|
@ -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.
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -4,5 +4,8 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "astro dev"
|
||||
}
|
||||
}
|
||||
|
|
BIN
packages/astro/test/fixtures/ssr-api-route/src/images/penguin.jpg
vendored
Normal file
BIN
packages/astro/test/fixtures/ssr-api-route/src/images/penguin.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
32
packages/astro/test/fixtures/ssr-api-route/src/pages/binary.js
vendored
Normal file
32
packages/astro/test/fixtures/ssr-api-route/src/pages/binary.js
vendored
Normal file
|
@ -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;
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue