haskellhappstack

backend serving currenttime (in json) in haskell


I want to write a simple webserver in haskell which provides the current time. The time should be returned in json format.

Here is what I have so far:

{-# LANGUAGE DeriveDataTypeable #-}

import Happstack.Server
import Text.JSON.Generic
import Data.Time
import System.IO.Unsafe

data TimeStr = TimeStr {time :: String} deriving (Data, Typeable)

main = simpleHTTP nullConf $ ok $ toResponse $ encodeJSON (TimeStr $ show (unsafePerformIO getCurrentTime)) 

I am aware that unsafePerformIO should be avoided, yet I could not find a better solution yet. Maybe this is where the problem lies? I have a very basic understanding of monads.

The result is the following:

{"time":"2014-10-16 16:11:38.834251 UTC"}

The problem is that when I refresh localhost:8000 the time doesn't change. Is there some sort of memoization going on?


Solution

  • unsafePerformIO :: IO a -> a
    

    This is the "back door" into the IO monad, allowing IO computation to be performed at any time. For this to be safe, the IO computation should be free of side effects and independent of its environment.

    getCurrentTime is dependent on its environment, so unsafePerformIO is not the way to go. However, given a MonadIO, we can use liftIO in order to lift the action into the appropriate monad. Lets have a look at the types to find out where we can plug it in:

    -- http://hackage.haskell.org/package/happstack-server-7.3.9/docs/Happstack-Server-SimpleHTTP.html
    simpleHTTP :: ToMessage a => Conf -> ServerPartT IO a -> IO () 
    

    ServerPartT is an instance of MonadIO, so we could definitely plug it in here. Lets check ok:

    ok :: FilterMonad Response m => a -> m a 
    --                       nope: ^^^
    

    So we really need to get the current time before we prepare the response. After all, this makes sense: when you create the response, all heavy work has been done, you know what response code you can use and you don't need to check whether the file or entry in the database exists. After all, you were going to sent an 200 OK, right?

    This leaves us with the following solution:

    {-# LANGUAGE DeriveDataTypeable #-}
    
    import Happstack.Server
    import Text.JSON.Generic
    import Data.Time
    import System.IO.Unsafe
    import Control.Monad.IO.Class (liftIO)
    
    data TimeStr = TimeStr {time :: String} deriving (Data, Typeable)
    
    main = simpleHTTP nullConf $ do
               currentTime <- liftIO getCurrentTime
               ok $ toResponse $ encodeJSON (TimeStr $ show currentTime)
    

    Lessons learned

    1. Don't use unsafePerformIO.
    2. Don't use unsafePerformIO, unless you're really sure what you're actually doing.
    3. Use liftIO if you want to use an IO action in an instance of MonadIO.