module Web.SocketIO.Connection
( runConnection
, newSessionTableRef
) where
import Web.SocketIO.Channel
import Web.SocketIO.Session
import Web.SocketIO.Timeout
import Web.SocketIO.Types
import Web.SocketIO.Log
import Control.Applicative ((<$>))
import Control.Concurrent.MVar.Lifted
import Control.Monad.Reader
import Control.Monad.Writer
import qualified Data.HashMap.Strict as H
import Data.IORef.Lifted
import System.Random (randomRIO)
newSessionTableRef :: IO (IORef SessionTable)
newSessionTableRef = newIORef H.empty
updateSession :: (SessionTable -> SessionTable) -> ConnectionM ()
updateSession update = do
tableRef <- getSessionTableRef
liftIO (modifyIORef tableRef update)
lookupSession :: SessionID -> ConnectionM (Maybe Session)
lookupSession sessionID = do
tableRef <- getSessionTableRef
table <- liftIO (readIORef tableRef)
return (H.lookup sessionID table)
executeHandler :: HandlerM () -> ChannelHub -> SessionID -> ConnectionM [Listener]
executeHandler handler channelHub sessionID = liftIO $ execWriterT (runReaderT (runHandlerM handler) (HandlerEnv channelHub sessionID))
runConnection :: Env -> Request -> IO Message
runConnection env req = do
runReaderT (runConnectionM (handleConnection =<< (retrieveSession req))) env
split :: Maybe Session -> Maybe (Session, SessionState)
split (Just session@(Session _ state _ _ _)) = Just (session, state)
split Nothing = Nothing
retrieveSession :: Request -> ConnectionM (Request, Maybe (Session, SessionState))
retrieveSession Handshake = do
return (Handshake, Nothing)
retrieveSession (Connect sessionID) = do
result <- lookupSession sessionID
return (Connect sessionID, split result)
retrieveSession (Disconnect sessionID) = do
result <- lookupSession sessionID
return (Disconnect sessionID, split result)
retrieveSession (Emit sessionID e) = do
result <- lookupSession sessionID
return (Emit sessionID e, split result)
handleConnection :: (Request, Maybe (Session, SessionState)) -> ConnectionM Message
handleConnection (Handshake, _) = do
session@(Session sessionID _ _ _ _) <- makeSession
updateSession (H.insert sessionID session)
logWithSessionID Debug sessionID "[Session] Created"
logWithSessionID Debug sessionID "[Request] Handshake"
setTimeout session
runSession SessionHandshake session
where genSessionID = liftIO $ serialize <$> randomRIO (100000000000, 999999999999 :: Integer)
makeSession = do
sessionID <- genSessionID
channelHub <- makeChannelHub sessionID
handler <- getHandler
listeners <- executeHandler handler channelHub sessionID
timeoutVar <- newEmptyMVar
return $ Session sessionID Connecting channelHub listeners timeoutVar
handleConnection (Connect sessionID, Just (session, Connecting)) = do
logWithSessionID Debug sessionID "[Request] Connect: ACK"
extendTimeout session
let session' = session { sessionState = Connected }
updateSession (H.insert sessionID session')
runSession SessionConnect session'
handleConnection (Connect sessionID, Just (session, Connected)) = do
logWithSessionID Debug sessionID "[Request] Connect: Polling"
extendTimeout session
runSession SessionPolling session
handleConnection (Connect sessionID, Nothing) = do
logWithSessionID Warn sessionID "[Request] Connect: Session not found"
return $ MsgError NoEndpoint NoData
handleConnection (Disconnect sessionID, Just (session, _)) = do
logWithSessionID Debug sessionID "[Request] Disconnect: By client"
clearTimeout session
updateSession (H.delete sessionID)
logWithSessionID Debug sessionID "[Session] Destroyed"
runSession SessionDisconnectByClient session
handleConnection (Disconnect sessionID, Nothing) = do
logWithSessionID Warn sessionID "[Request] Disconnect: Session not found"
return MsgNoop
handleConnection (Emit sessionID _, Just (session, Connecting)) = do
extendTimeout session
logWithSessionID Warn sessionID "[Request] Emit: Session still connecting, not ACKed"
return $ MsgError NoEndpoint NoData
handleConnection (Emit sessionID event@(Event eventName (Payload payloads)), Just (session, Connected)) = do
logWithSessionID Debug sessionID $ "[Request] Emit: " <> serialize eventName <> " " <> serialize payloads
runSession (SessionEmit event) session
handleConnection (Emit sessionID NoEvent, Just (_, Connected)) = do
logWithSessionID Warn sessionID "[Request] Emit: event malformed"
return $ MsgError NoEndpoint NoData
handleConnection (Emit sessionID _, Nothing) = do
logWithSessionID Warn sessionID "[Request] Emit: Session not found"
return $ MsgError NoEndpoint NoData