Consider this example server:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
import Servant
import Servant.API
import Network.Wai
import Network.Wai.Handler.Warp
import Data.IORef
import qualified Data.ByteString.Char8 as C
type API = "one" :> Get '[PlainText] String :<|> "two" :> Get '[PlainText] String
api :: Proxy API
api = Proxy
server :: Server API
server = return "1\n" :<|> return "2\n"
app :: Application
app = serve api server
main = run 3000 app
The app value is a WAI Application, defined by:
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
Conceptually, an Application accepts a Request, and invokes a callback on its Response. WAI supports the concept of Middleware which can wrap an application, allowing preprocessing of every request and post-processing of every response in the IO monad:
type Middleware = Application -> Application
So, you can write a piece of middleware to update an IORef on every received request like so:
counter :: IORef Int -> Middleware
counter cref = convert
where
convert :: Application -> Application -- AKA Middleware
convert oldapp = newapp
where
newapp :: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived -- AKA Application
newapp req respond = do
n <- atomicModifyIORef cref (\n' -> (n'+1,n'+1))
putStrLn $ "Request #" ++ show n ++ ": " ++ showRequest req
oldapp req respond
showRequest req = C.unpack (requestMethod req) ++ " " ++ C.unpack (rawPathInfo req)
I've broken this up into separate functions to make it clear how the middleware is constructed piece by piece, but the definition of counter can be simplified to the equivalent:
counter :: IORef Int -> Middleware
counter cref oldapp req respond
= do n <- atomicModifyIORef cref (\n' -> (n'+1,n'+1))
putStrLn $ "Request #" ++ show n ++ ": " ++ showRequest req
oldapp req respond
where
showRequest req = C.unpack (requestMethod req) ++ " " ++ C.unpack (rawPathInfo req)
Now, all you need to do is wrap your app with the middleware in main:
main = do
cref <- newIORef (0 :: Int)
run 3000 $ counter cref app
Full code:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
import Servant
import Servant.API
import Network.Wai
import Network.Wai.Handler.Warp
import Data.IORef
import qualified Data.ByteString.Char8 as C
type API = "one" :> Get '[PlainText] String :<|> "two" :> Get '[PlainText] String
api :: Proxy API
api = Proxy
server :: Server API
server = return "1\n" :<|> return "2\n"
app :: Application
app = serve api server
counter :: IORef Int -> Middleware
counter cref oldapp req respond
= do n <- atomicModifyIORef cref (\n' -> (n'+1,n'+1))
putStrLn $ "Request #" ++ show n ++ ": " ++ showRequest req
oldapp req respond
where
showRequest req = C.unpack (requestMethod req) ++ " " ++ C.unpack (rawPathInfo req)
main = do
cref <- newIORef (0 :: Int)
run 3000 $ counter cref app