packages feed

hack2-handler-mongrel2-http-2011.6.22: src/Hack2/Handler/Mongrel2.hs

-- adopted from Bardur Arantsson's MIT mongrel2-handler


{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}



module Hack2.Handler.Mongrel2
       ( 
         mkHandler
       , withConnectedHandler
       , receiveRequest
       , sendResponse
       -- Re-exported
       , module Hack2.Handler.Mongrel2.Types
       ) where

import Blaze.ByteString.Builder (Builder,fromByteString)
import Data.Attoparsec
import Hack2.Handler.Mongrel2.Parser (messageParser)
import Hack2.Handler.Mongrel2.Response
import Hack2.Handler.Mongrel2.Types
import qualified System.ZMQ as ZMQ
import Data.ByteString.Char8 (ByteString, unpack)
import Control.Monad (forM_)
import qualified Data.ByteString.Char8 as B

import Data.Enumerator
import Air.Env
import Prelude ()


-- | Create a new handler. @mkHandler pullFromAddress publishToAddress id@
-- creates a handler which pulls requests from @fromAddress@ and publishes
-- replies to @publishAddress@.
mkHandler :: String -> String -> Maybe String -> Handler
mkHandler = Handler

-- | Run an IO action with a connected handler.
withConnectedHandler :: Handler -> (ConnectedHandler -> IO a) -> IO a
withConnectedHandler h io =
  ZMQ.withContext 1 - \c ->
    ZMQ.withSocket c ZMQ.Pull - \s -> do
      ZMQ.withSocket c ZMQ.Pub - \ps -> do
        -- Connect to the poll socket.
        ZMQ.connect s - handlerPullFrom h
        -- Bind to publish socket.
        ZMQ.connect ps - handlerPublishTo h
        case handlerId h of
          Nothing -> return ()
          Just uuid -> ZMQ.setOption ps - ZMQ.Identity uuid
        -- Run the action with the connected handler.
        io - ConnectedHandler s ps

-- | Receive a parsed request from the Mongrel2 server. Blocks until
-- a message is received.
receiveRequest :: ConnectedHandler -> IO Request
receiveRequest handler = do
      msg <- ZMQ.receive (chPullSocket handler) []
      
      -- putStrLn - "handler received: " + unpack msg
      
      -- Parse into a structured message.
      case parseOnly messageParser msg of
        Left errMsg ->
          -- Invalid message. This can only happen if there's
          -- an error in our parsing code or Mongrel2 itself.
          fail - "Couldn't parse message: " + (show errMsg)
        Right m -> do
          -- Return the parsed message.
          return m

sendResponse :: ConnectedHandler -> Response -> IO ()
sendResponse handler response = 
  run_ - _responseEnum $$ _send_iteratee
  
  where
      uuid             = responseUuid response
      clientId         = responseClientId response
      _responseEnum    = responseBody response
      _mk_response msg = mkResponse uuid clientId msg
      
      _socket          = chPublishSocket handler

      _send msg        = ZMQ.send _socket (_mk_response msg) [] -- [ZMQ.SndMore]
      _done            = ZMQ.send _socket (_mk_response "") []
  
      -- head :: Monad m => Iteratee B.ByteString m (Maybe Word8)
      -- head = continue loop where
      --  loop (Chunks xs) = case BL.uncons (BL.fromChunks xs) of
      --    Just (char, extra) -> yield (Just char) (toChunks extra)
      --    Nothing -> head
      --  loop EOF = yield Nothing EOF
      --  
      
      _send_iteratee :: Iteratee ByteString IO ()
      _send_iteratee = continue loop where
        loop (Chunks xs) = do
          io - xs.mapM_ (\msg -> do
            -- puts - "send msg: " + msg.unpack
            _send msg
            )
          _send_iteratee
          
        loop EOF = do
          -- io - puts - "done"
          io - _done
          yield () EOF
  
  -- let combined = B.append _responseHeader _responseBody
  -- putStrLn - "message: " + unpack combined
  
  -- ZMQ.send _socket combined []