2017-10-15
I have recently released a new Haskell package called servant-rawm. It allows embedding a raw WAI Application inside a Servant API.
How does it work?
Below I will give a simple example of how servant-rawm can be used.
Imagine we have an API that looks like the following:
This represents a Servant API that can be accessed at http://localhost/serve-directory-example/ and does something. RawM represents any possible server that can be defined as a WAI Application. Without looking at the actual server definition, we are not sure what it does, so let's take a look at the server definition:
import Control.Monad.Reader (ReaderT, ask)
import Network.Wai (Application)
import Servant (ServerT)
import Servant.RawM (serveDirectoryWebApp)
serverRoot :: ServerT Api (ReaderT FilePath IO)
serverRoot = rawEndpoint
rawEndpoint :: ReaderT FilePath IO Application
rawEndpoint = do
filePath <- ask
serveDirectoryWebApp filePathserverRoot is our main top-level Servant server. Since there is only one endpoint defined in Api, there is only one endpoint in the body of serverRoot: rawEndpoint.
rawEndpoint is a fileserver. It uses serveDirectoryWebApp to serve files from of a given directory. Note that the directory is obtained from the ReaderT monad.
serverRoot can be run with the following two functions:
import Contorl.Monad.Reader (ReaderT, runReaderT)
import Control.Monad.IO.Class (liftIO)
import Data.Proxy (Proxy(Proxy))
import Network.Wai (Application)
import Network.Wai.Handler.Warp (run)
import Servant (Handler, Serve, serve)
import Servant.Utils.Enter ((:~>)(NT), enter)
app :: Application
app = serve (Proxy :: Proxy Api) apiServer
where
apiServer :: Server Api
apiServer = enter (NT trans) serverRoot
trans :: ReaderT FilePath IO a -> Handler a
trans readerT = liftIO $ runReaderT readerT "./some/path/"
main :: IO ()
main = do
putStrLn "example server running on port 8421"
run 8421 $ app configapp transforms our serverRoot Servant API into a WAI Application (which happens to also contain the Application from rawEndpoint). This uses the enter machinery provided by Servant.1
main uses Warp's run function to run app.
We can access this server with curl. Assuming the server has been run in the current directory, and the file ./some/path/foo.txt exists, it can be fetched like the following:
How is RawM different from Servant's Raw type?
RawM from servant-rawm is similar to, but more powerful than, Raw provided by Servant.
Here is what the code would look like if we were using Raw instead of RawM:
import Control.Monad.Reader (ReaderT)
import Data.Tagged (Tagged(Tagged))
import Network.Wai (Application)
import Servant ((:>), Raw, ServerT)
type Api' = "serve-directory-example" :> Raw
serverRoot' :: ServerT Api' (ReaderT FilePath IO)
serverRoot' = rawEndpoint'
rawEndpoint' :: Tagged (ReaderT FilePath IO) Application
rawEndpoint' = Tagged $ ...If you look at the body of the rawEndpoint' function, you can see that it is using the Tagged data constructor, which ends up with a type like the following type:
Notice that Tagged takes an Application and not a ReaderT Filepath IO Application. This means that the ReaderT cannot be used to create the Application.
Contrast the type of rawEndpoint from the previous section with the type of rawEndpoint':
rawEndpoint :: ReaderT FilePath IO Application
rawEndpoint' :: Tagged (ReaderT FilePath IO) ApplicationYou can see that RawM is strictly more powerful than Raw, because it allows the Application to be created monadically.
Client and Documentation
servant-rawm also provides two additional instances for RawM: HasClient (for use with servant-client) and HasDocs (for use with servant-docs).
More information can be found in the client example and the docs example in the source repository.
Conclusion
servant-rawm should be used whenever you need to use monadic effects to create an Application in a Servant API.
Footnotes
Take a look at the Servant tutorial for an explanation of how
enterworks. This blog post is based on servant-0.11. Future versions of Servant will do away withenter. However,RawMwill still work in a similar fashion. See this PR for more information.↩︎
tags: haskell