module Eventloop.Utility.Websockets
( module Eventloop.Utility.Websockets
, S.Socket
, Connection
) where
import qualified Network.Socket as S
import qualified Data.Text as T
import Network.WebSockets hiding (Message)
import Control.Concurrent.MVar
import Control.Concurrent
import Control.Exception
import Data.ByteString.Lazy
type Host = [Char]
type Port = Int
type Message = [Char]
type ReceiveBuffer = MVar Message
createBindListenServerSocket :: Host -> Port -> IO S.Socket
createBindListenServerSocket host port = do
host' <- S.inet_addr host
socket <- S.socket S.AF_INET S.Stream S.defaultProtocol
S.setSocketOption socket S.ReuseAddr 1
S.bindSocket socket (S.SockAddrInet (fromIntegral port) host')
S.listen socket 5
return socket
acceptFirstConnection :: S.Socket -> IO Connection
acceptFirstConnection serverSocket = do
(clientSocket, clientAddr) <- S.accept serverSocket
pendingConnection <- makePendingConnection clientSocket defaultConnectionOptions
connection <- acceptRequest pendingConnection
return connection
setupWebsocketConnection :: Host -> Port -> IO (ReceiveBuffer, Connection, S.Socket)
setupWebsocketConnection host port = S.withSocketsDo $ do
serverSocket <- createBindListenServerSocket host port
clientConnection <- acceptFirstConnection serverSocket
recvBuffer <- newEmptyMVar
spawnReader recvBuffer clientConnection
return (recvBuffer, clientConnection, serverSocket)
spawnReader :: ReceiveBuffer -> Connection -> IO ThreadId
spawnReader recvBuffer conn = do
forkIO (handle handleCloseRequestException $ readIntoBuffer recvBuffer conn)
readIntoBuffer :: ReceiveBuffer -> Connection -> IO ()
readIntoBuffer recvBuffer conn = do
textMessage <- receiveData conn
let
message = T.unpack textMessage
putMVar recvBuffer message
readIntoBuffer recvBuffer conn
handleCloseRequestException :: ConnectionException -> IO ()
handleCloseRequestException (CloseRequest i reason) | i == 1000 = return ()
| otherwise = Prelude.putStrLn ("Connection was closed but reason unknown: " ++ show i ++ show reason)
handleCloseRequestException (ConnectionClosed) = Prelude.putStrLn ("Connection was closed unexpectedly")
handleCloseRequestException (ParseException text) = Prelude.putStrLn ("Parse exception on message: " ++ text)
hasMessage :: ReceiveBuffer -> IO Bool
hasMessage recvBuffer = do
buffer <- tryReadMVar recvBuffer
return (buffer /= Nothing)
takeMessage :: ReceiveBuffer -> IO Message
takeMessage recvBuffer = do
message <- takeMVar recvBuffer
return message
writeMessage :: Connection -> Message -> IO ()
writeMessage conn message = sendTextData conn (T.pack message)
writeBinaryMessage :: Connection -> ByteString -> IO ()
writeBinaryMessage conn message = sendBinaryData conn message
closeWebsocketConnection :: S.Socket -> Connection -> IO ()
closeWebsocketConnection serverSocket clientConnection = do
S.sClose serverSocket
sendClose clientConnection (T.pack "Shutting down..")