How do I read the request body as JSON?

I’m having trouble reading the request body in my worker. I’ve found a few options, but none seems to have the trifecta of being able to run in production, in the online simulator, and locally in Jest tests.

Option 1: body.json()

The request docs say that request.body has a json() method that returns a promise. And service-worker-mock supports body.json(), which makes it easy to test workers locally. But it doesn’t work in the online editor. body.json is undefined.

Option 2: getReader()

If I use request.body.getReader().read() I can get the body, but it is consumed and can’t be read again. Specifically, I can’t pass that request on to the origin.

If I use request.body.tee(), I get two readers – one that I can consume in my worker and one that I can pass on to the origin. This too consumes the original request.body. My first attempt was this:

const [ body1, body2 ] = request.body.tee();
request.body = body1;
const { value: bodyIntArray } = await body2.getReader().read()
const bodyJSON = new TextDecoder().decode(bodyIntArray)
return JSON.parse(bodyJSON)

Unfortunately, the request is immutable, so request.body = body1 throws an exception. Nor does this workaround seem to work:

request = new Request(request, { body: body1 })

Any suggestions?

Oh! Request is a Body, not has a. (Though request.body does exists, it’s not a Body; it’s a ReadableStream)

So request.json() does in fact, work, but it also consumes the request body so it can’t be sent upstream. This throws TypeError: This ReadableStream is disturbed (has already been read from), and cannot be used as a body.:

addEventListener('fetch', event => {
  event.respondWith(handle(event))
})

async function handle(event) {
  const json = await event.request.json()
  console.log(json)
  return fetch('https://httpbin.org/post', event.request)
}

But this works:

addEventListener('fetch', event => {
  event.respondWith(handle(event))
})

async function handle(event) {
  const json = await event.request.json()
  console.log(json)
  const request = new Request(event.request, { body: JSON.stringify(json) })
  return fetch('https://httpbin.org/post', request)
}

It makes the JSON available locally within the worker and also passes the JSON and headers through to the origin.

1 Like

Hi @jamesr,

To read a request body twice, you can use the Request.clone() method (which uses ReadableStream.tee() under the hood). The same method can be found on responses.

addEventListener('fetch', event => {
  event.respondWith(handle(event))
})

async function handle(event) {
  const json = await event.request.clone().json()
  console.log(json)
  return fetch('https://httpbin.org/post', event.request)
}
1 Like

Great point! I had tried .clone() earlier for some other use-cases, but dismissed it because I wanted to be able to modify the result. But this is a perfect use!