packages feed

web-mongrel2-0.0.3: src/Web/Mongrel2.hs

{-# LANGUAGE TemplateHaskell #-}

-- |
-- Module: Web.Mongrel2
-- Copyright: (c) 2011 Clint Moore
-- License: BSD-style
-- Maintainer: cmoore@wamboli.com
-- Stability: experimental
-- Portability: GHC
--
--
-- A simple abstraction for applications to use Mongrel2.
-- Mongrel2 is simple and easy to use, and hopefully others
-- find this module almost as easy.
--
-- > require Web.Mongrel2
-- > require Control.Monad (forever)
-- >
-- > main :: IO ()
-- > main = do
-- >   conn <- connect $ def { m2_publish = "tcp://127.0.0.1:9996
-- >                         , m2_pull = "tcp://127.0.0.1:9997"
-- >                         , m2_uuid = "my-awesome-webapp" }
-- >   case m2_pull_socket conn of
-- >     Nothing -> error "Didn't connect to Mongrel2!"
-- >     Just sock ->
-- >       forever $ poll sock >>=
-- >                   recv dumper conn >>
-- >                   return ()
-- >
-- >  where
-- >    dumper :: Request -> IO Response
-- >    dumper req = do
-- >      putStrLn req
-- >      return $ defaultr req
-- >

module Web.Mongrel2 (
    -- * Connection
  connect
  , poll
  , recv
    -- * Utilities
  , defaultr
  ) where

import qualified Data.Text as T
import qualified Data.Text.Encoding as TE

import Control.Applicative
import qualified Data.ByteString.Char8 as BS
import Data.Default (def)
import Data.FileEmbed (embedFile)
import System.Time (getClockTime)
import qualified System.ZMQ as Z
import Text.StringTemplate

import Web.Mongrel2.Parsing
import Web.Mongrel2.Types

-- | Generate a 'Response' from the 'Request' copying sane defaults.
defaultr :: Request -> Response
defaultr req =
  def { response_uuid = request_uuid req
      , response_id = request_id req
      , response_path = request_path req 
      , response_headers = request_headers req }

-- Request
-- UUID ID PATH SIZE:HEADERS,SIZE:BODY

-- Response
-- UUID SIZE:ID ID ID, BODY

send_response :: Z.Socket a -> Response -> IO ()
send_response sock resp = do
  now <- getClockTime
  -- TODO: Why the hell do I have to do this?
  -- TODO: Otherwise, every response is missing one character.
  let leng = (T.length $ response_body resp) + 1
                    
  let res = BS.pack $
             render $
             setAttribute "headers" (response_headers resp) $
             setManyAttrib [("id", (response_id resp)),
                            ("uuid", (response_uuid resp)),
                            ("idl", (T.pack $ show $ T.length $ response_id resp)),
                            ("now", (T.pack $ show now)),
                            ("clen", T.pack $ show leng),
                            ("sep", T.pack "\r\n"),
                            ("status", response_status resp),
                            ("contenttype", response_content_type resp),
                            ("charset", response_charset resp),
                            ("body",(response_body resp))] $
             newSTMP response_template
  Z.send sock res []

-- | The receive action.
--
recv :: (Request -> IO Response) -> M2 -> [Z.Poll] -> IO ()
recv handle pub ((Z.S s _):_ss) = do
  req <- Z.receive s []
  case m2_parse (TE.decodeUtf8 req) of
    Left err -> error $ T.unpack err
    Right rq -> do
      rsp <- handle rq
      case m2_publish_socket pub of
        Nothing -> error "Publish socket not connected?!"
        Just so -> send_response so rsp
recv _ _ _ = return ()

-- | Shortcut for @system-zeromq@'s poll function.
-- Interval is hardcoded to 1000000 ms.
poll :: Z.Socket a -> IO [Z.Poll]
poll sock =
  Z.poll [Z.S sock Z.InOut] 1000000

-- | Connects the internal sockets.
connect :: M2 -> IO M2
connect mong = do
  case ((,)
        <$> m2_publish_socket mong
        <*> m2_pull_socket mong ) of
    Just _ -> return mong
    Nothing -> do
      ctx <- Z.init 1
      pub <- Z.socket ctx Z.Pub
      pull <- Z.socket ctx Z.Pull

      let uid = case m2_uuid mong of
            Just v -> v
            Nothing -> T.pack "82209006-86GF-4982-B5EA-D1E29E55D481"

      Z.connect pull $ T.unpack $ m2_pull mong
      Z.setOption pull $ Z.Identity $ T.unpack uid

      Z.connect pub $ T.unpack $ m2_publish mong

      return $
        mong { m2_publish_socket = Just pub
             , m2_pull_socket = Just pull
             , m2_context = Just ctx
             , m2_uuid = Just uid
             }

response_template :: String
response_template = BS.unpack $(embedFile "response_template.st")