{-# LANGUAGE OverloadedStrings #-}
module Network.WebSockets.Server
( ServerApp
, runServer
, ServerOptions (..)
, defaultServerOptions
, runServerWithOptions
, runServerWith
, makeListenSocket
, makePendingConnection
, makePendingConnectionFromStream
, PongTimeout
) where
import Control.Concurrent (threadDelay)
import qualified Control.Concurrent.Async as Async
import Control.Exception (Exception, allowInterrupt,
bracket, bracketOnError,
finally, mask_, throwIO)
import Control.Monad (forever, void, when)
import qualified Data.IORef as IORef
import Data.Maybe (isJust)
import Network.Socket (Socket)
import qualified Network.Socket as S
import qualified System.Clock as Clock
import Network.WebSockets.Connection
import Network.WebSockets.Http
import qualified Network.WebSockets.Stream as Stream
import Network.WebSockets.Types
type ServerApp = PendingConnection -> IO ()
runServer :: String
-> Int
-> ServerApp
-> IO ()
runServer :: String -> Int -> ServerApp -> IO ()
runServer String
host Int
port ServerApp
app = String -> Int -> ConnectionOptions -> ServerApp -> IO ()
runServerWith String
host Int
port ConnectionOptions
defaultConnectionOptions ServerApp
app
runServerWith :: String -> Int -> ConnectionOptions -> ServerApp -> IO ()
runServerWith :: String -> Int -> ConnectionOptions -> ServerApp -> IO ()
runServerWith String
host Int
port ConnectionOptions
opts = ServerOptions -> ServerApp -> IO ()
forall a. ServerOptions -> ServerApp -> IO a
runServerWithOptions ServerOptions
defaultServerOptions
{ serverHost :: String
serverHost = String
host
, serverPort :: Int
serverPort = Int
port
, serverConnectionOptions :: ConnectionOptions
serverConnectionOptions = ConnectionOptions
opts
}
{-# DEPRECATED runServerWith "Use 'runServerWithOptions' instead" #-}
data ServerOptions = ServerOptions
{ ServerOptions -> String
serverHost :: String
, ServerOptions -> Int
serverPort :: Int
, ServerOptions -> ConnectionOptions
serverConnectionOptions :: ConnectionOptions
, ServerOptions -> Maybe Int
serverRequirePong :: Maybe Int
}
defaultServerOptions :: ServerOptions
defaultServerOptions :: ServerOptions
defaultServerOptions = ServerOptions :: String -> Int -> ConnectionOptions -> Maybe Int -> ServerOptions
ServerOptions
{ serverHost :: String
serverHost = String
"127.0.0.1"
, serverPort :: Int
serverPort = Int
8080
, serverConnectionOptions :: ConnectionOptions
serverConnectionOptions = ConnectionOptions
defaultConnectionOptions
, serverRequirePong :: Maybe Int
serverRequirePong = Maybe Int
forall a. Maybe a
Nothing
}
runServerWithOptions :: ServerOptions -> ServerApp -> IO a
runServerWithOptions :: ServerOptions -> ServerApp -> IO a
runServerWithOptions ServerOptions
opts ServerApp
app = IO a -> IO a
forall a. IO a -> IO a
S.withSocketsDo (IO a -> IO a) -> IO a -> IO a
forall a b. (a -> b) -> a -> b
$
IO Socket -> (Socket -> IO ()) -> (Socket -> IO a) -> IO a
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket
(String -> Int -> IO Socket
makeListenSocket String
host Int
port)
Socket -> IO ()
S.close ((Socket -> IO a) -> IO a) -> (Socket -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \Socket
sock -> IO a -> IO a
forall a. IO a -> IO a
mask_ (IO a -> IO a) -> IO a -> IO a
forall a b. (a -> b) -> a -> b
$ IO () -> IO a
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever (IO () -> IO a) -> IO () -> IO a
forall a b. (a -> b) -> a -> b
$ do
IO ()
allowInterrupt
(Socket
conn, SockAddr
_) <- Socket -> IO (Socket, SockAddr)
S.accept Socket
sock
IORef Int64
killRef <- Int64 -> IO (IORef Int64)
forall a. a -> IO (IORef a)
IORef.newIORef (Int64 -> IO (IORef Int64)) -> IO Int64 -> IO (IORef Int64)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (Int64 -> Int64 -> Int64
forall a. Num a => a -> a -> a
+ Int64
killDelay) (Int64 -> Int64) -> IO Int64 -> IO Int64
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO Int64
getSecs
let tickle :: IO ()
tickle = IORef Int64 -> Int64 -> IO ()
forall a. IORef a -> a -> IO ()
IORef.writeIORef IORef Int64
killRef (Int64 -> IO ()) -> IO Int64 -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (Int64 -> Int64 -> Int64
forall a. Num a => a -> a -> a
+ Int64
killDelay) (Int64 -> Int64) -> IO Int64 -> IO Int64
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO Int64
getSecs
let connOpts' :: ConnectionOptions
connOpts'
| Bool -> Bool
not Bool
useKiller = ConnectionOptions
connOpts
| Bool
otherwise = ConnectionOptions
connOpts
{ connectionOnPong :: IO ()
connectionOnPong = IO ()
tickle IO () -> IO () -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ConnectionOptions -> IO ()
connectionOnPong ConnectionOptions
connOpts
}
Async ()
appAsync <- ((forall a. IO a -> IO a) -> IO ()) -> IO (Async ())
forall a. ((forall a. IO a -> IO a) -> IO a) -> IO (Async a)
Async.asyncWithUnmask (((forall a. IO a -> IO a) -> IO ()) -> IO (Async ()))
-> ((forall a. IO a -> IO a) -> IO ()) -> IO (Async ())
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
unmask ->
(IO () -> IO ()
forall a. IO a -> IO a
unmask (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
Socket -> ConnectionOptions -> ServerApp -> IO ()
runApp Socket
conn ConnectionOptions
connOpts' ServerApp
app) IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO a
`finally`
(Socket -> IO ()
S.close Socket
conn)
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
useKiller (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ IO (Async ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Async ()) -> IO ()) -> IO (Async ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ IO () -> IO (Async ())
forall a. IO a -> IO (Async a)
Async.async (IORef Int64 -> Async () -> IO ()
forall a. IORef Int64 -> Async a -> IO ()
killer IORef Int64
killRef Async ()
appAsync)
where
host :: String
host = ServerOptions -> String
serverHost ServerOptions
opts
port :: Int
port = ServerOptions -> Int
serverPort ServerOptions
opts
connOpts :: ConnectionOptions
connOpts = ServerOptions -> ConnectionOptions
serverConnectionOptions ServerOptions
opts
getSecs :: IO Int64
getSecs = TimeSpec -> Int64
Clock.sec (TimeSpec -> Int64) -> IO TimeSpec -> IO Int64
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Clock -> IO TimeSpec
Clock.getTime Clock
Clock.Monotonic
useKiller :: Bool
useKiller = Maybe Int -> Bool
forall a. Maybe a -> Bool
isJust (Maybe Int -> Bool) -> Maybe Int -> Bool
forall a b. (a -> b) -> a -> b
$ ServerOptions -> Maybe Int
serverRequirePong ServerOptions
opts
killDelay :: Int64
killDelay = Int64 -> (Int -> Int64) -> Maybe Int -> Int64
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int64
0 Int -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ServerOptions -> Maybe Int
serverRequirePong ServerOptions
opts)
killer :: IORef Int64 -> Async a -> IO ()
killer IORef Int64
killRef Async a
appAsync = do
Int64
killAt <- IORef Int64 -> IO Int64
forall a. IORef a -> IO a
IORef.readIORef IORef Int64
killRef
Int64
now <- IO Int64
getSecs
Maybe (Either SomeException a)
appState <- Async a -> IO (Maybe (Either SomeException a))
forall a. Async a -> IO (Maybe (Either SomeException a))
Async.poll Async a
appAsync
case Maybe (Either SomeException a)
appState of
Just Either SomeException a
_ -> () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
Maybe (Either SomeException a)
Nothing | Int64
now Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
< Int64
killAt -> do
Int -> IO ()
threadDelay (Int64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
killDelay Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000)
IORef Int64 -> Async a -> IO ()
killer IORef Int64
killRef Async a
appAsync
Maybe (Either SomeException a)
_ -> Async a -> PongTimeout -> IO ()
forall e a. Exception e => Async a -> e -> IO ()
Async.cancelWith Async a
appAsync PongTimeout
PongTimeout
makeListenSocket :: String -> Int -> IO Socket
makeListenSocket :: String -> Int -> IO Socket
makeListenSocket String
host Int
port = do
AddrInfo
addr:[AddrInfo]
_ <- Maybe AddrInfo -> Maybe String -> Maybe String -> IO [AddrInfo]
S.getAddrInfo (AddrInfo -> Maybe AddrInfo
forall a. a -> Maybe a
Just AddrInfo
hints) (String -> Maybe String
forall a. a -> Maybe a
Just String
host) (String -> Maybe String
forall a. a -> Maybe a
Just (Int -> String
forall a. Show a => a -> String
show Int
port))
IO Socket
-> (Socket -> IO ()) -> (Socket -> IO Socket) -> IO Socket
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracketOnError
(Family -> SocketType -> ProtocolNumber -> IO Socket
S.socket (AddrInfo -> Family
S.addrFamily AddrInfo
addr) SocketType
S.Stream ProtocolNumber
S.defaultProtocol)
Socket -> IO ()
S.close
(\Socket
sock -> do
()
_ <- Socket -> SocketOption -> Int -> IO ()
S.setSocketOption Socket
sock SocketOption
S.ReuseAddr Int
1
()
_ <- Socket -> SocketOption -> Int -> IO ()
S.setSocketOption Socket
sock SocketOption
S.NoDelay Int
1
Socket -> SockAddr -> IO ()
S.bind Socket
sock (AddrInfo -> SockAddr
S.addrAddress AddrInfo
addr)
Socket -> Int -> IO ()
S.listen Socket
sock Int
5
Socket -> IO Socket
forall (m :: * -> *) a. Monad m => a -> m a
return Socket
sock
)
where
hints :: AddrInfo
hints = AddrInfo
S.defaultHints { addrSocketType :: SocketType
S.addrSocketType = SocketType
S.Stream }
runApp :: Socket
-> ConnectionOptions
-> ServerApp
-> IO ()
runApp :: Socket -> ConnectionOptions -> ServerApp -> IO ()
runApp Socket
socket ConnectionOptions
opts ServerApp
app =
IO PendingConnection -> ServerApp -> ServerApp -> IO ()
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket
(Socket -> ConnectionOptions -> IO PendingConnection
makePendingConnection Socket
socket ConnectionOptions
opts)
(Stream -> IO ()
Stream.close (Stream -> IO ()) -> (PendingConnection -> Stream) -> ServerApp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PendingConnection -> Stream
pendingStream)
ServerApp
app
makePendingConnection
:: Socket -> ConnectionOptions -> IO PendingConnection
makePendingConnection :: Socket -> ConnectionOptions -> IO PendingConnection
makePendingConnection Socket
socket ConnectionOptions
opts = do
Stream
stream <- Socket -> IO Stream
Stream.makeSocketStream Socket
socket
Stream -> ConnectionOptions -> IO PendingConnection
makePendingConnectionFromStream Stream
stream ConnectionOptions
opts
makePendingConnectionFromStream
:: Stream.Stream -> ConnectionOptions -> IO PendingConnection
makePendingConnectionFromStream :: Stream -> ConnectionOptions -> IO PendingConnection
makePendingConnectionFromStream Stream
stream ConnectionOptions
opts = do
Maybe RequestHead
mbRequest <- Stream -> Parser RequestHead -> IO (Maybe RequestHead)
forall a. Stream -> Parser a -> IO (Maybe a)
Stream.parse Stream
stream (Bool -> Parser RequestHead
decodeRequestHead Bool
False)
case Maybe RequestHead
mbRequest of
Maybe RequestHead
Nothing -> ConnectionException -> IO PendingConnection
forall e a. Exception e => e -> IO a
throwIO ConnectionException
ConnectionClosed
Just RequestHead
request -> PendingConnection -> IO PendingConnection
forall (m :: * -> *) a. Monad m => a -> m a
return PendingConnection :: ConnectionOptions
-> RequestHead
-> (Connection -> IO ())
-> Stream
-> PendingConnection
PendingConnection
{ pendingOptions :: ConnectionOptions
pendingOptions = ConnectionOptions
opts
, pendingRequest :: RequestHead
pendingRequest = RequestHead
request
, pendingOnAccept :: Connection -> IO ()
pendingOnAccept = \Connection
_ -> () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
, pendingStream :: Stream
pendingStream = Stream
stream
}
data PongTimeout = PongTimeout deriving Int -> PongTimeout -> ShowS
[PongTimeout] -> ShowS
PongTimeout -> String
(Int -> PongTimeout -> ShowS)
-> (PongTimeout -> String)
-> ([PongTimeout] -> ShowS)
-> Show PongTimeout
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PongTimeout] -> ShowS
$cshowList :: [PongTimeout] -> ShowS
show :: PongTimeout -> String
$cshow :: PongTimeout -> String
showsPrec :: Int -> PongTimeout -> ShowS
$cshowsPrec :: Int -> PongTimeout -> ShowS
Show
instance Exception PongTimeout