How to rebuild the request body after reading it in WAI middleware

I’m trying to rebuild a consumed request body in a WAI middleware.

When I call

getRequestBodyChunk :: Request -> IO ByteString

the body content is consumed, so the downstream application receives an empty body. To forward the original request body again, it needs to be preserved and then restored before passing the request onward.

I found that WAI provides this function:

setRequestBodyChunks :: IO ByteString -> Request -> Request

which seems to allow restoring the body. However, I’m not sure how to construct the required IO ByteString argument.

The documentation says:

The supplied IO action should return the next chunk of the body each time it is called and empty when it has been fully consumed.

Could someone show an example of how to implement such an action, or explain the recommended approach for reusing the request body within middleware?

Thanks!

1 Like

Something like this:

  bodyChunksRef <- newIORef []
  ...
  chunk <- getRequestBodyChunk request
  modifyIORef' bodyChunksRef (chunk :)
  ... 
  modifyIORef' bodyChunksRef reverse
  let request' = flip setRequestBodyChunks request $ do
    chunks <- readIORef bodyChunksRef
    case chunks of
      [] -> pure mempty
      (chunk:rest) -> do
        writeIORef bodyChunksRef rest
        pure chunk

Essentially, instead of reading a body chunk from the network, we’re reading it from a mutable variable. You’ll want to track the body chunks as you get them. It’s a bit easier to just prepend the chunks as they come in, and then reverse them before passing them downstream.

If you need to pass the partial body along, you’ll want to use something like Data.Sequence instead, and you’ll need to think about concurrency.

1 Like

Thank you for your help! Using the hint from your code, I was able to solve it as follows:

makeAction :: L.ByteString -> IO (IO B.ByteString)
makeAction bs = do
  let chunks = L.toChunks bs
  ref <- newIORef chunks
  return $ do
    chunk <- readIORef ref
    case chunk of
      [] -> return B.empty
      (x:xs) -> do
        writeIORef ref xs
        return x

I really appreciate it!

2 Likes