packages feed

network-conduit 0.5.0 → 0.5.0.1

raw patch · 4 files changed

+214/−85 lines, 4 filesdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Conduit.Network: instance Eq HostPreference
- Data.Conduit.Network: instance IsString HostPreference
- Data.Conduit.Network: instance Ord HostPreference
- Data.Conduit.Network: instance Read HostPreference
- Data.Conduit.Network: instance Show HostPreference
+ Data.Conduit.Network.UDP: Host :: String -> HostPreference
+ Data.Conduit.Network.UDP: HostAny :: HostPreference
+ Data.Conduit.Network.UDP: HostIPv4 :: HostPreference
+ Data.Conduit.Network.UDP: HostIPv6 :: HostPreference
+ Data.Conduit.Network.UDP: Message :: {-# UNPACK #-} !ByteString -> !SockAddr -> Message
+ Data.Conduit.Network.UDP: bindPort :: Int -> HostPreference -> IO Socket
+ Data.Conduit.Network.UDP: data HostPreference
+ Data.Conduit.Network.UDP: data Message
+ Data.Conduit.Network.UDP: getSocket :: String -> Int -> IO (Socket, AddrInfo)
+ Data.Conduit.Network.UDP: msgData :: Message -> {-# UNPACK #-} !ByteString
+ Data.Conduit.Network.UDP: msgSender :: Message -> !SockAddr
+ Data.Conduit.Network.UDP: sinkAllSocket :: MonadIO m => Socket -> GInfSink ByteString m
+ Data.Conduit.Network.UDP: sinkAllToSocket :: MonadIO m => Socket -> GInfSink Message m
+ Data.Conduit.Network.UDP: sinkSocket :: MonadIO m => Socket -> GInfSink ByteString m
+ Data.Conduit.Network.UDP: sinkToSocket :: MonadIO m => Socket -> GInfSink Message m
+ Data.Conduit.Network.UDP: sourceSocket :: MonadIO m => Socket -> Int -> GSource m Message

Files

Data/Conduit/Network.hs view
@@ -1,6 +1,5 @@-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-} module Data.Conduit.Network     ( -- * Basic utilities       sourceSocket@@ -26,14 +25,15 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as S import Control.Monad.IO.Class (MonadIO (liftIO))-import Control.Exception (bracketOnError, IOException, throwIO, SomeException, try, finally, bracket)+import Control.Exception (throwIO, SomeException, try, finally, bracket) import Control.Monad (forever) import Control.Monad.Trans.Control (MonadBaseControl, control) import Control.Monad.Trans.Class (lift) import Control.Concurrent (forkIO)-import Data.String (IsString (fromString))-import qualified Control.Exception as E +import Data.Conduit.Network.Utils (HostPreference)+import qualified Data.Conduit.Network.Utils as Utils+ -- | Stream data from the socket. -- -- This function does /not/ automatically close the socket.@@ -120,13 +120,7 @@ -- 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)+    (sock, addr) <- Utils.getSocket host' port' NS.Stream     ee <- try' $ NS.connect sock (NS.addrAddress addr)     case ee of         Left e -> NS.sClose sock >> throwIO e@@ -135,81 +129,12 @@     try' :: IO a -> IO (Either SomeException a)     try' = try --- | Which host to bind.------ Note: The @IsString@ instance recognizes the following special values:------ * @*@ means @HostAny@------ * @*4@ means @HostIPv4@------ * @*6@ means @HostIPv6@-data HostPreference =-    HostAny-  | HostIPv4-  | HostIPv6-  | Host String-    deriving (Eq, Ord, Show, Read)--instance IsString HostPreference where-    -- The funny code coming up is to get around some irritating warnings from-    -- GHC. I should be able to just write:-    {--    fromString "*" = HostAny-    fromString "*4" = HostIPv4-    fromString "*6" = HostIPv6-    -}-    fromString s'@('*':s) =-        case s of-            [] -> HostAny-            ['4'] -> HostIPv4-            ['6'] -> HostIPv6-            _ -> Host s'-    fromString s = Host s- -- | Attempt to bind a listening @Socket@ on the given host/port. If no host is -- given, will use the first address available. -- -- Since 0.3.0 bindPort :: Int -> HostPreference -> IO Socket bindPort p s = do-    let hints = NS.defaultHints-            { NS.addrFlags = [ NS.AI_PASSIVE-                             , NS.AI_NUMERICSERV-                             , NS.AI_NUMERICHOST-                             ]-            , NS.addrSocketType = NS.Stream-            }-        host =-            case s of-                Host s' -> Just s'-                _ -> Nothing-        port = Just . show $ p-    addrs <- NS.getAddrInfo (Just hints) host port-    -- Choose an IPv6 socket if exists.  This ensures the socket can-    -- handle both IPv4 and IPv6 if v6only is false.-    let addrs4 = filter (\x -> NS.addrFamily x /= NS.AF_INET6) addrs-        addrs6 = filter (\x -> NS.addrFamily x == NS.AF_INET6) addrs-        addrs' =-            case s of-                HostIPv4 -> addrs4 ++ addrs6-                HostIPv6 -> addrs6 ++ addrs4-                _ -> addrs--        tryAddrs (addr1:rest@(_:_)) =-                                      E.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'+    sock <- Utils.bindPort p s NS.Stream+    NS.listen sock NS.maxListenQueue+    return sock
+ Data/Conduit/Network/UDP.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE RankNTypes #-}+module Data.Conduit.Network.UDP+    ( -- * UDP message representation+      Message (..)+      -- * Basic utilities+    , sourceSocket+    , sinkSocket+    , sinkAllSocket+    , sinkToSocket+    , sinkAllToSocket+      -- * Helper Utilities+    , HostPreference (..)+    , bindPort+    , getSocket+    ) where++import Data.Conduit+import Network.Socket (AddrInfo, SockAddr, Socket)+import qualified Network.Socket as NS+import Network.Socket.ByteString (recvFrom, send, sendAll, sendTo, sendAllTo)+import Data.ByteString (ByteString)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad (void)+import Control.Monad.Trans.Class (lift)++import Data.Conduit.Network.Utils (HostPreference)+import qualified Data.Conduit.Network.Utils as Utils++-- | Representation of a single message+data Message = Message { msgData :: {-# UNPACK #-} !ByteString+                       , msgSender :: !SockAddr+                       }++-- | Stream messages from the socket.+--+-- The given @len@ defines the maximum packet size. Every produced item+-- contains the message payload and the origin address.+--+-- This function does /not/ automatically close the socket.+sourceSocket :: MonadIO m => Socket -> Int -> GSource m Message+sourceSocket socket len = loop+  where+    loop = do+        (bs, addr) <- lift $ liftIO $ recvFrom socket len+        yield (Message bs addr) >> loop++-- | Stream messages to the connected socket.+--+-- The payload is sent using @send@, so some of it might be lost.+--+-- This function does /not/ automatically close the socket.+sinkSocket :: MonadIO m => Socket -> GInfSink ByteString m+sinkSocket = sinkSocketHelper (\sock bs -> void $ send sock bs)++-- | Stream messages to the connected socket.+--+-- The payload is sent using @sendAll@, so it might end up in multiple packets.+--+-- This function does /not/ automatically close the socket.+sinkAllSocket :: MonadIO m => Socket -> GInfSink ByteString m+sinkAllSocket = sinkSocketHelper sendAll++-- | Stream messages to the socket.+--+-- Every handled item contains the message payload and the destination+-- address. The payload is sent using @sendTo@, so some of it might be+-- lost.+--+-- This function does /not/ automatically close the socket.+sinkToSocket :: MonadIO m => Socket -> GInfSink Message m+sinkToSocket = sinkSocketHelper (\sock (Message bs addr) -> void $ sendTo sock bs addr)++-- | Stream messages to the socket.+--+-- Every handled item contains the message payload and the destination+-- address. The payload is sent using @sendAllTo@, so it might end up in+-- multiple packets.+--+-- This function does /not/ automatically close the socket.+sinkAllToSocket :: MonadIO m => Socket -> GInfSink Message m+sinkAllToSocket = sinkSocketHelper (\sock (Message bs addr) -> sendAllTo sock bs addr)++-- | Attempt to connect to the given host/port.+getSocket :: String -> Int -> IO (Socket, AddrInfo)+getSocket host' port' = Utils.getSocket host' port' NS.Datagram++-- | Attempt to bind a listening @Socket@ on the given host/port. If no host is+-- given, will use the first address available.+bindPort :: Int -> HostPreference -> IO Socket+bindPort p s = Utils.bindPort p s NS.Datagram++-- Internal+sinkSocketHelper :: MonadIO m => (Socket -> a -> IO ())+                              -> Socket+                              -> GInfSink a m+sinkSocketHelper act socket = loop+  where+    loop = awaitE >>= either+                        return+                        (\a -> lift (liftIO $ act socket a) >> loop)+{-# INLINE sinkSocketHelper #-}
+ Data/Conduit/Network/Utils.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Data.Conduit.Network.Utils+    ( -- * Helper utilities+      HostPreference (..)+    , bindPort+    , getSocket+    ) where++import Network.Socket (AddrInfo, Socket, SocketType)+import qualified Network.Socket as NS+import Data.String (IsString (fromString))+import Control.Exception (bracketOnError, IOException)+import qualified Control.Exception as E++-- | Attempt to connect to the given host/port using given @SocketType@.+getSocket :: String -> Int -> SocketType -> IO (Socket, AddrInfo)+getSocket host' port' sockettype = do+    let hints = NS.defaultHints {+                          NS.addrFlags = [NS.AI_ADDRCONFIG]+                        , NS.addrSocketType = sockettype+                        }+    (addr:_) <- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')+    sock <- NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)+                      (NS.addrProtocol addr)+    return (sock, addr)++-- | Which host to bind.+--+-- Note: The @IsString@ instance recognizes the following special values:+--+-- * @*@ means @HostAny@+--+-- * @*4@ means @HostIPv4@+--+-- * @*6@ means @HostIPv6@+data HostPreference =+    HostAny+  | HostIPv4+  | HostIPv6+  | Host String+    deriving (Eq, Ord, Show, Read)++instance IsString HostPreference where+    -- The funny code coming up is to get around some irritating warnings from+    -- GHC. I should be able to just write:+    {-+    fromString "*" = HostAny+    fromString "*4" = HostIPv4+    fromString "*6" = HostIPv6+    -}+    fromString s'@('*':s) =+        case s of+            [] -> HostAny+            ['4'] -> HostIPv4+            ['6'] -> HostIPv6+            _ -> Host s'+    fromString s = Host s++-- | Attempt to bind a listening @Socket@ on the given host/port using given+-- @SocketType@. If no host is given, will use the first address available.+bindPort :: Int -> HostPreference -> SocketType -> IO Socket+bindPort p s sockettype = do+    let hints = NS.defaultHints+            { NS.addrFlags = [ NS.AI_PASSIVE+                             , NS.AI_NUMERICSERV+                             , NS.AI_NUMERICHOST+                             ]+            , NS.addrSocketType = sockettype+            }+        host =+            case s of+                Host s' -> Just s'+                _ -> Nothing+        port = Just . show $ p+    addrs <- NS.getAddrInfo (Just hints) host port+    -- Choose an IPv6 socket if exists.  This ensures the socket can+    -- handle both IPv4 and IPv6 if v6only is false.+    let addrs4 = filter (\x -> NS.addrFamily x /= NS.AF_INET6) addrs+        addrs6 = filter (\x -> NS.addrFamily x == NS.AF_INET6) addrs+        addrs' =+            case s of+                HostIPv4 -> addrs4 ++ addrs6+                HostIPv6 -> addrs6 ++ addrs4+                _ -> addrs++        tryAddrs (addr1:rest@(_:_)) =+                                      E.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)+              return sock+          )+    tryAddrs addrs'
network-conduit.cabal view
@@ -1,5 +1,5 @@ Name:                network-conduit-Version:             0.5.0+Version:             0.5.0.1 Synopsis:            Stream socket data using conduits. Description:         Stream socket data using conduits. License:             BSD3@@ -17,6 +17,8 @@  Library   Exposed-modules:     Data.Conduit.Network+                     , Data.Conduit.Network.UDP+  other-modules:       Data.Conduit.Network.Utils   Build-depends:       base                     >= 4            && < 5                      , transformers             >= 0.2.2        && < 0.4                      , bytestring               >= 0.9