hono
hono copied to clipboard
Inconsistent Request Body Caching Between c.req.json() and Validator Middleware
What version of Hono are you using?
3.12.11
What runtime/platform is your app running on?
Cloudflare Workers
What steps can reproduce the bug?
I've encountered an issue with how Hono caches the request body in different formats, leading to unexpected behavior when using middlewares in a specific order.
When one middleware calls c.req.json()
, it updates bodyCache[json]
without also updating bodyCache[arrayBuffer]
.
If we later use the validator
middleware, which expects the arrayBuffer
to be cached, it results in an error for a malformed request.
app.post(
'/test',
async (c, next) => {
const body = await c.req.json();
await next();
},
validator('json', (value, c) => {
return value;
}),
async (c) => {
return c.json({ message: 'OK' }, 200);
},
);
What is the expected behavior?
The request passes through both middlewares without issues, with the validator correctly validating the parsed JSON.
What do you see instead?
Receive a 400 Bad Request error.
[ <-- POST /TEST]: undefined
✘ [ERROR] Error: Malformed JSON in request body
Additional information
The validator middleware throws an error indicating a malformed request body, despite the body being valid JSON. This seems to occur because c.req.json() does not update bodyCache[arrayBuffer], leading to a mismatch in the cached body data.
// request.ts
private cachedBody = (key: keyof Body) => {
// ...
if (bodyCache.arrayBuffer) {
return (async () => {
return await new Response(bodyCache.arrayBuffer)[key]()
})()
}
return (bodyCache[key] = raw[key]()) // doesn't update arrayBuffer
}
// validator/validator.ts
/**
* Get the arrayBuffer first, create JSON object via Response,
* and cache the arrayBuffer in the c.req.bodyCache.
*/
try {
const arrayBuffer = c.req.bodyCache.arrayBuffer ?? (await c.req.raw.arrayBuffer()) // Uses arrayBuffer
value = await new Response(arrayBuffer).json()
c.req.bodyCache.json = value
c.req.bodyCache.arrayBuffer = arrayBuffer
} catch {
console.error('Error: Malformed JSON in request body')
return c.json(
{
success: false,
message: 'Malformed JSON in request body',
},
400
)
}
@zsh-eng
Thanks. This should be fixed. I'll work on it later.
It looks like this issue is still present when reading the request body as .text()
@sam-potter
You are right. Bugs still remain.
There's no way you fixed it that fast, you're a beast 🙏 @yusukebe
@sam-potter @yusukebe Issue is still there as it treats object as promise
Custom Middlware:
app.use('*', async (c: Context, next) => {
if (c.req.method == 'POST') {
const authHeader = c.req.header('PayloadToken');
if (!authHeader) {
return c.json('Error in header');
}
const secret = c.env.HMAC_SECRET;
const payload = await c.req.json();
const calculatedHmac = CryptoJS.HmacSHA256(payload, secret);
const calculatedHmacString = calculatedHmac.toString(CryptoJS.enc.Hex);
if (calculatedHmacString != authHeader) {
return c.json('Unauthorized: Invalid HMAC token', { status: 401 });
}
}
await next();
});
route:
app.post(
'enrollmentSettings',
zValidator('json', joiningRoleSchema),
setMerchantConfig,
);
z schema:
export const joiningRoleSchema = z.object({
role: z.string(),
storeId: z.string(),
});
Error:
{
"success": false,
"error": {
"issues": [
{
"code": "invalid_type",
"expected": "object",
"received": "promise",
"path": [],
"message": "Expected object, received promise"
}
],
"name": "ZodError"
}
}
If I comment the line const payload = await c.req.json(); in the middleware, validation pass otherwise fails
@osameyy Thanks!
I noticed the same thing yesterday! I'll fix it soon.
Thanks @yusukebe Its Working now!