{-# LANGUAGE RankNTypes #-} -- | This minimal module exports facilities that ease the usage of TCP -- 'Socket's in the /Pipes ecosystem/. It is meant to be used together with -- the "Network.Simple.TCP" module from the @network-simple@ package, which is -- completely re-exported from this module. -- -- This module /does not/ export facilities that would allow you to acquire new -- 'Socket's within a pipeline. If you need to do so, then you should use -- "Pipes.Network.TCP.Safe" instead, which exports a similar API to the one -- exported by this module. However, don't be confused by the word “safe” in -- that module; this module is equally safe to use as long as you don't try to -- acquire resources within the pipeline. module Pipes.Network.TCP ( -- * Receiving -- $receiving fromSocket , fromSocketTimeout -- ** Bidirectional pipes -- $bidirectional , fromSocketN , fromSocketTimeoutN -- * Sending -- $sending , toSocket , toSocketTimeout -- * Exports -- $exports , module Network.Simple.TCP ) where import qualified Data.ByteString as B import Foreign.C.Error (errnoToIOError, eTIMEDOUT) import qualified Network.Socket.ByteString as NSB import Network.Simple.TCP (connect, serve, listen, accept, acceptFork, bindSock, connectSock, recv, send, withSocketsDo, HostName, HostPreference(HostAny, HostIPv4, HostIPv6, Host), ServiceName, SockAddr, Socket) import Pipes import Pipes.Core import System.Timeout (timeout) -------------------------------------------------------------------------------- -- $receiving -- -- The following pipes allow you to receive bytes from the remote end. -- -- Besides the pipes below, you might want to use "Network.Simple.TCP"'s -- 'Network.Simple.TCP.recv', which happens to be an 'Effect'': -- -- @ -- 'Network.Simple.TCP.recv' :: 'MonadIO' m => 'Socket' -> 'Int' -> 'Effect'' m ('Maybe' 'B.ByteString') -- @ -------------------------------------------------------------------------------- -- | Receives bytes from the remote end and sends them downstream. -- -- The number of bytes received at once is always in the interval -- /[1 .. specified maximum]/. -- -- This 'Producer'' returns if the remote peer closes its side of the connection -- or EOF is received. fromSocket :: MonadIO m => Socket -- ^Connected socket. -> Int -- ^Maximum number of bytes to receive and send -- dowstream at once. Any positive value is fine, the -- optimal value depends on how you deal with the -- received data. Try using @4096@ if you don't care. -> Producer' B.ByteString m () fromSocket sock nbytes = loop where loop = do bs <- liftIO (NSB.recv sock nbytes) if B.null bs then return () else yield bs >> loop {-# INLINABLE fromSocket #-} -- | Like 'fromSocket', except with the first 'Int' argument you can specify -- the maximum time that each interaction with the remote end can take. If such -- time elapses before the interaction finishes, then an 'IOError' exception is -- thrown. The time is specified in microseconds (1 second = 1e6 microseconds). fromSocketTimeout :: MonadIO m => Int -> Socket -> Int -> Producer' B.ByteString m () fromSocketTimeout wait sock nbytes = loop where loop = do mbs <- liftIO (timeout wait (NSB.recv sock nbytes)) case mbs of Just bs -> yield bs >> loop Nothing -> liftIO $ ioError $ errnoToIOError "Pipes.Network.TCP.fromSocketTimeout" eTIMEDOUT Nothing Nothing {-# INLINABLE fromSocketTimeout #-} -------------------------------------------------------------------------------- -- $bidirectional -- -- The following pipes are bidirectional, which means useful data can flow -- through them upstream and downstream. If you don't care about bidirectional -- pipes, just skip this section. -------------------------------------------------------------------------------- -- | Like 'fromSocket', except the downstream pipe can specify the maximum -- number of bytes to receive at once using 'request'. fromSocketN :: MonadIO m => Socket -> Int -> Server' Int B.ByteString m () fromSocketN sock = loop where loop = \nbytes -> do bs <- liftIO (NSB.recv sock nbytes) if B.null bs then return () else respond bs >>= loop {-# INLINABLE fromSocketN #-} -- | Like 'fromSocketN', except with the first 'Int' argument you can specify -- the maximum time that each interaction with the remote end can take. If such -- time elapses before the interaction finishes, then an 'IOError' exception is -- thrown. The time is specified in microseconds (1 second = 1e6 microseconds). fromSocketTimeoutN :: MonadIO m => Int -> Socket -> Int -> Server' Int B.ByteString m () fromSocketTimeoutN wait sock = loop where loop = \nbytes -> do mbs <- liftIO (timeout wait (NSB.recv sock nbytes)) case mbs of Just bs -> respond bs >>= loop Nothing -> liftIO $ ioError $ errnoToIOError "Pipes.Network.TCP.fromSocketTimeoutN" eTIMEDOUT Nothing Nothing {-# INLINABLE fromSocketTimeoutN #-} -------------------------------------------------------------------------------- -- $sending -- -- The following pipes allow you to send bytes to the remote end. -- -- Besides the pipes below, you might want to use "Network.Simple.TCP"'s -- 'Network.Simple.TCP.send', which happens to be an 'Effect'': -- -- @ -- 'Network.Simple.TCP.send' :: 'MonadIO' m => 'Socket' -> 'B.ByteString' -> 'Effect'' m () -- @ -- | Sends to the remote end each 'B.ByteString' received from upstream. toSocket :: MonadIO m => Socket -- ^Connected socket. -> Consumer' B.ByteString m r toSocket sock = for cat (\a -> send sock a) {-# INLINABLE toSocket #-} -- | Like 'toSocket', except with the first 'Int' argument you can specify -- the maximum time that each interaction with the remote end can take. If such -- time elapses before the interaction finishes, then an 'IOError' exception is -- thrown. The time is specified in microseconds (1 second = 1e6 microseconds). toSocketTimeout :: MonadIO m => Int -> Socket -> Consumer' B.ByteString m r toSocketTimeout wait sock = for cat $ \a -> do mu <- liftIO (timeout wait (NSB.sendAll sock a)) case mu of Just () -> return () Nothing -> liftIO $ ioError $ errnoToIOError "Pipes.Network.TCP.toSocketTimeout" eTIMEDOUT Nothing Nothing {-# INLINABLE toSocketTimeout #-} -------------------------------------------------------------------------------- -- $exports -- -- [From "Network.Simple.TCP"] -- 'accept', -- 'acceptFork', -- 'bindSock', -- 'connect', -- 'connectSock', -- 'HostPreference'('HostAny','HostIPv4','HostIPv6','Host'), -- 'listen', -- 'recv', -- 'send', -- 'serve'. -- -- [From "Network.Socket"] -- 'HostName', -- 'ServiceName', -- 'SockAddr', -- 'Socket', -- 'withSocketsDo'.