hw-polysemy-0.2.1.0: core/HaskellWorks/IO/Network/Socket.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module HaskellWorks.IO.Network.Socket
( doesSocketExist
, isPortOpen
, canConnect
, listenOn
, allocateRandomPorts
) where
import Control.Exception (IOException, handle)
import Control.Monad
import Data.Bool
import Data.Function
import Data.Functor
import Data.Int
import Data.Maybe
import Network.Socket (Family (AF_INET), SockAddr (..), Socket,
SocketType (Stream))
import Prelude (fromIntegral)
import System.IO (FilePath, IO)
import Text.Show (show)
import qualified Network.Socket as IO
import qualified System.Directory as IO
import qualified UnliftIO.Exception as IO
-- | Check if a TCP port is open
isPortOpen :: Int -> IO Bool
isPortOpen port = do
socketAddressInfos <- IO.getAddrInfo Nothing (Just "127.0.0.1") (Just (show port))
case socketAddressInfos of
socketAddressInfo:_ ->
handle (return . const @Bool @IOException False) $
canConnect (IO.addrAddress socketAddressInfo) $> True
[] -> return False
-- | Check if it is possible to connect to a socket address
canConnect :: SockAddr -> IO ()
canConnect sockAddr = IO.bracket (IO.socket AF_INET Stream 6) IO.close' $ \sock -> do
IO.connect sock sockAddr
-- | Open a socket at the specified port for listening
listenOn :: Int -> IO Socket
listenOn n = do
sock <- IO.socket AF_INET Stream 0
sockAddrInfo:_ <- IO.getAddrInfo Nothing (Just "127.0.0.1") (Just (show n))
IO.setSocketOption sock IO.ReuseAddr 1
IO.bind sock (IO.addrAddress sockAddrInfo)
IO.listen sock 2
return sock
doesSocketExist :: FilePath -> IO Bool
doesSocketExist = IO.doesFileExist
{-# INLINE doesSocketExist #-}
-- | Allocate the specified number of random ports
allocateRandomPorts :: Int -> IO [Int]
allocateRandomPorts n = do
let hints = IO.defaultHints
{ IO.addrFlags = [IO.AI_PASSIVE]
, IO.addrSocketType = IO.Stream
}
-- Create n sockets with randomly bound ports, grab the port numbers and close those ports
addr:_ <- IO.getAddrInfo (Just hints) (Just "127.0.0.1") (Just "0")
socks <- forM [1..n] $ \_ -> IO.socket (IO.addrFamily addr) (IO.addrSocketType addr) (IO.addrProtocol addr)
forM_ socks $ \sock -> IO.bind sock (IO.addrAddress addr)
ports <- forM socks IO.socketPort
forM_ socks IO.close
return $ fmap fromIntegral ports