hono icon indicating copy to clipboard operation
hono copied to clipboard

c.req.json() throws an error if c.req.text() has been called before

Open lacolaco opened this issue 2 years ago • 5 comments

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...

  1. HonoRequest has its own clone()-like API to reduce .req.raw access.
  2. 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();
  };

lacolaco avatar Sep 23 '23 01:09 lacolaco

I have no idea whether this is a bug or a feature request.

lacolaco avatar Sep 23 '23 01:09 lacolaco

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 avatar Sep 23 '23 06:09 yusukebe

@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 avatar Sep 23 '23 06:09 lacolaco

@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 avatar Sep 23 '23 07:09 yusukebe

@yusukebe It makes sense. Thank you for the clarification!

lacolaco avatar Sep 23 '23 07:09 lacolaco