Files
simplexmq/src/Simplex/Messaging/Transport.hs
T
Evgeny Poberezkin d0b56034a7 subscriptions (#27)
* subscribe connection and track subscriptions

* notify client when subscription ENDs

* tcp connection timeout

* move types
2021-01-24 12:01:44 +00:00

93 lines
3.0 KiB
Haskell

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Simplex.Messaging.Transport where
import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
import Control.Monad.Reader
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import GHC.IO.Exception (IOErrorType (..))
import Network.Socket
import System.IO
import System.IO.Error
import UnliftIO.Concurrent
import UnliftIO.Exception (Exception, IOException)
import qualified UnliftIO.Exception as E
import qualified UnliftIO.IO as IO
startTCPServer :: ServiceName -> IO Socket
startTCPServer port = withSocketsDo $ resolve >>= open
where
resolve = do
let hints = defaultHints {addrFlags = [AI_PASSIVE], addrSocketType = Stream}
head <$> getAddrInfo (Just hints) Nothing (Just port)
open addr = do
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
setSocketOption sock ReuseAddr 1
withFdSocket sock setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
return sock
runTCPServer :: MonadUnliftIO m => ServiceName -> (Handle -> m ()) -> m ()
runTCPServer port server =
E.bracket (liftIO $ startTCPServer port) (liftIO . close) $ \sock -> forever $ do
h <- liftIO $ acceptTCPConn sock
forkFinally (server h) (const $ IO.hClose h)
acceptTCPConn :: Socket -> IO Handle
acceptTCPConn sock = do
(conn, _) <- accept sock
getSocketHandle conn
startTCPClient :: HostName -> ServiceName -> IO Handle
startTCPClient host port =
withSocketsDo $
resolve >>= foldM tryOpen (Left err) >>= either E.throwIO return
where
err :: IOException
err = mkIOError NoSuchThing "no address" Nothing Nothing
resolve :: IO [AddrInfo]
resolve = do
let hints = defaultHints {addrSocketType = Stream}
getAddrInfo (Just hints) (Just host) (Just port)
tryOpen :: Exception e => Either e Handle -> AddrInfo -> IO (Either e Handle)
tryOpen h@(Right _) _ = return h
tryOpen (Left _) addr = E.try $ open addr
open :: AddrInfo -> IO Handle
open addr = do
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
connect sock $ addrAddress addr
getSocketHandle sock
runTCPClient :: MonadUnliftIO m => HostName -> ServiceName -> (Handle -> m a) -> m a
runTCPClient host port client = do
h <- liftIO $ startTCPClient host port
client h `E.finally` IO.hClose h
getSocketHandle :: Socket -> IO Handle
getSocketHandle conn = do
h <- socketToHandle conn ReadWriteMode
hSetBinaryMode h True
hSetNewlineMode h NewlineMode {inputNL = CRLF, outputNL = CRLF}
hSetBuffering h LineBuffering
return h
putLn :: Handle -> ByteString -> IO ()
putLn h = B.hPut h . (<> "\r\n")
getLn :: Handle -> IO ByteString
getLn h = trim_cr <$> B.hGetLine h
where
trim_cr "" = ""
trim_cr s = if B.last s == '\r' then B.init s else s