How to consume a conduit with WAI (raw request body)

1.2k views Asked by At

I'm using scotty, which is a sinatra-like wrapper around WAI. I want to get the raw request body as a byte string so I can parse it as json. The following is close. This is similar to other questions about consuming a body using WAI, but is different because I want the body as a bytestring, and because I'm in a different monad, ActionM

import Network.Wai (requestBody)
import Web.Scotty (ActionM, request, text)

bodyExample :: ActionM ()
bodyExample = do
    r <- request
    bss <- requestBody r -- this needs a lift or something
    text "ok"
    ...

It obviously won't work, I think I need some kind of lift or something, but I don't know what to use. liftIO isn't right, and lift gives me weird errors.

http://hackage.haskell.org/packages/archive/scotty/0.0.1/doc/html/Web-Scotty.html

http://hackage.haskell.org/packages/archive/wai/latest/doc/html/Network-Wai.html

4

There are 4 answers

0
dflemstr On BEST ANSWER

requestBody isn't a monadic value. It is simply a function that returns a Conduit Source IO ByteString.

To consume the source, use Data.Conduit.List.consume (or Data.Conduit.Lazy.lazyConsume). You will get a list of ByteStrings as the result. To then exit the ResourceT monad transformer, use runResourceT. The resulting code:

bss <- liftIO . runResourceT . lazyConsume . requestBody $ r
bss :: [ByteString]
0
airportyh On

This is the code that finally work for me, adapted from jhickner's

rawRequestBody req = mconcat <$> (requestBody req $$ consume)
0
jhickner On

The accepted answer won't actually work due to the way lazyConsume works. It will always return an empty list. You need to consume the data before exiting ResourceT if you're using lazyConsume.

As an alternative, here's how to strictly consume the bytestring and return it:

rawRequestBody :: Request -> IO B.ByteString
rawRequestBody req = mconcat <$> runResourceT (requestBody req $$ consume)
0
Andrew Farmer On

For what it's worth, the new version of Scotty (0.2.0) has a 'jsonData' method to do this for you. Thanks for using!