{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TupleSections #-} {-| Module : Reflex.Backend.Socket.Connect Copyright : (c) 2018-2019, Commonwealth Scientific and Industrial Research Organisation License : BSD3 Maintainer : dave.laing.80@gmail.com, jack.kelly@data61.csiro.au Stability : experimental Portability : non-portable Use 'connect' to attempt a connection to a remote endpoint, and get an @'Event' t ('Either' 'SetupError' 'Socket')@ that tells you whether or not it worked. -} module Reflex.Backend.Socket.Connect (connect) where import Control.Concurrent (forkIO) import Control.Exception (IOException, onException, try) import Control.Monad.Except (ExceptT(..), runExceptT, withExceptT) import Control.Monad.Trans (MonadIO(..)) import Data.Functor (($>), void) import Data.List.NonEmpty (NonEmpty, fromList) import Data.Semigroup.Foldable (asum1) import Network.Socket (Socket, AddrInfo(..), defaultProtocol) import qualified Network.Socket as NS import Reflex import Reflex.Backend.Socket.Error (SetupError(..)) -- | Connect to a remote endpoint. The connection happens in a -- background thread. connect :: ( Reflex t , PerformEvent t m , TriggerEvent t m , PostBuild t m , MonadIO (Performable m) , MonadIO m ) => Maybe NS.HostName -- ^ Host to connect to. If 'Nothing', connect via loopback. -> NS.ServiceName -- ^ Service (port number or service name). See the -- . -> m (Event t (Either SetupError Socket)) -- ^ This event will fire exactly once. connect mHost service = do ePostBuild <- getPostBuild performEventAsync $ ePostBuild $> \onRes -> void . liftIO . forkIO $ let getAddrs :: ExceptT SetupError IO (NonEmpty AddrInfo) getAddrs = withExceptT GetAddrInfoError . ExceptT . try $ -- fromList is OK here, as getaddrinfo(3) is required to -- return a nonempty list of addrinfos. -- -- See: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html -- And: https://github.com/haskell/network/issues/407 fromList <$> NS.getAddrInfo Nothing mHost (Just service) tryConnect :: AddrInfo -> ExceptT (NonEmpty (AddrInfo, IOException)) IO Socket tryConnect info = withExceptT (pure . (info,)) . ExceptT . try $ do sock <- NS.socket (addrFamily info) NS.Stream defaultProtocol NS.connect sock (addrAddress info) `onException` NS.close sock pure sock in do res <- runExceptT $ do addrs <- getAddrs let attempts = tryConnect <$> addrs withExceptT UseAddrInfoError $ asum1 attempts onRes res