{-# OPTIONS_HADDOCK ignore-exports #-} -- |The Server module of the PupEvents framework is designed to be run from -- the file that specifies the Events that you are handling. The only thing -- that would need to be written to use this is the Events file, it does -- not depend on the application code. module PupEventsServer (server) where import GHC.IO.Handle import Control.Concurrent.STM import Control.Concurrent.STM.TChan (tryReadTChan) import Control.Concurrent import Control.Monad import Control.Exception.Base (finally) import qualified Control.Exception as C import Text.Parsec import Network.Socket import System.IO import PupEventsPQueue import Data.Functor.Identity -- Make a socket, communication channels and start listening for connections -- |The main entry point of the program. This ends by forever calling 'acceptCon'. server :: Maybe [Char] -- ^ The address to listen on, if it's not given default to "0.0.0.0". -> Int -- ^ The number of priority levels to create the PQueue with -> String -- ^ The port to start on -> (t -> Int) -- ^ The function to look up the priority level of an event -> (t1 -> t1 -> String) -- ^ The lookup function to convert an event to a string representation -> (t -> t -> IO t1) -- ^ The lookup function to look up the handler for an event -> [ParsecT [Char] () Data.Functor.Identity.Identity t] -- ^ The list of parsers to try and parse events with -> Maybe t -- ^Optional. The event to put on the pqueue when a client disconnects -> IO b server Nothing priorities port lookupPriority lookupUnHandler lookupHandler parsers dcEvent = server (Just "0.0.0.0") priorities port lookupPriority lookupUnHandler lookupHandler parsers dcEvent server ip priorities port lookupPriority lookupUnHandler lookupHandler parsers dcEvent = do -- get port addrinfos <- getAddrInfo (Just (defaultHints {addrFlags = [AI_PASSIVE]})) ip (Just port) let serveraddr = head addrinfos -- create socket sock <- socket (addrFamily serveraddr) Stream 6 -- bind to the address we're listening on bindSocket sock (addrAddress serveraddr) -- listen with maximum 5 queued requests listen sock 5 -- accept forever finally (forever $ acceptCon sock priorities lookupPriority lookupUnHandler lookupHandler parsers dcEvent) ({-hPutStr stderr "Shutting down server..." >> -}sClose sock) -- |Checks the given PQueue for an event to handle and calls the handler -- for it, then sends the event returned by the handler to the client. handleEvents :: Handle -- ^ The handle to send the handled event on -> PQueue t -- ^ The PQueue on which to listen for events to be handled -> (t1 -> t1 -> String) -- ^ The lookup function to find the function that returns a string representation of the event. -> (t -> t -> IO t1) -- ^ The lookup function that returns the event handler -> IO () handleEvents handle pqueue lookupUnHandler lookupHandler = do event <- atomically $ do event <- getThing pqueue case event of Nothing -> retry Just event -> return event event' <- lookupHandler event event C.catch ( do hPutStr handle (lookupUnHandler event' event') hFlush handle handleEvents handle pqueue lookupUnHandler lookupHandler ) (\e -> do let err = show (e :: C.IOException) {-hPutStr stderr ("Client disconnected"-} return () ) -- accept a connection and fork a new thread to handle receiving events from it -- after the connection is accepted, create a new channel for the dispatcher to -- receive events from. -- |Listens for an incoming connection. When it gets one it spawns two threads for the connection, one using 'handleEvents' and the other using 'recvEvents'. acceptCon :: Socket -- ^ The socket to listen for incoming connections on -> Int -- ^ The number of priorities for the PQueues to have -> (t -> Int) -- ^ The function to lookup the priority level of an event -> (t1 -> t1 -> String) -- ^ The function to lookup the string representation for an event -> (t -> t -> IO t1) -- ^ The function to lookup the handler for the event -> [ParsecT [Char] () Data.Functor.Identity.Identity t] -- ^ A list of parsers to use when trying to parse Events -> Maybe t -- ^Optional. The event to put on the pqueue when a client disconnects -> IO ThreadId acceptCon sock priorities lookupPriority lookupUnHandler lookupHandler parsers dcEvent = do {-hPutStr stderr "Accepting Connections\n"-} (connsock, clientaddr) <- accept sock --hPutStr stderr $ "Connection received from: " ++ show clientaddr ++ "\n" connHandle <- socketToHandle connsock ReadWriteMode hSetBuffering connHandle NoBuffering hSetBinaryMode connHandle True pqueue <- makeQueues priorities forkIO (recvEvents connHandle pqueue lookupPriority parsers dcEvent) forkIO (handleEvents connHandle pqueue lookupUnHandler lookupHandler) -- Receive events until the connection is closed, parse them, and push them on the -- channel to the dispatcher -- |Listens for events from the Client and sends them to the 'handleEvents' thread. recvEvents :: Handle -- ^ The handle to listen for events on -> PQueue a -- ^ The PQueue used to send events to the 'handleEvents' thread -> (a -> Int) -- ^ The function to lookup the priority level of an event -> [ParsecT [Char] () Data.Functor.Identity.Identity a] -- ^ A list of parsers to use when trying to parse Events -> Maybe a -- ^Optional. The event to put on the pqueue when a client disconnects -> IO () recvEvents handle pqueue lookupPriority parsers dcEvent = -- I don't really understand how these two lines work, but I think its -- got something to do with lazy evalution. they're from RWH. do messages <- hGetContents handle mapM_ toDispatch (nullLines messages) hClose handle case dcEvent of Just x -> atomically $ writeThing pqueue (lookupPriority x) x Nothing -> return () where toDispatch str = case parse parseMsg "" str of Left e -> hPutStr stderr $ "ParseError: " ++ show e ++ "\nString: " ++ show str ++ "\n" Right a -> atomically $ writeThing pqueue (lookupPriority a) a parseMsg = choice parsers nullLines "" = [] nullLines str = x:nullLines xs where (x, xs) = splitAt (nullLines' 0 str) str nullLines' n [] = n nullLines' n ('\0':'\0':str) = n+2 nullLines' n (s:str) = nullLines' (n+1) str