packages feed

network-simple (empty) → 0.1.0.0

raw patch · 7 files changed

+410/−0 lines, 7 filesdep +basedep +networksetup-changed

Dependencies added: base, network

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2013, Renzo Carbonara <renzocarbonaraλgmail.com>++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Renzo Carbonara nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ PEOPLE view
@@ -0,0 +1,5 @@+The following people have participated in creating this library, either+by directly contributing code or by providing thoughtful input in+discussions about the library design.++Renzo Carbonara
+ README.md view
@@ -0,0 +1,14 @@+# network-simple++Haskell library abstracring over simple network sockets usage patterns.+Currently, only TCP sockets are supported. Support for UDP and Unix+sockets is planned for future versions.++Check the source or rendered Haddocks for extensive documentation.++This code is licensed under the terms of the so called **3-clause BSD+license**. Read the file named ``LICENSE`` found in this same directory+for details.++See the ``PEOPLE`` file to learn about the people involved in this+effort.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ network-simple.cabal view
@@ -0,0 +1,26 @@+name:                network-simple+version:             0.1.0.0+synopsis:            Abstract simple network sockets usage patterns.+description:         Abstract simple network sockets usage patterns.+homepage:            https://github.com/k0001/network-simple+bug-reports:         https://github.com/k0001/network-simple/issues+license:             BSD3+license-file:        LICENSE+author:              Renzo Carbonara+maintainer:          renzocarbonaraaλgmail.com+copyright:           Copyright (c) Renzo Carbonara 2013+category:            Network+build-type:          Simple+cabal-version:       >=1.8+extra-source-files:  README.md PEOPLE++source-repository head+    type: git+    location: git://github.com/k0001/network-simple.git++library+  hs-source-dirs:    src+  exposed-modules:   Network.Simple.TCP+  other-modules:     Network.Simple.Internal+  build-depends:     base (>=4.5 && < 5)+                   , network (>=2.3 && <2.5)
+ src/Network/Simple/Internal.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# OPTIONS_HADDOCK hide #-}++-- Some code in this file was adapted from the @network-conduit@ library by+-- Michael Snoyman. Copyright (c) 2011. See its licensing terms (BSD3) at:+--   https://github.com/snoyberg/conduit/blob/master/network-conduit/LICENSE++module Network.Simple.Internal+  ( HostPreference(..)+  , hpHostName+  ) where++import           Data.String                   (IsString (fromString))+import qualified Network.Socket as             NS++-- | Preferred host to bind.+data HostPreference+  = HostAny          -- ^Any available host.+  | HostIPv4         -- ^Any available IPv4 host.+  | HostIPv6         -- ^Any available IPv6 host.+  | Host NS.HostName -- ^An explicit host name.+  deriving (Eq, Ord, Show, Read)++-- | The following special values are recognized:+--+-- * @*@ means 'HostAny'+--+-- * @*4@ means 'HostIPv4'+--+-- * @*6@ means 'HostIPv6'+--+-- * Any other string is 'Host'+instance IsString HostPreference where+  fromString "*"  = HostAny+  fromString "*4" = HostIPv4+  fromString "*6" = HostIPv6+  fromString s    = Host s++-- | Extract the 'NS.HostName' from a 'Host' preference, or 'Nothing' otherwise.+hpHostName:: HostPreference -> Maybe NS.HostName+hpHostName (Host s) = Just s+hpHostName _        = Nothing+
+ src/Network/Simple/TCP.hs view
@@ -0,0 +1,290 @@+-- | This module exports functions that abstract simple TCP 'NS.Socket'+-- usage patterns.++-- Some code in this file was adapted from the @pipes-network@ library by+-- Renzo Carbonara. Copyright (c) 2012-2013. See its licensing terms (BSD3) at:+--   https://github.com/k0001/pipes-network/blob/master/LICENSE+--+-- Some code in this file was adapted from the @network-conduit@ library by+-- Michael Snoyman. Copyright (c) 2011. See its licensing terms (BSD3) at:+--   https://github.com/snoyberg/conduit/blob/master/network-conduit/LICENSE+++module Network.Simple.TCP (+  -- * Introduction to TCP networking+  -- $tcp-101++  -- * Server side+  -- $server-side+  serve,+  serveFork,+  -- ** Listening+  listen,+  -- ** Accepting+  accept,+  acceptFork,++  -- * Client side+  -- $client-side+  connect,++  -- * Low level support+  bindSock,+  connectSock,++  -- * Exports+  HostPreference(..),+  ) where++import           Control.Concurrent             (ThreadId, forkIO)+import qualified Control.Exception              as E+import           Control.Monad+import           Data.List                      (partition)+import qualified Network.Socket                 as NS+import           Network.Simple.Internal++--------------------------------------------------------------------------------+-- $tcp-101+--+-- This introduction aims to give you a overly simplified overview of some+-- concepts you need to know about TCP sockets in order to make effective use of+-- this module.+--+-- There's two ends in a single TCP connection: one is the TCP «server» and the+-- other is the TCP «client». Each end is uniquely identified by an IP address+-- and a TCP port pair, and each end knows the IP address and TCP port of the+-- other end. Each end can send and receive data to and from the other end.+--+-- A TCP server, once «bound» to a well-known IP address and TCP port, starts+-- «listening» for incoming connections from TCP clients to such bound IP+-- address and TCP port. When a TCP client attempts to connect to the TCP+-- server, the TCP server must «accept» the incoming connection in order to+-- start exchanging data with the remote end. A single TCP server can+-- sequentially accept many incoming connections, possibly handling each one+-- concurrently.+--+-- A TCP client can «connect» to a well-known IP address and TCP port previously+-- bound by a listening TCP server willing to accept new incoming connections.+-- Once the connection is established, the TCP client can immediately start+-- exchanging data with the TCP server. The TCP client is randomly assigned a+-- TCP port when connecting, and its IP address is selected by the operating+-- system so that it is reachable from the remote end.+--+-- The TCP client a and the TCP server can be running in the same host or in+-- different hosts.++--------------------------------------------------------------------------------++-- $client-side+--+-- The following functions allow you to obtain and use 'NS.Socket's useful to+-- the client side of a TCP connection.+--+-- Here's how you could run a TCP client:+--+-- > connect "www.example.org" "80" $ \(connectionSocket, remoteAddr) -> do+-- >   putStrLn $ "Connection established to " ++ show remoteAddr+-- >   -- now you may use connectionSocket as you please within this scope.++-- | Connect to a TCP server and use the connection.+--+-- The connection socket is closed when done or in case of exceptions.+--+-- If you prefer to acquire and close the socket yourself, then use+-- 'connectSock' and the 'NS.sClose' function from "Network.Socket" instead.+connect+  :: NS.HostName      -- ^Server hostname.+  -> NS.ServiceName   -- ^Server service port.+  -> ((NS.Socket, NS.SockAddr) -> IO r)+                      -- ^Computation taking the communication socket+                      -- and the server address.+  -> IO r+connect host port = E.bracket (connectSock host port) (NS.sClose . fst)++--------------------------------------------------------------------------------++-- $server-side+--+-- The following functions allow you to obtain and use 'NS.Socket's useful to+-- the server side of a TCP connection.+--+-- Here's how you could run a TCP server that handles in different threads each+-- incoming connection to port @8000@ at IPv4 address @127.0.0.1@:+--+-- > listen (Host "127.0.0.1") "8000" $ \(listeningSocket, listeningAddr) -> do+-- >   putStrLn $ "Listening for incoming connections at " ++ show listeningAddr+-- >   forever . acceptFork listeningSocket $ \(connectionSocket, remoteAddr) -> do+-- >     putStrLn $ "Connection established from " ++ show remoteAddr+-- >     -- now you may use connectionSocket as you please within this scope.+--+-- If you keep reading you'll discover there are different ways to achieve+-- the same, some ways more general than others. The above one was just an+-- example using a pretty general approach, you are encouraged to use simpler+-- approaches such as 'serve' if those suit your needs.++-- | Bind a TCP listening socket and use it.+--+-- The listening socket is closed when done or in case of exceptions.+--+-- If you prefer to acquire and close the socket yourself, then use+-- 'bindSock' and the 'NS.listen' and 'NS.sClose' functions from+-- "Network.Socket" instead.+--+-- Note: 'N.maxListenQueue' is tipically 128, which is too small for high+-- performance servers. So, we use the maximum between 'N.maxListenQueue' and+-- 2048 as the default size of the listening queue. The 'NS.NoDelay' and+-- 'NS.ReuseAddr' options are set on the socket.+listen+  :: HostPreference   -- ^Preferred host to bind.+  -> NS.ServiceName   -- ^Service port to bind.+  -> ((NS.Socket, NS.SockAddr) -> IO r)+                      -- ^Computation taking the listening socket and+                      -- the address it's bound to.+  -> IO r+listen hp port = E.bracket listen' (NS.sClose . fst)+  where+    listen' = do x@(bsock,_) <- bindSock hp port+                 NS.listen bsock $ max 2048 NS.maxListenQueue+                 return x++-- | Start a TCP server that sequentially accepts and uses each incoming+-- connection.+--+-- Both the listening and connection sockets are closed when done or in case of+-- exceptions.+--+-- Note: You don't need to use 'listen' nor 'accept' manually if you use this+-- function.+serve+  :: HostPreference   -- ^Preferred host to bind.+  -> NS.ServiceName   -- ^Service port to bind.+  -> ((NS.Socket, NS.SockAddr) -> IO r)+                      -- ^Computation to run once an incoming+                      -- connection is accepted. Takes the connection socket+                      -- and remote end address.+  -> IO r+serve hp port k = do+    listen hp port $ \(lsock,_) -> do+      forever $ accept lsock k++-- | Start a TCP server that accepts incoming connections and uses them+-- concurrently in different threads.+--+-- The listening and connection sockets are closed when done or in case of+-- exceptions.+--+-- Note: You don't need to use 'listen' nor 'acceptFork' manually if you use+-- this function.+serveFork+  :: HostPreference   -- ^Preferred host to bind.+  -> NS.ServiceName   -- ^Service port to bind.+  -> ((NS.Socket, NS.SockAddr) -> IO ())+                      -- ^Computation to run in a different thread+                      -- once an incoming connection is accepted. Takes the+                      -- connection socket and remote end address.+  -> IO ()+serveFork hp port k = do+    listen hp port $ \(lsock,_) -> do+      forever $ acceptFork lsock k++-- | Accept a single incoming connection and use it.+--+-- The connection socket is closed when done or in case of exceptions.+accept+  :: NS.Socket        -- ^Listening and bound socket.+  -> ((NS.Socket, NS.SockAddr) -> IO b)+                      -- ^Computation to run once an incoming+                      -- connection is accepted. Takes the connection socket+                      -- and remote end address.+  -> IO b+accept lsock k = do+    conn@(csock,_) <- NS.accept lsock+    E.finally (k conn) (NS.sClose csock)+{-# INLINABLE accept #-}++-- | Accept a single incoming connection and use it in a different thread.+--+-- The connection socket is closed when done or in case of exceptions.+acceptFork+  :: NS.Socket        -- ^Listening and bound socket.+  -> ((NS.Socket, NS.SockAddr) -> IO ())+                      -- ^Computation to run in a different thread+                      -- once an incoming connection is accepted. Takes the+                      -- connection socket and remote end address.+  -> IO ThreadId+acceptFork lsock f = do+    client@(csock,_) <- NS.accept lsock+    forkIO $ E.finally (f client) (NS.sClose csock)+{-# INLINABLE acceptFork #-}++--------------------------------------------------------------------------------++-- | Obtain a 'NS.Socket' connected to the given host and TCP service port.+--+-- The obtained 'NS.Socket' should be closed manually using 'NS.sClose' when+-- it's not needed anymore, otherwise you risk having the socket open for much+-- longer than needed.+--+-- Prefer to use 'connect' if you will be using the socket within a limited+-- scope and would like it to be closed immediately after its usage or in case+-- of exceptions.+connectSock :: NS.HostName -> NS.ServiceName -> IO (NS.Socket, NS.SockAddr)+connectSock host port = do+    (addr:_) <- NS.getAddrInfo (Just hints) (Just host) (Just port)+    E.bracketOnError (newSocket addr) NS.sClose $ \sock -> do+       let sockAddr = NS.addrAddress addr+       NS.connect sock sockAddr+       return (sock, sockAddr)+  where+    hints = NS.defaultHints { NS.addrFlags = [NS.AI_ADDRCONFIG]+                            , NS.addrSocketType = NS.Stream }++-- | Obtain a 'NS.Socket' bound to the given host name and TCP service port.+--+-- The obtained 'NS.Socket' should be closed manually using 'NS.sClose' when+-- it's not needed anymore.+--+-- Prefer to use 'listen' if you will be listening on this socket and using it+-- within a limited scope, and would like it to be closed immediately after its+-- usage or in case of exceptions.+bindSock :: HostPreference -> NS.ServiceName -> IO (NS.Socket, NS.SockAddr)+bindSock hp port = do+    addrs <- NS.getAddrInfo (Just hints) (hpHostName hp) (Just port)+    let addrs' = case hp of+          HostIPv4 -> prioritize isIPv4addr addrs+          HostIPv6 -> prioritize isIPv6addr addrs+          _        -> addrs+    tryAddrs addrs'+  where+    hints = NS.defaultHints { NS.addrFlags = [NS.AI_PASSIVE]+                            , NS.addrSocketType = NS.Stream }++    tryAddrs []     = error "bindSock: no addresses available"+    tryAddrs [x]    = useAddr x+    tryAddrs (x:xs) = E.catch (useAddr x)+                              (\e -> let _ = e :: E.IOException in tryAddrs xs)++    useAddr addr = E.bracketOnError (newSocket addr) NS.sClose $ \sock -> do+      let sockAddr = NS.addrAddress addr+      NS.setSocketOption sock NS.NoDelay 1+      NS.setSocketOption sock NS.ReuseAddr 1+      NS.bindSocket sock sockAddr+      return (sock, sockAddr)++--------------------------------------------------------------------------------++-- Misc++newSocket :: NS.AddrInfo -> IO NS.Socket+newSocket addr = NS.socket (NS.addrFamily addr)+                           (NS.addrSocketType addr)+                           (NS.addrProtocol addr)++isIPv4addr, isIPv6addr :: NS.AddrInfo -> Bool+isIPv4addr x = NS.addrFamily x == NS.AF_INET+isIPv6addr x = NS.addrFamily x == NS.AF_INET6++-- | Move the elements that match the predicate closer to the head of the list.+-- Sorting is stable.+prioritize :: (a -> Bool) -> [a] -> [a]+prioritize p = uncurry (++) . partition p