haskellservant

How to use IO in a raw serving handler?


I use servant 0.19, so no the type RawM. I have a handler:

hnd :: Something -> ServerT Raw Handler
hnd Something {..} = do
  -- p <- readIORef somePath -- COULDN'T MATCH TYPE "IO b0" WITH "Tagged Handler Application"
  let stSet = defaultFileServerSettings "a/b/c"
  serveDirectoryWith stSet

which is described as an API's type: :<|> "some" :> Raw

I want to do IO operations in my hnd handler. How to do it with this servant version (without RawM)? Also, I'd be grateful if you show it with RawM too (though it is not important due to the used version 0.19).


Solution

  • Short answer. Try:

    hnd :: Something -> ServerT Raw Handler
    hnd Something{..} = Tagged $ \req resp -> do
      p <- readIORef somePath
      let stSet = defaultFileServerSettings p
      staticApp stSet req resp
    

    The longer answer is that ServerT is a type family associated with the HasServer type class that maps API expressions to transformations of the Handler monad to implement the API :> and :<|> combinators as well as individual endpoints. For a "normal" endpoint, like Get, it maps the type ServerT (Get ...) Handler to plain old Handler.

    To implement Raw, ServerT Raw Handler is mapped to Tagged Handler Application. This is simply a WAI Application whose type has been Tagged with the Handler monad.

    Servant-supplied wrappers, like serveDirectoryWith, are really just WAI Applications that have been Tagged:

    serveDirectoryWith :: StaticSettings -> ServerT Raw m
    serveDirectoryWith = Tagged . staticApp
    

    where staticApp is the underlying static file serving Application from the wai-app-static package.

    So, that means that the simplified handler:

    hnd :: Something -> ServerT Raw Handler
    hnd _ = serveDirectoryWith "a/b/c"
    

    is equivalent to:

    hnd _ = Tagged (staticApp "a/b/c")
    

    and given that an Application is actually a Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived, this is the same as:

    hnd _ = Tagged (\req resp -> staticApp "a/b/c" req resp)
    

    or:

    hnd Something{..} = Tagged $ \req resp -> do
      staticApp "a/b/c" req resp
    

    where the do-block is running in the plain old IO monad. In this form, it's now possible to do "normal" IO stuff, which is how I derived the short answer above.

    RawM from version 0.20 doesn't gain you much here. It maps ServantT RawM Handler to a modified version of an application:

    Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived
                                                    ^^^^^^^
                                                    instead of IO
    

    which adds the ability to perform Handler actions besides IO, namely signaling a specific ServerError. If you had a custom HandlerT, it could be more useful. It wouldn't make it any easier to write your hnd, though.