stop parsing request bodies on sidecar proxy routes
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>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
// Which public path prefixes are served by `createSidecarProxy` — routes whose body the platform only
|
||||
// ever forwards and never reads.
|
||||
//
|
||||
// This exists so `bodyParser` can leave those request streams untouched. Parsing a body that is merely
|
||||
// passing through is not just wasted work; for `multipart/form-data` it is corrupting. Hono caches the
|
||||
// parsed `FormData` on the request, so when the proxy later asks for the bytes Hono re-serialises them
|
||||
// from that cache — minting a NEW multipart boundary, while the proxy still forwards the ORIGINAL
|
||||
// `content-type` header naming the old one. Header and body then disagree and the far side rejects every
|
||||
// upload with "Multipart: Unexpected end of form".
|
||||
//
|
||||
// Registered by `createSidecarProxy` itself rather than hand-listed here, so a new sidecar cannot forget
|
||||
// to add itself and rediscover the same bug.
|
||||
|
||||
const proxiedPrefixes = new Set<string>();
|
||||
|
||||
export const registerProxiedPrefix = (prefix: string): void => {
|
||||
proxiedPrefixes.add(prefix);
|
||||
};
|
||||
|
||||
/** True when `path` is served by a sidecar proxy. Matches on a path boundary: `/api/memos` never matches
|
||||
* `/api/memoserver`. */
|
||||
export function isProxiedPath(path: string): boolean {
|
||||
for (const prefix of proxiedPrefixes) {
|
||||
if (path === prefix || path.startsWith(`${prefix}/`)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user