{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies, FlexibleContexts #-} -- | Underlying transport abstraction module Network.BERT.Transport ( -- * Core definitions Transport(..) , Server(..) , TransportM(..) , SendPacketFn -- * Sending and receiving packets , sendt, recvt, recvtForever -- * TCP transport , TCP(..) , tcpClient , TCPServer(..) , tcpServer -- * Utilities , resolve ) where import Control.Monad import Control.Applicative import Control.Monad.Reader import Network.Socket import Data.Conduit import Data.Conduit.Network import Data.Conduit.Serialization.Binary import Data.Void import Data.BERT -- | A function to send packets to the peer type SendPacketFn = Packet -> IO () -- | The transport monad allows receiving packets through the conduit, -- and sending functions via the provided 'SendPacketFn' type TransportM = ReaderT SendPacketFn (ConduitM Packet Void IO) -- | The class for transports class Transport t where runSession :: t -> TransportM a -> IO a closeConnection :: t -> IO () class Transport (ServerTransport s) => Server s where -- | The underlying transport used by the server type ServerTransport s -- | This method should listen for incoming requests, establish some -- sort of a connection (represented by the transport) and then invoke -- the handling function runServer :: s -> (ServerTransport s -> IO ()) -> IO () -- | Free any resources that the server has acquired (such as the -- listening socket) cleanup :: s -> IO () -- | The TCP transport data TCP = TCP { getTcpSocket :: !Socket -- ^ The socket used for communication. -- -- The connection is assumed to be already established when this -- structure is passed in. } tcpSendPacketFn :: TCP -> SendPacketFn tcpSendPacketFn (TCP sock) packet = yield packet $= conduitEncode $$ sinkSocket sock instance Transport TCP where runSession tcp@(TCP sock) session = sourceSocket sock $= conduitDecode $$ (runReaderT session (tcpSendPacketFn tcp)) closeConnection (TCP sock) = sClose sock -- | Establish a connection to the TCP server and return the resulting -- transport. It can be used to make multiple requests. tcpClient :: HostName -> PortNumber -> IO TCP tcpClient host port = do sock <- socket AF_INET Stream defaultProtocol sa <- SockAddrInet port <$> resolve host connect sock sa return $ TCP sock -- | The TCP server data TCPServer = TCPServer { getTcpListenSocket :: !Socket -- ^ The listening socket. Assumed to be bound but not listening yet. } instance Server TCPServer where type ServerTransport TCPServer = TCP runServer (TCPServer sock) handle = do listen sock sOMAXCONN forever $ do (clientsock, _) <- accept sock setSocketOption clientsock NoDelay 1 handle $ TCP clientsock cleanup (TCPServer sock) = sClose sock -- | A simple 'TCPServer' constructor, listens on all local interfaces. -- -- If you want to bind only to some of the interfaces, create the socket -- manually using the functions from "Network.Socket". tcpServer :: PortNumber -> IO TCPServer tcpServer port = do sock <- socket AF_INET Stream defaultProtocol setSocketOption sock ReuseAddr 1 bindSocket sock $ SockAddrInet port iNADDR_ANY return $ TCPServer sock -- | Send a term sendt :: Term -> TransportM () sendt t = ask >>= \send -> liftIO . send . Packet $ t -- | Receive a term recvt :: TransportM (Maybe Term) recvt = fmap fromPacket <$> lift await -- | Execute an action for every incoming term, until the connection is -- closed recvtForever :: (Term -> TransportM a) -> TransportM () recvtForever f = ReaderT $ \send -> awaitForever $ flip runReaderT send . f . fromPacket -- | A simple address resolver resolve :: HostName -> IO HostAddress resolve host = do r <- getAddrInfo (Just hints) (Just host) Nothing case r of (AddrInfo { addrAddress = (SockAddrInet _ addr) }:_) -> return addr _ -> fail $ "Failed to resolve " ++ host where hints = defaultHints { addrFamily = AF_INET }