mqtt-0.1.0.0: src/Network/Stack/Server.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
--------------------------------------------------------------------------------
-- |
-- Module : Network.Stack.Server
-- Copyright : (c) Lars Petersen 2016
-- License : MIT
--
-- Maintainer : info@lars-petersen.net
-- Stability : experimental
--------------------------------------------------------------------------------
module Network.Stack.Server where
import Control.Concurrent.Async
import Control.Concurrent.MVar
import qualified Control.Exception as E
import Control.Monad
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BS
import qualified Data.ByteString.Builder.Extra as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Int
import Data.Typeable
import qualified Data.X509 as X509
import qualified Network.TLS as TLS
import qualified Network.WebSockets as WS
import qualified Network.WebSockets.Stream as WS
import qualified System.Socket as S
import qualified System.Socket.Type.Stream as S
data TLS a
data WebSocket a
class (Typeable a) => ServerStack a where
data Server a
data ServerConfig a
data ServerException a
data ServerConnection a
data ServerConnectionInfo a
-- | Creates a new server from a configuration and passes it to a handler function.
--
-- The server given to the handler function shall be bound and in
-- listening state. The handler function is usually a
-- `Control.Monad.forever` loop that accepts and handles new connections.
--
-- > withServer config $ \server->
-- > forever $ withConnection handleConnection
withServer :: ServerConfig a -> (Server a -> IO b) -> IO b
-- | Waits for and accepts a new connection from a listening server and passes
-- it to a handler function.
--
-- This operation is blocking until the lowest layer in the stack accepts
-- a new connection. The handlers of all other layers are executed within
-- an `Control.Concurrent.Async.Async` which is returned. This allows
-- the main thread waiting on the underlying socket to block just as long
-- as necessary. Upper layer protocol handshakes (TLS etc) will be executed
-- in the new thread.
--
-- > withServer config $ \server-> forever $
-- > future <- withConnection server handleConnection
-- > putStrLn "The lowest layer accepted a new connection!"
-- > async $ do
-- > result <- wait future
-- > putStrLn "The connection handler returned:"
-- > print result
withConnection :: Server a -> (ServerConnection a -> ServerConnectionInfo a -> IO b) -> IO (Async b)
class ServerStack a => StreamServerStack a where
sendStream :: ServerConnection a -> BS.ByteString -> IO Int
sendStream server bs = fromIntegral <$> sendStreamLazy server (BSL.fromStrict bs)
sendStreamLazy :: ServerConnection a -> BSL.ByteString -> IO Int64
sendStreamLazy server = foldM
(\sent bs-> sendStream server bs >>= \sent'-> pure $! sent + fromIntegral sent') 0 . BSL.toChunks
sendStreamBuilder :: ServerConnection a -> Int -> BS.Builder -> IO Int64
sendStreamBuilder server chunksize = sendStreamLazy server
. BS.toLazyByteStringWith (BS.untrimmedStrategy chunksize chunksize) mempty
receiveStream :: ServerConnection a -> Int -> IO BS.ByteString
receiveStream server i = BSL.toStrict <$> receiveStreamLazy server i
receiveStreamLazy :: ServerConnection a -> Int -> IO BSL.ByteString
receiveStreamLazy server i = BSL.fromStrict <$> receiveStream server i
{-# MINIMAL (sendStream|sendStreamLazy), (receiveStream|receiveStreamLazy) #-}
-- | This class is an abstraction for `ServerStack`s that support the
-- transmission and reception of finite messages.
class ServerStack a => MessageServerStack a where
type ClientMessage a
type ServerMessage a
-- | Send a message.
--
-- - Returns the encoded message size.
sendMessage :: ServerConnection a -> ServerMessage a -> IO Int64
-- | Send several messages. This might lead to an improvement for very short messages.
--
-- - Returns the summed size of all encoded messages.
sendMessages :: Foldable t => ServerConnection a -> t (ServerMessage a) -> IO Int64
-- | Receive a message.
--
-- - The second parameter determines the maximum encoded message size which
-- must not be exceeded by the client or an exception will be thrown.
-- Implementations shall track the consumed bytes and shall throw an
-- exception as soon as the limit is exceeded even if the message is not
-- yet complete. This is important to prevent _denial of service_ attacks.
receiveMessage :: ServerConnection a -> Int64 -> IO (ClientMessage a)
-- | Consumes incoming messages with a supplied consumer callback.
--
-- - The second parameter limits the size of a single encoded message
-- (see `receiveMessage`).
consumeMessages :: ServerConnection a -> Int64 -> (ClientMessage a -> IO Bool) -> IO ()
instance (Typeable f, Typeable p, S.Family f, S.Protocol p) => StreamServerStack (S.Socket f S.Stream p) where
sendStream (SocketServerConnection s) bs = S.sendAll s bs S.msgNoSignal
sendStreamLazy (SocketServerConnection s) lbs = S.sendAllLazy s lbs S.msgNoSignal
sendStreamBuilder (SocketServerConnection s) bufsize builder = S.sendAllBuilder s bufsize builder S.msgNoSignal
receiveStream (SocketServerConnection s) i = S.receive s i S.msgNoSignal
instance (StreamServerStack a) => StreamServerStack (TLS a) where
sendStreamLazy connection lbs = TLS.sendData (tlsContext connection) lbs >> pure (BSL.length lbs)
receiveStream connection _ = TLS.recvData (tlsContext connection)
instance (StreamServerStack a) => StreamServerStack (WebSocket a) where
sendStream connection bs = WS.sendBinaryData (wsConnection connection) bs >> pure (BS.length bs)
sendStreamLazy connection lbs = WS.sendBinaryData (wsConnection connection) lbs >> pure (BSL.length lbs)
receiveStreamLazy connection _ = WS.receiveData (wsConnection connection)
instance (S.Family f, S.Type t, S.Protocol p, Typeable f, Typeable t, Typeable p) => ServerStack (S.Socket f t p) where
data Server (S.Socket f t p) = SocketServer
{ socketServer :: !(S.Socket f t p)
, socketServerConfig :: !(ServerConfig (S.Socket f t p))
}
data ServerConfig (S.Socket f t p) = SocketServerConfig
{ socketServerConfigBindAddress :: !(S.SocketAddress f)
, socketServerConfigListenQueueSize :: Int
}
data ServerException (S.Socket f t p) = SocketServerException !S.SocketException
data ServerConnection (S.Socket f t p) = SocketServerConnection !(S.Socket f t p)
data ServerConnectionInfo (S.Socket f t p) = SocketServerConnectionInfo !(S.SocketAddress f)
withServer c handle = E.bracket
(SocketServer <$> S.socket <*> pure c)
(S.close . socketServer) $ \server-> do
S.setSocketOption (socketServer server) (S.ReuseAddress True)
S.bind (socketServer server) (socketServerConfigBindAddress $ socketServerConfig server)
S.listen (socketServer server) (socketServerConfigListenQueueSize $ socketServerConfig server)
handle server
withConnection server handle =
E.bracketOnError (S.accept (socketServer server)) (S.close . fst) $ \(connection, addr)->
async (handle (SocketServerConnection connection) (SocketServerConnectionInfo addr) `E.finally` S.close connection)
instance (StreamServerStack a, Typeable a) => ServerStack (TLS a) where
data Server (TLS a) = TlsServer
{ tlsTransportServer :: Server a
, tlsServerConfig :: ServerConfig (TLS a)
}
data ServerConfig (TLS a) = TlsServerConfig
{ tlsTransportConfig :: ServerConfig a
, tlsServerParams :: TLS.ServerParams
}
data ServerException (TLS a) =
TlsServerEndOfStreamException
deriving (Eq, Ord, Show)
data ServerConnection (TLS a) = TlsServerConnection
{ tlsTransportConnection :: ServerConnection a
, tlsContext :: TLS.Context
}
data ServerConnectionInfo (TLS a) = TlsServerConnectionInfo
{ tlsTransportServerConnectionInfo :: ServerConnectionInfo a
, tlsCertificateChain :: Maybe X509.CertificateChain
}
withServer config handle =
withServer (tlsTransportConfig config) $ \server->
handle (TlsServer server config)
withConnection server handle =
withConnection (tlsTransportServer server) $ \connection info-> do
let backend = TLS.Backend {
TLS.backendFlush = pure () -- backend doesn't buffer
, TLS.backendClose = pure () -- backend gets closed automatically
, TLS.backendSend = void . sendStream connection
-- The following is problematic: The TLS implementation requires us
-- to return exactly as many bytes as requested. The underlying transport
-- though only yields as many bytes as available.
-- The solution is to read, append and loop until the request
-- can be fulfilled.
-- TODO: Use bytestring builder for concatenation.
-- TODO: Fix TLS library upstream. The interface is awkward for a
-- networking lib.
, TLS.backendRecv = flip (receiveExactly connection) mempty
}
mvar <- newEmptyMVar
let srvParams = tlsServerParams $ tlsServerConfig server
srvParams' = srvParams {
TLS.serverHooks = (TLS.serverHooks srvParams) {
TLS.onClientCertificate = \certChain-> do
putMVar mvar certChain
pure TLS.CertificateUsageAccept
}
}
context <- TLS.contextNew backend srvParams'
TLS.handshake context
certificateChain <- tryTakeMVar mvar
x <- handle
(TlsServerConnection connection context)
(TlsServerConnectionInfo info certificateChain)
TLS.bye context
pure x
where
receiveExactly connection bytes accum = do
bs <- receiveStream connection bytes
-- TCP sockets signal a graceful end of the stream by returning zero bytes.
-- This function is not allowed to return less than the bytes
-- request and we shall not loop forever here (we did!). There is no
-- other option than throwing an exception here.
when (BS.null bs) $
E.throwIO (TlsServerEndOfStreamException :: ServerException (TLS a))
if BS.length bs < bytes
then receiveExactly connection (bytes - BS.length bs) $! accum `mappend` bs
else pure $! accum `mappend` bs
instance (StreamServerStack a) => ServerStack (WebSocket a) where
data Server (WebSocket a) = WebSocketServer
{ wsTransportServer :: Server a
}
data ServerConfig (WebSocket a) = WebSocketServerConfig
{ wsTransportConfig :: ServerConfig a
}
data ServerException (WebSocket a) = WebSocketServerException
data ServerConnection (WebSocket a) = WebSocketServerConnection
{ wsTransportConnection :: ServerConnection a
, wsConnection :: WS.Connection
}
data ServerConnectionInfo (WebSocket a) = WebSocketServerConnectionInfo
{ wsTransportServerConnectionInfo :: ServerConnectionInfo a
, wsRequestHead :: WS.RequestHead
}
withServer config handle =
withServer (wsTransportConfig config) $ \server->
handle (WebSocketServer server)
withConnection server handle =
withConnection (wsTransportServer server) $ \connection info-> do
let readSocket = (\bs-> if BS.null bs then Nothing else Just bs) <$> receiveStream connection 4096
let writeSocket Nothing = pure ()
writeSocket (Just bs) = void (sendStream connection (BSL.toStrict bs))
stream <- WS.makeStream readSocket writeSocket
pendingConnection <- WS.makePendingConnectionFromStream stream (WS.ConnectionOptions $ pure ())
acceptedConnection <- WS.acceptRequestWith pendingConnection (WS.AcceptRequest $ Just "mqtt")
x <- handle
(WebSocketServerConnection connection acceptedConnection)
(WebSocketServerConnectionInfo info $ WS.pendingRequest pendingConnection)
WS.sendClose acceptedConnection ("Thank you for flying Haskell." :: BS.ByteString)
pure x
deriving instance Show (S.SocketAddress f) => Show (ServerConnectionInfo (S.Socket f t p))
deriving instance Show (ServerConnectionInfo a) => Show (ServerConnectionInfo (TLS a))
deriving instance Show (ServerConnectionInfo a) => Show (ServerConnectionInfo (WebSocket a))
instance Typeable a => E.Exception (ServerException (TLS a))