network-conduit 0.2.0 → 0.2.1
raw patch · 2 files changed
+123/−2 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.Conduit.Network: ClientSettings :: Int -> String -> ClientSettings
+ Data.Conduit.Network: ServerSettings :: Int -> Maybe String -> ServerSettings
+ Data.Conduit.Network: bindPort :: Maybe String -> Int -> IO Socket
+ Data.Conduit.Network: clientHost :: ClientSettings -> String
+ Data.Conduit.Network: clientPort :: ClientSettings -> Int
+ Data.Conduit.Network: data ClientSettings
+ Data.Conduit.Network: data ServerSettings
+ Data.Conduit.Network: getSocket :: String -> Int -> IO Socket
+ Data.Conduit.Network: runTCPClient :: ClientSettings -> Application -> IO ()
+ Data.Conduit.Network: runTCPServer :: ServerSettings -> Application -> IO ()
+ Data.Conduit.Network: serverHost :: ServerSettings -> Maybe String
+ Data.Conduit.Network: serverPort :: ServerSettings -> Int
+ Data.Conduit.Network: type Application = Source IO ByteString -> Sink ByteString IO () -> ResourceT IO ()
Files
- Data/Conduit/Network.hs +122/−1
- network-conduit.cabal +1/−1
Data/Conduit/Network.hs view
@@ -1,14 +1,31 @@+{-# LANGUAGE ScopedTypeVariables #-} module Data.Conduit.Network- ( sourceSocket+ ( -- * Basic utilities+ sourceSocket , sinkSocket+ -- * Simple TCP server/client interface.+ , Application+ -- ** Server+ , ServerSettings (..)+ , runTCPServer+ -- ** Client+ , ClientSettings (..)+ , runTCPClient+ -- * Helper utilities+ , bindPort+ , getSocket ) where import Data.Conduit+import qualified Network.Socket as NS import Network.Socket (Socket) import Network.Socket.ByteString (sendAll, recv) import Data.ByteString (ByteString) import qualified Data.ByteString as S import Control.Monad.IO.Class (liftIO)+import Control.Exception (bracketOnError, IOException, bracket, throwIO, SomeException, try)+import Control.Monad (forever)+import Control.Concurrent (forkIO) -- | Stream data from the socket. --@@ -39,3 +56,107 @@ liftIO (sendAll socket bs) return (Processing push close) close = return ()++-- | A simple TCP application. It takes two arguments: the @Source@ to read+-- input data from, and the @Sink@ to send output data to.+--+-- Since 0.2.1+type Application = Source IO ByteString+ -> Sink ByteString IO ()+ -> ResourceT IO ()++-- | Settings for a TCP server. It takes a port to listen on, and an optional+-- hostname to bind to.+--+-- Since 0.2.1+data ServerSettings = ServerSettings+ { serverPort :: Int+ , serverHost :: Maybe String -- ^ 'Nothing' indicates no preference+ }++-- | Run an @Application@ with the given settings. This function will create a+-- new listening socket, accept connections on it, and spawn a new thread for+-- each connection.+--+-- Since 0.2.1+runTCPServer :: ServerSettings -> Application -> IO ()+runTCPServer (ServerSettings port host) app = bracket+ (bindPort host port)+ NS.sClose+ (forever . serve)+ where+ serve lsocket = do+ (socket, _addr) <- NS.accept lsocket+ forkIO $ runResourceT $ app (sourceSocket socket) (sinkSocket socket)++-- | Settings for a TCP client, specifying how to connect to the server.+data ClientSettings = ClientSettings+ { clientPort :: Int+ , clientHost :: String+ }++-- | Run an @Application@ by connecting to the specified server.+--+-- Since 0.2.1+runTCPClient :: ClientSettings -> Application -> IO ()+runTCPClient (ClientSettings port host) app = do+ socket <- getSocket host port+ runResourceT $ app (sourceSocket socket) (sinkSocket socket)++-- | Attempt to connect to the given host/port.+--+-- Since 0.2.1+getSocket :: String -> Int -> IO NS.Socket+getSocket host' port' = do+ let hints = NS.defaultHints {+ NS.addrFlags = [NS.AI_ADDRCONFIG]+ , NS.addrSocketType = NS.Stream+ }+ (addr:_) <- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')+ sock <- NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)+ (NS.addrProtocol addr)+ ee <- try' $ NS.connect sock (NS.addrAddress addr)+ case ee of+ Left e -> NS.sClose sock >> throwIO e+ Right () -> return sock+ where+ try' :: IO a -> IO (Either SomeException a)+ try' = try++-- | Attempt to bind a listening @Socket@ on the given host/port. If no host is+-- given, will use the first address available.+--+-- Since 0.2.1+bindPort :: Maybe String -> Int -> IO Socket+bindPort host p = do+ let hints = NS.defaultHints+ { NS.addrFlags =+ [ NS.AI_PASSIVE+ , NS.AI_NUMERICSERV+ , NS.AI_NUMERICHOST+ ]+ , NS.addrSocketType = NS.Stream+ }+ port = Just . show $ p+ addrs <- NS.getAddrInfo (Just hints) host port+ let+ tryAddrs (addr1:rest@(_:_)) =+ catch+ (theBody addr1)+ (\(_ :: IOException) -> tryAddrs rest)+ tryAddrs (addr1:[]) = theBody addr1+ tryAddrs _ = error "bindPort: addrs is empty"+ theBody addr =+ bracketOnError+ (NS.socket+ (NS.addrFamily addr)+ (NS.addrSocketType addr)+ (NS.addrProtocol addr))+ NS.sClose+ (\sock -> do+ NS.setSocketOption sock NS.ReuseAddr 1+ NS.bindSocket sock (NS.addrAddress addr)+ NS.listen sock NS.maxListenQueue+ return sock+ )+ tryAddrs addrs
network-conduit.cabal view
@@ -1,5 +1,5 @@ Name: network-conduit-Version: 0.2.0+Version: 0.2.1 Synopsis: Stream socket data using conduits. Description: Stream socket data using conduits. License: BSD3