3

I want to create a high-performance HTTP-based API running on Haskell using warp as a HTTP backend.

The server shall return JSON data upon request. This data shall be serialized by using Aeson

However, warp requires a response object whereas Aeson returns lazy ByteStrings.

How can I tie both libraries together? For this question's scope I'm not interested in query parsing or routing, but in an example of how to tie both libraries together to deliver a correct JSON with correct headers.

Note: This question intentionally does not show any research effort, as it was answered Q&A-style-ish. See my answer if you require research effort.

Uli Köhler
  • 13,012
  • 16
  • 70
  • 120

1 Answers1

5

I'll build my example on the HaskellWiki minimal warp example.

For the sake of simplicity I removed any code like routing, replacing the most relevant parts by stubs and comments where to place what.

The JSON data we will serialize in this example is the list ["a","b","c"]. The same response (= the JSON) will be returned for any URL.

The issue in connecting both libraries is that while warp requires a Blaze Builder to build its response properly, while Aeson returns (as you said) a lazy ByteString. The appropriate function to connect both together is called fromLazyByteString.

{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Text (Text)
import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Header (hContentType)
import Blaze.ByteString.Builder.ByteString (fromLazyByteString)
import qualified Data.ByteString.UTF8 as BU

main = do
    let port = 3000
    putStrLn $ "Listening on port " ++ show port
    run port app

app :: Application
app req f = f $
    case pathInfo req of
        -- Place custom routes here
        _ -> anyRoute

-- The data that will be converted to JSON
jsonData = ["a","b","c"] :: [Text]

anyRoute = responseLBS
            status200
            [(hContentType, "application/json")]
            (encode jsonData)

Update 05/01/2015: Fix example for Warp/WAI 3.x

Uli Köhler
  • 13,012
  • 16
  • 70
  • 120
  • Note that wai provides a `responseLBS` function, which automatically adds the `fromLazyByteString` for you. It's also preferable to stick with the constructors in `Network.Wai` so you can avoid importing the `.Internal` module. – Michael Snoyman Mar 05 '14 at 06:51
  • @MichaelSnoyman Thanks for noting that! I edited the code above to use `responseLBS` – Uli Köhler Mar 05 '14 at 16:11
  • @UliKöhler I get an error on the line `app req = return $` that says `Couldn't match type ‘Response’ with ‘IO ResponseReceived’ Expected type: Application Actual type: Request` Changing it to this works: `app req respond = respond $` – Calvin Cheng Jan 05 '15 at 02:30
  • @CalvinCheng I believe this is related to API changes in WARP 3.x. I'll look into that within the next few days! – Uli Köhler Jan 05 '15 at 05:04
  • Because `Application` type expects a `request` argument as well as a `respond` argument? Reference - http://www.stackage.org/haddock/nightly-2015-01-04/wai-3.0.2.1/Network-Wai.html#t:Application – Calvin Cheng Jan 05 '15 at 05:16
  • @CalvinCheng Basically the WAI maintainers changed it so instead of using `return`, you get a function `f` that you have to apply to the result. I edited that in the answer, it should work now. Please feel free to inform me in case you encounter any further issues. – Uli Köhler Jan 05 '15 at 16:20