every multipart upload through /api/<sidecar>/* arrived corrupted. bodyParser ran on
proxy routes and called parseBody for multipart, so hono cached a FormData on the
request; when the proxy then asked for the bytes hono re-serialised them from that
cache with a NEW boundary, while the proxy still forwarded the ORIGINAL content-type
header. header and body disagreed and the far side rejected it with
"Multipart: Unexpected end of form".
bodyParser now skips prefixes owned by createSidecarProxy, which register themselves
so a new sidecar cannot forget. the proxy also forwards the body as a stream instead
of buffering it, which drops the second in-memory copy of every upload.
note the bug report proposed skipping multipart in bodyParser outright; that would
have broken /upload, /file-browser upload and /bug-report, which do read a multipart
body from ctx.get('body').
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
import type { MiddlewareHandler } from 'hono';
|
|
import * as errors from '../custom-errors';
|
|
import { isProxiedPath } from '../sidecar/proxied-prefixes';
|
|
|
|
export const bodyParser: () => MiddlewareHandler = () => async (ctx, next) => {
|
|
// Sidecar proxy routes: the body is forwarded verbatim and never read here. Consuming it would be
|
|
// pointless for JSON and destructive for multipart — see sidecar/proxied-prefixes.ts for why a parsed
|
|
// multipart body cannot be forwarded intact.
|
|
if (isProxiedPath(ctx.req.path)) {
|
|
ctx.set('body', {});
|
|
return next();
|
|
}
|
|
|
|
if (!['POST', 'PUT', 'PATCH'].includes(ctx.req.method.toUpperCase())) {
|
|
ctx.set('body', {});
|
|
return next();
|
|
}
|
|
|
|
const contentType = ctx.req.header('Content-Type') || '';
|
|
|
|
try {
|
|
if (contentType.includes('application/json')) {
|
|
const text = await ctx.req.text();
|
|
const body = text ? JSON.parse(text) : {};
|
|
ctx.set('body', body);
|
|
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
const body = await ctx.req.parseBody();
|
|
ctx.set('body', body);
|
|
} else if (contentType.includes('multipart/form-data')) {
|
|
const body = await ctx.req.parseBody({ all: true });
|
|
ctx.set('body', body);
|
|
} else {
|
|
// No recognized content type, set empty body
|
|
ctx.set('body', {});
|
|
}
|
|
} catch {
|
|
// Previously swallowed, which left `body` unset — handlers then destructured undefined and the
|
|
// client got a 500 for what is squarely a malformed request.
|
|
throw errors.BAD_REQUEST('Invalid request body');
|
|
}
|
|
|
|
return next();
|
|
};
|