packages feed

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

{-# LANGUAGE QuasiQuotes #-}

-- |
-- 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" }
-- >   case m2_pull_socket conn of
-- >     Nothing -> error "Didn't connect to Mongrel2!"
-- >     Just sock ->
-- >       forever $ poll sock >>=
-- >                   recv dumper bx >>
-- >                   return ()
-- > 
-- >  where
-- >    dumper :: Request -> IO Response
-- >    dumper req = do
-- >      putStrLn req
-- >      return $ defaultr req
-- >

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

import Control.Applicative
import qualified Data.ByteString.Char8 as BS
import Prelude hiding (lookup)
import System.Time (getClockTime)
import qualified System.ZMQ as Z
import Text.StringTemplate

import Web.Mongrel2.Parsing
import Web.Mongrel2.QQ (qq)
import Web.Mongrel2.Types

import Data.Default (def)

-- | 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 }
  
-- 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
  Z.send sock (BS.pack $ render $
    setAttribute "headers" (response_headers resp) $
    setManyAttrib [("id", (response_id resp)),
                   ("uuid", (response_uuid resp)),
                   ("idl", (show $ length $ response_id resp)),
                   ("now", (show now)),
                   ("clen", (show $ length $ response_body resp)),
                   ("sep", "\r\n"),
                   ("status", response_status resp),
                   ("contenttype", response_content_type resp),
                   ("charset", response_charset resp),
                   ("body",(response_body resp))] $
    newSTMP response_template) []

-- | 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 (BS.unpack req) of
    Left err -> error 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 -> "82209006-86GF-4982-B5EA-D1E29E55D481"
            
      Z.connect pull $ m2_pull mong
      Z.setOption pull $ Z.Identity uid
      
      Z.connect pub $ 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 = [$qq|$uuid$ $idl$:$id$, HTTP/1.1 $status$ OK
Content-Type: $contenttype$; charset=$charset$
Connection: close
Content-Length: $clen$
Server: Mongrel2
Date: $now$
$headers:{a|$a.0$:$a.1$
}$

$body$

|]