on 43dda21 help sign in
457 lines 15 kB history raw
1#include "inline.hs"
3-- |
4-- Module : Streamly.Internal.Network.Inet.TCP
5-- Copyright : (c) 2019 Composewell Technologies
6--
7-- License : BSD3
8-- Maintainer : streamly@composewell.com
9-- Stability : experimental
10-- Portability : GHC
11--
12-- Combinators to build Inet/TCP clients and servers.
14module Streamly.Internal.Network.Inet.TCP
15 (
16 -- * TCP Servers
17 -- ** Unfolds
18 acceptOnAddr
19 , acceptOnAddrWith
20 , acceptOnPort
21 , acceptOnPortWith
22 , acceptOnPortLocal
24 -- ** Streams
25 , connectionsOnAddr
26 , connectionsOnAddrWith
27 , connectionsOnPort
28 , connectionsOnLocalHost
30 -- * TCP clients
31 -- | IP Address based operations.
32 , connect
33 , withConnectionM
35 -- ** Unfolds
36 , usingConnection
37 , read
39 -- ** Streams
40 , withConnection
41 -- *** Source
42 , toBytes
43 -- , readUtf8
44 -- , readLines
45 -- , readFrames
46 -- , readByChunks
48 -- -- * Array Read
49 -- , readArrayUpto
50 -- , readArrayOf
52 -- , readChunksUpto
53 -- , readChunksOf
54 -- , readChunks
56 -- *** Sink
57 , write
58 -- , writeUtf8
59 -- , writeUtf8ByLines
60 -- , writeByFrames
61 , writeWithBufferOf
62 , putBytes
63 , putBytesWithBufferOf
65 -- -- * Array Write
66 -- , writeArray
67 , writeChunks
68 , putChunks
70 -- ** Transformation
71 , processBytes
72 {-
73 -- ** Sink Servers
75 -- These abstractions can be applied to any setting where we need to do a
76 -- sink processing of multiple streams e.g. output from multiple processes
77 -- or data coming from multiple files.
79 -- handle connections concurrently using a specified fold
80 -- , handleConnections
82 -- handle frames concurrently using a specified fold
83 , handleFrames
85 -- merge frames from all connection into a single stream. Frames can be
86 -- created by a specified fold.
87 , mergeFrames
89 -- * UDP Servers
90 , datagrams
91 , datagramsOn
92 -}
93 )
94where
96import Control.Exception (onException)
97import Control.Monad.Catch (MonadCatch, MonadMask, bracket)
98import Control.Monad.IO.Class (MonadIO(..))
99import Data.Word (Word8)
100import Network.Socket
101 (Socket, PortNumber, SocketOption(..), Family(..), SockAddr(..),
102 SocketType(..), defaultProtocol, maxListenQueue, tupleToHostAddress,
103 socket)
104import Prelude hiding (read)
106import Streamly.Internal.Control.Concurrent (MonadAsync)
107import Streamly.Internal.Control.ForkLifted (fork)
108import Streamly.Internal.Data.Array.Foreign.Type (Array(..), writeNUnsafe)
109import Streamly.Internal.Data.Fold.Type (Fold(..))
110import Streamly.Internal.Data.Stream.IsStream.Type (IsStream)
111import Streamly.Internal.Data.Stream.Serial (SerialT)
112import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
113import Streamly.Internal.Data.Unfold.Type (Unfold(..))
114import Streamly.Internal.Network.Socket (SockSpec(..), accept, connections)
115import Streamly.Internal.System.IO (defaultChunkSize)
117import qualified Control.Monad.Catch as MC
118import qualified Network.Socket as Net
120import qualified Streamly.Internal.Data.Unfold as UF
121import qualified Streamly.Internal.Data.Array.Foreign as A
122import qualified Streamly.Internal.Data.Array.Stream.Foreign as AS
123import qualified Streamly.Internal.Data.Fold.Type as FL
124import qualified Streamly.Internal.Data.Stream.IsStream as S
125import qualified Streamly.Internal.Network.Socket as ISK
127-------------------------------------------------------------------------------
128-- Accept (unfolds)
129-------------------------------------------------------------------------------
131{-# INLINE acceptOnAddrWith #-}
132acceptOnAddrWith
133 :: MonadIO m
134 => [(SocketOption, Int)]
135 -> Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Socket
136acceptOnAddrWith opts = UF.lmap f accept
137 where
138 f (addr, port) =
139 (maxListenQueue
140 , SockSpec
141 { sockFamily = AF_INET
142 , sockType = Stream
143 , sockProto = defaultProtocol -- TCP
144 , sockOpts = opts
145 }
146 , SockAddrInet port (tupleToHostAddress addr)
147 )
149-- | Unfold a tuple @(ipAddr, port)@ into a stream of connected TCP sockets.
150-- @ipAddr@ is the local IP address and @port@ is the local port on which
151-- connections are accepted.
152--
153-- @since 0.7.0
154{-# INLINE acceptOnAddr #-}
155acceptOnAddr
156 :: MonadIO m
157 => Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Socket
158acceptOnAddr = acceptOnAddrWith []
160{-# INLINE acceptOnPortWith #-}
161acceptOnPortWith :: MonadIO m
162 => [(SocketOption, Int)]
163 -> Unfold m PortNumber Socket
164acceptOnPortWith opts = UF.first (0,0,0,0) (acceptOnAddrWith opts)
166-- | Like 'acceptOnAddr' but binds on the IPv4 address @0.0.0.0@ i.e. on all
167-- IPv4 addresses/interfaces of the machine and listens for TCP connections on
168-- the specified port.
169--
170-- > acceptOnPort = UF.first acceptOnAddr (0,0,0,0)
171--
172-- @since 0.7.0
173{-# INLINE acceptOnPort #-}
174acceptOnPort :: MonadIO m => Unfold m PortNumber Socket
175acceptOnPort = UF.first (0,0,0,0) acceptOnAddr
177-- | Like 'acceptOnAddr' but binds on the localhost IPv4 address @127.0.0.1@.
178-- The server can only be accessed from the local host, it cannot be accessed
179-- from other hosts on the network.
180--
181-- > acceptOnPortLocal = UF.first acceptOnAddr (127,0,0,1)
182--
183-- @since 0.7.0
184{-# INLINE acceptOnPortLocal #-}
185acceptOnPortLocal :: MonadIO m => Unfold m PortNumber Socket
186acceptOnPortLocal = UF.first (127,0,0,1) acceptOnAddr
188-------------------------------------------------------------------------------
189-- Accept (streams)
190-------------------------------------------------------------------------------
192{-# INLINE connectionsOnAddrWith #-}
193connectionsOnAddrWith
194 :: MonadAsync m
195 => [(SocketOption, Int)]
196 -> (Word8, Word8, Word8, Word8)
197 -> PortNumber
198 -> SerialT m Socket
199connectionsOnAddrWith opts addr port =
200 connections maxListenQueue SockSpec
201 { sockFamily = AF_INET
202 , sockType = Stream
203 , sockProto = defaultProtocol
204 , sockOpts = opts
205 }
206 (SockAddrInet port (tupleToHostAddress addr))
208-- | Like 'connections' but binds on the specified IPv4 address of the machine
209-- and listens for TCP connections on the specified port.
210--
211-- /Pre-release/
212{-# INLINE connectionsOnAddr #-}
213connectionsOnAddr
214 :: MonadAsync m
215 => (Word8, Word8, Word8, Word8)
216 -> PortNumber
217 -> SerialT m Socket
218connectionsOnAddr = connectionsOnAddrWith []
220-- | Like 'connections' but binds on the IPv4 address @0.0.0.0@ i.e. on all
221-- IPv4 addresses/interfaces of the machine and listens for TCP connections on
222-- the specified port.
223--
224-- > connectionsOnPort = connectionsOnAddr (0,0,0,0)
225--
226-- /Pre-release/
227{-# INLINE connectionsOnPort #-}
228connectionsOnPort :: MonadAsync m => PortNumber -> SerialT m Socket
229connectionsOnPort = connectionsOnAddr (0,0,0,0)
231-- | Like 'connections' but binds on the localhost IPv4 address @127.0.0.1@.
232-- The server can only be accessed from the local host, it cannot be accessed
233-- from other hosts on the network.
234--
235-- > connectionsOnLocalHost = connectionsOnAddr (127,0,0,1)
236--
237-- /Pre-release/
238{-# INLINE connectionsOnLocalHost #-}
239connectionsOnLocalHost :: MonadAsync m => PortNumber -> SerialT m Socket
240connectionsOnLocalHost = connectionsOnAddr (127,0,0,1)
242-------------------------------------------------------------------------------
243-- TCP Clients
244-------------------------------------------------------------------------------
246-- | Connect to the specified IP address and port number. Returns a connected
247-- socket or throws an exception.
248--
249-- @since 0.7.0
250connect :: (Word8, Word8, Word8, Word8) -> PortNumber -> IO Socket
251connect addr port = do
252 sock <- socket AF_INET Stream defaultProtocol
253 Net.connect sock (SockAddrInet port (Net.tupleToHostAddress addr))
254 `onException` Net.close sock
255 return sock
257-- | Connect to a remote host using IP address and port and run the supplied
258-- action on the resulting socket. 'withConnectionM' makes sure that the
259-- socket is closed on normal termination or in case of an exception. If
260-- closing the socket raises an exception, then this exception will be raised
261-- by 'withConnectionM'.
262--
263-- /Pre-release/
264{-# INLINABLE withConnectionM #-}
265withConnectionM :: (MonadMask m, MonadIO m)
266 => (Word8, Word8, Word8, Word8) -> PortNumber -> (Socket -> m ()) -> m ()
267withConnectionM addr port =
268 bracket (liftIO $ connect addr port) (liftIO . Net.close)
270-------------------------------------------------------------------------------
271-- Connect (unfolds)
272-------------------------------------------------------------------------------
274-- | Transform an 'Unfold' from a 'Socket' to an unfold from a remote IP
275-- address and port. The resulting unfold opens a socket, uses it using the
276-- supplied unfold and then makes sure that the socket is closed on normal
277-- termination or in case of an exception. If closing the socket raises an
278-- exception, then this exception will be raised by 'usingConnection'.
279--
280-- /Pre-release/
281{-# INLINE usingConnection #-}
282usingConnection :: (MonadCatch m, MonadAsync m)
283 => Unfold m Socket a
284 -> Unfold m ((Word8, Word8, Word8, Word8), PortNumber) a
285usingConnection =
286 UF.bracket (\(addr, port) -> liftIO $ connect addr port)
287 (liftIO . Net.close)
289-------------------------------------------------------------------------------
290-- Connect (streams)
291-------------------------------------------------------------------------------
293-- | @'withConnection' addr port act@ opens a connection to the specified IPv4
294-- host address and port and passes the resulting socket handle to the
295-- computation @act@. The handle will be closed on exit from 'withConnection',
296-- whether by normal termination or by raising an exception. If closing the
297-- handle raises an exception, then this exception will be raised by
298-- 'withConnection' rather than any exception raised by 'act'.
299--
300-- /Pre-release/
301{-# INLINE withConnection #-}
302withConnection :: (IsStream t, MonadCatch m, MonadAsync m)
303 => (Word8, Word8, Word8, Word8) -> PortNumber -> (Socket -> t m a) -> t m a
304withConnection addr port =
305 S.bracket (liftIO $ connect addr port) (liftIO . Net.close)
307-------------------------------------------------------------------------------
308-- Read Addr to Stream
309-------------------------------------------------------------------------------
311-- | Read a stream from the supplied IPv4 host address and port number.
312--
313-- @since 0.7.0
314{-# INLINE read #-}
315read :: (MonadCatch m, MonadAsync m)
316 => Unfold m ((Word8, Word8, Word8, Word8), PortNumber) Word8
317read = UF.many A.read (usingConnection ISK.readChunks)
319-- | Read a stream from the supplied IPv4 host address and port number.
320--
321-- @since 0.7.0
322{-# INLINE toBytes #-}
323toBytes :: (IsStream t, MonadCatch m, MonadAsync m)
324 => (Word8, Word8, Word8, Word8) -> PortNumber -> t m Word8
325toBytes addr port = AS.concat $ withConnection addr port ISK.toChunks
327-------------------------------------------------------------------------------
328-- Writing
329-------------------------------------------------------------------------------
331-- | Write a stream of arrays to the supplied IPv4 host address and port
332-- number.
333--
334-- @since 0.7.0
335{-# INLINE putChunks #-}
336putChunks
337 :: (MonadCatch m, MonadAsync m)
338 => (Word8, Word8, Word8, Word8)
339 -> PortNumber
340 -> SerialT m (Array Word8)
341 -> m ()
342putChunks addr port xs =
343 S.drain $ withConnection addr port (\sk -> S.fromEffect $ ISK.putChunks sk xs)
345-- | Write a stream of arrays to the supplied IPv4 host address and port
346-- number.
347--
348-- @since 0.7.0
349{-# INLINE writeChunks #-}
350writeChunks
351 :: (MonadAsync m, MonadCatch m)
352 => (Word8, Word8, Word8, Word8)
353 -> PortNumber
354 -> Fold m (Array Word8) ()
355writeChunks addr port = Fold step initial extract
356 where
357 initial = do
358 skt <- liftIO (connect addr port)
359 fld <- FL.initialize (ISK.writeChunks skt)
360 `MC.onException` liftIO (Net.close skt)
361 return $ FL.Partial (Tuple' fld skt)
362 step (Tuple' fld skt) x = do
363 r <- FL.snoc fld x `MC.onException` liftIO (Net.close skt)
364 return $ FL.Partial (Tuple' r skt)
365 extract (Tuple' (Fold _ initial1 extract1) skt) = do
366 liftIO $ Net.close skt
367 res <- initial1
368 case res of
369 FL.Partial fs -> extract1 fs
370 FL.Done fb -> return fb
372-- | Like 'write' but provides control over the write buffer. Output will
373-- be written to the IO device as soon as we collect the specified number of
374-- input elements.
375--
376-- @since 0.7.0
377{-# INLINE putBytesWithBufferOf #-}
378putBytesWithBufferOf
379 :: (MonadCatch m, MonadAsync m)
380 => Int
381 -> (Word8, Word8, Word8, Word8)
382 -> PortNumber
383 -> SerialT m Word8
384 -> m ()
385putBytesWithBufferOf n addr port m = putChunks addr port $ AS.arraysOf n m
387-- | Like 'write' but provides control over the write buffer. Output will
388-- be written to the IO device as soon as we collect the specified number of
389-- input elements.
390--
391-- @since 0.7.0
392{-# INLINE writeWithBufferOf #-}
393writeWithBufferOf
394 :: (MonadAsync m, MonadCatch m)
395 => Int
396 -> (Word8, Word8, Word8, Word8)
397 -> PortNumber
398 -> Fold m Word8 ()
399writeWithBufferOf n addr port =
400 FL.chunksOf n (writeNUnsafe n) (writeChunks addr port)
402-- | Write a stream to the supplied IPv4 host address and port number.
403--
404-- @since 0.7.0
405{-# INLINE putBytes #-}
406putBytes :: (MonadCatch m, MonadAsync m)
407 => (Word8, Word8, Word8, Word8) -> PortNumber -> SerialT m Word8 -> m ()
408putBytes = putBytesWithBufferOf defaultChunkSize
410-- | Write a stream to the supplied IPv4 host address and port number.
411--
412-- @since 0.7.0
413{-# INLINE write #-}
414write :: (MonadAsync m, MonadCatch m)
415 => (Word8, Word8, Word8, Word8) -> PortNumber -> Fold m Word8 ()
416write = writeWithBufferOf defaultChunkSize
418-------------------------------------------------------------------------------
419-- Transformations
420-------------------------------------------------------------------------------
422{-# INLINE withInputConnect #-}
423withInputConnect
424 :: (IsStream t, MonadCatch m, MonadAsync m)
425 => (Word8, Word8, Word8, Word8)
426 -> PortNumber
427 -> SerialT m Word8
428 -> (Socket -> t m a)
429 -> t m a
430withInputConnect addr port input f = S.bracket pre post handler
432 where
434 pre = do
435 sk <- liftIO $ connect addr port
436 tid <- fork (ISK.putBytes sk input)
437 return (sk, tid)
439 handler (sk, _) = f sk
441 -- XXX kill the thread immediately?
442 post (sk, _) = liftIO $ Net.close sk
444-- | Send an input stream to a remote host and produce the output stream from
445-- the host. The server host just acts as a transformation function on the
446-- input stream. Both sending and receiving happen asynchronously.
447--
448-- /Pre-release/
449--
450{-# INLINE processBytes #-}
451processBytes
452 :: (IsStream t, MonadAsync m, MonadCatch m)
453 => (Word8, Word8, Word8, Word8)
454 -> PortNumber
455 -> SerialT m Word8
456 -> t m Word8
457processBytes addr port input = withInputConnect addr port input ISK.toBytes