c.req.json() throws an error if c.req.text() has been called before
What version of Hono are you using?
3.7.1
What runtime/platform is your app running on?
Node.js, Cloudflare Workers
What steps can reproduce the bug?
https://github.com/lacolaco/hono-body-reuse-issue-repro/blob/main/index.js
- Read
c.req.text() - Then, read
c.req.json() - It throws an error
TypeError: Body is unusable
json() and text() can be swapped. And it is no problem when reading the body by the same method like text() -> text().
What is the expected behavior?
I read the past release note and expected the body content to be cached. But that is wrong.
https://github.com/honojs/hono/pull/1333
That behavior, reading the body via different methods is not cached and throws the error, is acceptable to me. But I hope that the use case is supported in another way.
Currently, I can avoid the error by using c.req.raw.clone().text() but this is verbose. So I propose...
- HonoRequest has its own
clone()-like API to reduce.req.rawaccess. - Something like
c.req.text({clone: true})? (Honestly, I don't think this is better)
Anyway, my thought is just "c.req.raw.clone().text() is verbose".
What do you see instead?
No response
Additional information
My use case is the verification of HTTP header signatures to implement Discord bot interactions. It requires reading the request body.
https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization
const verifyKeyMiddleware =
(): MiddlewareHandler<{ Bindings: Env }> => async (c, next) => {
const signature = c.req.header('X-Signature-Ed25519');
const timestamp = c.req.header('X-Signature-Timestamp');
const body = await c.req.raw.clone().text();
const isValidRequest =
signature &&
timestamp &&
verifyKey(body, signature, timestamp, c.env.DISCORD_PUBLIC_KEY);
if (!isValidRequest) {
console.log('Invalid request signature');
return c.text('Bad request signature', 401);
}
return await next();
};
I have no idea whether this is a bug or a feature request.
Hi @lacolaco
I have no idea whether this is a bug or a feature request.
It is indeed hard to know if this is a bug or not, but this is not a bug, but expected behavior.
This is the trouble with the Web API Request object: once the body of the Request object is used in json() or text(), it cannot be used again. So, as you showed, you have to clone() it.
const req = new Request('http://localhost', {
method: 'POST',
body: JSON.stringify({ foo: 'bar' }),
})
console.log(await req.json())
console.log(await req.text()) // Errror!
HonoRequest uses the Request method as is. (But, the same format, i.e. c.text(), will cache text, and the Validator will cache arrayBuffer to use it later.) So you need to clone().
Of course, we can cache the result of c.text() or c.json() and use it again, but it will cause performance degradation. So, we keep this implementation, please use clone(), although it is verbose for now.
It might be nice to have an interface like c.req.text({ cache: true }) for example. So, let's make this issue a feature request and keep it open.
@yusukebe Thanks! How do you feel about HonoRequest#clone(): HonoRequest, which allows cloning the request without req.raw access?
const cloned: HonoRequest = c.req.clone();
const body = await cloned.text();
@lacolaco
That is neat as an API, but I don't want to do that because I think we would have to create clone() for cloning “HonoRequest".
Although, I read your article and it is good to have a method like c.text({ cache: true }) to clone it “intentionally" within middleware.
@yusukebe It makes sense. Thank you for the clarification!