Search code examples
httphaskellquery-stringhaskell-warp

Handling HTTP GET Query parameters in Warp


Using the warp HTTP server, I want to handle HTTP query parameters.

It's easy (see for example here) to make Warp render something for URLs like

http://localhost:3000/foo

How can I make it render

http://localhost:3000/foo?id=bar

in a way where the content is dependent on the id query parameter?

Additionally, how can I handle if there is no such parameter?

How to deliver JSON over HTTP using Warp with Aeson


Solution

  • I'll build my example on this previous answer.

    The most important module in this context it Network.HTTP.Types, specifically the Query type.

    You can get a Query from a WAI Request using QueryString.

    As a Query is nothing more than a [(ByteString, Maybe ByteString)], we can use lookup from the base library to find the appropriate attribute.

    However, as lookup wraps the type in a Maybe itself, we end up witha Maybe (Maybe ByteString). My example contains the rather ugly-sounding function maybeMaybeToMaybe to convert this to a Maybe ByteString.

    The example returns a plaintext response (on any URL) that contains the id query parameter. As it just uses show, for the example URL

    http://localhost:3000/foo?id=bar
    

    it yields

    Query parameter: Just "foobar"
    

    whereas for

    http://localhost:3000/
    

    it yields

    Query parameter: Nothing
    

    Here's the full source code:

    {-# LANGUAGE OverloadedStrings #-}
    import Control.Applicative ((<$>))
    import Control.Monad
    import Network.Wai
    import Network.Wai.Handler.Warp
    import Network.HTTP.Types (status200)
    import Network.HTTP.Types.Header (hContentType)
    import Blaze.ByteString.Builder.Char.Utf8 (fromString)
    import Data.ByteString (ByteString)
    
    main = do
        let port = 3000
        putStrLn $ "Listening on port " ++ show port
        run port app
    
    app req f = f $
        case pathInfo req of
            -- Place custom routes here
            _ -> anyRoute req
    
    anyRoute req =
        let query = queryString req :: [(ByteString, Maybe ByteString)]
            idParam = join $ lookup "id" query :: Maybe ByteString
        in responseBuilder
                status200
                [(hContentType, "text/plain")]
                $ fromString $ "Query parameter: " ++ (show idParam)