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 +30/−0
- PEOPLE +5/−0
- README.md +14/−0
- Setup.hs +2/−0
- network-simple.cabal +26/−0
- src/Network/Simple/Internal.hs +43/−0
- src/Network/Simple/TCP.hs +290/−0
+ 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