connection 0.1.2 → 0.1.3
raw patch · 3 files changed
+111/−38 lines, 3 filesdep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: bytestring
API changes (from Hackage documentation)
+ Network.Connection: LineTooLong :: LineTooLong
+ Network.Connection: connectionGetLine :: Int -> Connection -> IO ByteString
+ Network.Connection: data LineTooLong
+ Network.Connection: instance Exception LineTooLong
+ Network.Connection: instance Show LineTooLong
+ Network.Connection: instance Typeable LineTooLong
Files
- Network/Connection.hs +108/−34
- Network/Connection/Types.hs +2/−3
- connection.cabal +1/−1
Network/Connection.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveDataTypeable #-} -- | -- Module : Network.Connection -- License : BSD-style@@ -16,6 +18,9 @@ , TLSSettings(..) , SockSettings(..) + -- * Exceptions+ , LineTooLong(..)+ -- * Library initialization , initConnectionContext , ConnectionContext@@ -29,6 +34,7 @@ , connectionGet , connectionGetChunk , connectionGetChunk'+ , connectionGetLine , connectionPut -- * TLS related operation@@ -37,8 +43,8 @@ ) where import Control.Applicative-import Control.Monad ((>=>), when) import Control.Concurrent.MVar+import Control.Monad (join) import qualified Control.Exception as E import qualified System.IO.Error as E @@ -50,6 +56,7 @@ import Network.Socks5 import qualified Network as N +import Data.Data import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L@@ -64,6 +71,12 @@ type Manager = MVar (M.Map TLS.SessionID TLS.SessionData) data ConnectionSessionManager = ConnectionSessionManager Manager +-- | This is the exception raised if we reached the user specified limit for+-- the line in ConnectionGetLine.+data LineTooLong = LineTooLong deriving (Show,Typeable)++instance E.Exception LineTooLong+ instance TLS.SessionManager ConnectionSessionManager where sessionResume (ConnectionSessionManager mvar) sessionID = withMVar mvar (return . M.lookup sessionID)@@ -92,17 +105,11 @@ withBackend :: (ConnectionBackend -> IO a) -> Connection -> IO a withBackend f conn = modifyMVar (connectionBackend conn) (\b -> f b >>= \a -> return (b,a)) -withBuffer :: (ByteString -> IO (ByteString, b)) -> Connection -> IO b-withBuffer f conn = modifyMVar (connectionBuffer conn) f--setEOF :: Connection -> IO ()-setEOF conn = modifyMVar_ (connectionEOF conn) (const (return True))--checkEOF :: Connection -> IO ()-checkEOF conn = readMVar (connectionEOF conn) >>= flip when (E.ioError $ E.mkIOError E.eofErrorType "" Nothing Nothing)- connectionNew :: ConnectionParams -> ConnectionBackend -> IO Connection-connectionNew p backend = Connection <$> newMVar backend <*> newMVar B.empty <*> pure (connectionHostname p, connectionPort p) <*> newMVar False+connectionNew p backend =+ Connection <$> newMVar backend+ <*> newMVar (Just B.empty)+ <*> pure (connectionHostname p, connectionPort p) -- | Use an already established handle to create a connection object. --@@ -121,7 +128,7 @@ -> ConnectionParams -- ^ The parameters for this connection (where to connect, and such). -> IO Connection -- ^ The new established connection on success. connectTo cg cParams = do- h <- conFct (connectionHostname cParams) (N.PortNumber $ connectionPort cParams) + h <- conFct (connectionHostname cParams) (N.PortNumber $ connectionPort cParams) connectFromHandle cg h cParams where conFct = case connectionUseSocks cParams of@@ -130,42 +137,109 @@ -- | Put a block of data in the connection. connectionPut :: Connection -> ByteString -> IO ()-connectionPut connection content = checkEOF connection >> withBackend doWrite connection+connectionPut connection content = withBackend doWrite connection where doWrite (ConnectionStream h) = B.hPut h content >> hFlush h doWrite (ConnectionTLS ctx) = TLS.sendData ctx $ L.fromChunks [content] -- | Get some bytes from a connection. ----- The size argument is just the maximum that could be returned to the user,--- however the call will returns as soon as there's data, even if there's less--- data than expected.+-- The size argument is just the maximum that could be returned to the user.+-- The call will return as soon as there's data, even if there's less+-- than requested. Hence, it behaves like 'B.hGetSome'.+--+-- On end of input, 'connectionGet' returns 0, but subsequent calls will throw+-- an 'E.isEOFError' exception. connectionGet :: Connection -> Int -> IO ByteString-connectionGet conn size = connectionGetChunk' conn $ B.splitAt size+connectionGet conn size+ | size < 0 = fail "Network.Connection.connectionGet: size < 0"+ | size == 0 = return B.empty+ | otherwise = connectionGetChunkBase "connectionGet" conn $ B.splitAt size -- | Get the next block of data from the connection. connectionGetChunk :: Connection -> IO ByteString-connectionGetChunk conn = connectionGetChunk' conn $ \s -> (s, B.empty)+connectionGetChunk conn =+ connectionGetChunkBase "connectionGetChunk" conn $ \s -> (s, B.empty) -- | Like 'connectionGetChunk', but return the unused portion to the buffer, -- where it will be the next chunk read. connectionGetChunk' :: Connection -> (ByteString -> (a, ByteString)) -> IO a-connectionGetChunk' conn f = checkEOF conn >> withBuffer getData conn- where getData buf- | B.null buf = do- chunk <- withBackend (getMoreData >=> markEOF) conn- return $ swap $ f chunk- | otherwise =- return $ swap $ f buf+connectionGetChunk' = connectionGetChunkBase "connectionGetChunk'" - markEOF bs- | B.null bs = setEOF conn >> return bs- | otherwise = return bs+connectionGetChunkBase :: String -> Connection -> (ByteString -> (a, ByteString)) -> IO a+connectionGetChunkBase loc conn f =+ modifyMVar (connectionBuffer conn) $ \m ->+ case m of+ Nothing -> throwEOF conn loc+ Just buf+ | B.null buf -> do+ chunk <- withBackend getMoreData conn+ if B.null chunk+ then closeBuf chunk+ else updateBuf chunk+ | otherwise ->+ updateBuf buf+ where+ getMoreData (ConnectionTLS tlsctx) = TLS.recvData tlsctx+ getMoreData (ConnectionStream h) = B.hGetSome h (16 * 1024) - getMoreData (ConnectionTLS tlsctx) = TLS.recvData tlsctx- getMoreData (ConnectionStream h) = B.hGetSome h (16 * 1024)+ updateBuf buf = case f buf of (a, !buf') -> return (Just buf', a)+ closeBuf buf = case f buf of (a, _buf') -> return (Nothing, a) - swap (a, b) = (b, a)+-- | Get the next line, using ASCII LF as the line terminator.+--+-- This throws an 'isEOFError' exception on end of input, and LineTooLong when+-- the number of bytes gathered is over the limit without a line terminator.+--+-- The actual line returned can be bigger than the limit specified, provided+-- that the last chunk returned by the underlaying backend contains a LF.+-- In another world only when we need more input and limit is reached that the+-- LineTooLong exception will be raised.+--+-- An end of file will be considered as a line terminator too, if line is+-- not empty.+connectionGetLine :: Int -- ^ Maximum number of bytes before raising a LineTooLong exception+ -> Connection -- ^ Connection+ -> IO ByteString -- ^ The received line with the LF trimmed+connectionGetLine limit conn = more (throwEOF conn loc) 0 id+ where+ loc = "connectionGetLine"+ lineTooLong = E.throwIO LineTooLong + -- Accumulate chunks using a difference list, and concatenate them+ -- when an end-of-line indicator is reached.+ more eofK !currentSz !dl =+ getChunk (\s -> let len = B.length s+ in if currentSz + len > limit+ then lineTooLong+ else more eofK (currentSz + len) (dl . (s:)))+ (\s -> done (dl . (s:)))+ (done dl)++ done :: ([ByteString] -> [ByteString]) -> IO ByteString+ done dl = return $! B.concat $ dl []++ -- Get another chunk, and call one of the continuations+ getChunk :: (ByteString -> IO r) -- moreK: need more input+ -> (ByteString -> IO r) -- doneK: end of line (line terminator found)+ -> IO r -- eofK: end of file+ -> IO r+ getChunk moreK doneK eofK =+ join $ connectionGetChunkBase loc conn $ \s ->+ if B.null s+ then (eofK, B.empty)+ else case B.breakByte 10 s of+ (a, b)+ | B.null b -> (moreK a, B.empty)+ | otherwise -> (doneK a, B.tail b)++throwEOF :: Connection -> String -> IO a+throwEOF conn loc =+ E.throwIO $ E.mkIOError E.eofErrorType loc' Nothing (Just path)+ where+ loc' = "Network.Connection." ++ loc+ path = let (host, port) = connectionID conn+ in host ++ ":" ++ show port+ -- | Close a connection. connectionClose :: Connection -> IO () connectionClose = withBackend backendClose@@ -173,12 +247,12 @@ backendClose (ConnectionStream h) = hClose h -- | Activate secure layer using the parameters specified.--- +-- -- This is typically used to negociate a TLS channel on an already -- establish channel, e.g. supporting a STARTTLS command. it also -- flush the received buffer to prevent application confusing -- received data before and after the setSecure call.--- +-- -- If the connection is already using TLS, nothing else happens. connectionSetSecure :: ConnectionContext -> Connection@@ -189,7 +263,7 @@ modifyMVar (connectionBackend connection) $ \backend -> case backend of (ConnectionStream h) -> do ctx <- tlsEstablish h (makeTLSParams cg params)- return (ConnectionTLS ctx, B.empty)+ return (ConnectionTLS ctx, Just B.empty) (ConnectionTLS _) -> return (backend, b) -- | Returns if the connection is establish securely or not.
Network/Connection/Types.hs view
@@ -47,7 +47,7 @@ -- The simple settings is just the hostname and portnumber of the proxy server. -- -- That's for now the only settings in the SOCKS package,--- socks password, or authentication is not yet implemented.+-- socks password, or any sort of other authentications is not yet implemented. data SockSettings = SockSettingsSimple HostName PortNumber -- | TLS Settings that can be either expressed as simple settings,@@ -74,9 +74,8 @@ -- | This opaque type represent a connection to a destination. data Connection = Connection { connectionBackend :: MVar ConnectionBackend- , connectionBuffer :: MVar ByteString+ , connectionBuffer :: MVar (Maybe ByteString) -- ^ this is set to 'Nothing' on EOF , connectionID :: (HostName, PortNumber) -- ^ return a simple tuple of the port and hostname that we're connected to.- , connectionEOF :: MVar Bool } -- | Shared values (certificate store, sessions, ..) between connections
connection.cabal view
@@ -1,5 +1,5 @@ Name: connection-Version: 0.1.2+Version: 0.1.3 Description: Simple network library for all your connection need. .