packages feed

http2-client 0.8.0.2 → 0.9.0.0

raw patch · 9 files changed

+234/−170 lines, 9 filesdep +lifted-asyncdep +lifted-basedep +mtldep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: lifted-async, lifted-base, mtl, transformers-base

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Network.HTTP2.Client.Exceptions: EarlyEndOfStream :: ClientError
+ Network.HTTP2.Client.Exceptions: data ClientError
+ Network.HTTP2.Client.Exceptions: data ExceptT e (m :: Type -> Type) a
+ Network.HTTP2.Client.Exceptions: instance GHC.Classes.Eq Network.HTTP2.Client.Exceptions.ClientError
+ Network.HTTP2.Client.Exceptions: instance GHC.Classes.Ord Network.HTTP2.Client.Exceptions.ClientError
+ Network.HTTP2.Client.Exceptions: instance GHC.Exception.Type.Exception Network.HTTP2.Client.Exceptions.ClientError
+ Network.HTTP2.Client.Exceptions: instance GHC.Show.Show Network.HTTP2.Client.Exceptions.ClientError
+ Network.HTTP2.Client.Exceptions: lift :: (MonadTrans t, Monad m) => m a -> t m a
+ Network.HTTP2.Client.Exceptions: runClientIO :: ClientIO a -> IO (Either ClientError a)
+ Network.HTTP2.Client.Exceptions: runExceptT :: () => ExceptT e m a -> m (Either e a)
+ Network.HTTP2.Client.Exceptions: throwError :: MonadError e m => e -> m a
+ Network.HTTP2.Client.Exceptions: type ClientIO = ExceptT ClientError IO
- Network.HTTP2.Client: Http2Client :: ByteString -> IO (IO (FrameHeader, FramePayload)) -> SettingsList -> IO (IO (FrameHeader, FramePayload)) -> ErrorCodeId -> ByteString -> IO () -> forall a. StreamStarter a -> IncomingFlowControl -> OutgoingFlowControl -> IO PayloadSplitter -> !Http2ClientAsyncs -> IO () -> Http2Client
+ Network.HTTP2.Client: Http2Client :: (ByteString -> ClientIO (ClientIO (FrameHeader, FramePayload))) -> (SettingsList -> ClientIO (ClientIO (FrameHeader, FramePayload))) -> (ErrorCodeId -> ByteString -> ClientIO ()) -> (forall a. StreamStarter a) -> IncomingFlowControl -> OutgoingFlowControl -> IO PayloadSplitter -> !Http2ClientAsyncs -> ClientIO () -> Http2Client
- Network.HTTP2.Client: Http2ClientAsyncs :: Async (FrameHeader, FramePayload) -> Async () -> Http2ClientAsyncs
+ Network.HTTP2.Client: Http2ClientAsyncs :: Async (Either ClientError (FrameHeader, FramePayload)) -> Async (Either ClientError ()) -> Http2ClientAsyncs
- Network.HTTP2.Client: Http2Stream :: HeaderList -> (FrameFlags -> FrameFlags) -> IO StreamThread -> Priority -> IO () -> ErrorCodeId -> IO () -> IO StreamEvent -> (FrameFlags -> FrameFlags) -> ByteString -> IO () -> StreamId -> HeaderList -> PushPromiseHandler -> IO () -> Http2Stream
+ Network.HTTP2.Client: Http2Stream :: (HeaderList -> (FrameFlags -> FrameFlags) -> ClientIO StreamThread) -> (Priority -> ClientIO ()) -> (ErrorCodeId -> ClientIO ()) -> ClientIO StreamEvent -> ((FrameFlags -> FrameFlags) -> ByteString -> ClientIO ()) -> (StreamId -> HeaderList -> PushPromiseHandler -> ClientIO ()) -> Http2Stream
- Network.HTTP2.Client: IncomingFlowControl :: WindowSize -> IO () -> WindowSize -> IO Int -> IO Bool -> IncomingFlowControl
+ Network.HTTP2.Client: IncomingFlowControl :: (WindowSize -> IO ()) -> (WindowSize -> IO Int) -> ClientIO Bool -> IncomingFlowControl
- Network.HTTP2.Client: OutgoingFlowControl :: WindowSize -> IO () -> WindowSize -> IO WindowSize -> OutgoingFlowControl
+ Network.HTTP2.Client: OutgoingFlowControl :: (WindowSize -> IO ()) -> (WindowSize -> ClientIO WindowSize) -> OutgoingFlowControl
- Network.HTTP2.Client: StreamDefinition :: IO StreamThread -> IncomingFlowControl -> OutgoingFlowControl -> IO a -> StreamDefinition a
+ Network.HTTP2.Client: StreamDefinition :: ClientIO StreamThread -> (IncomingFlowControl -> OutgoingFlowControl -> ClientIO a) -> StreamDefinition a
- Network.HTTP2.Client: [_close] :: Http2Client -> IO ()
+ Network.HTTP2.Client: [_close] :: Http2Client -> ClientIO ()
- Network.HTTP2.Client: [_goaway] :: Http2Client -> ErrorCodeId -> ByteString -> IO ()
+ Network.HTTP2.Client: [_goaway] :: Http2Client -> ErrorCodeId -> ByteString -> ClientIO ()
- Network.HTTP2.Client: [_handlePushPromise] :: Http2Stream -> StreamId -> HeaderList -> PushPromiseHandler -> IO ()
+ Network.HTTP2.Client: [_handlePushPromise] :: Http2Stream -> StreamId -> HeaderList -> PushPromiseHandler -> ClientIO ()
- Network.HTTP2.Client: [_handleStream] :: StreamDefinition a -> IncomingFlowControl -> OutgoingFlowControl -> IO a
+ Network.HTTP2.Client: [_handleStream] :: StreamDefinition a -> IncomingFlowControl -> OutgoingFlowControl -> ClientIO a
- Network.HTTP2.Client: [_headers] :: Http2Stream -> HeaderList -> (FrameFlags -> FrameFlags) -> IO StreamThread
+ Network.HTTP2.Client: [_headers] :: Http2Stream -> HeaderList -> (FrameFlags -> FrameFlags) -> ClientIO StreamThread
- Network.HTTP2.Client: [_incomingFramesAsync] :: Http2ClientAsyncs -> Async ()
+ Network.HTTP2.Client: [_incomingFramesAsync] :: Http2ClientAsyncs -> Async (Either ClientError ())
- Network.HTTP2.Client: [_initStream] :: StreamDefinition a -> IO StreamThread
+ Network.HTTP2.Client: [_initStream] :: StreamDefinition a -> ClientIO StreamThread
- Network.HTTP2.Client: [_ping] :: Http2Client -> ByteString -> IO (IO (FrameHeader, FramePayload))
+ Network.HTTP2.Client: [_ping] :: Http2Client -> ByteString -> ClientIO (ClientIO (FrameHeader, FramePayload))
- Network.HTTP2.Client: [_prio] :: Http2Stream -> Priority -> IO ()
+ Network.HTTP2.Client: [_prio] :: Http2Stream -> Priority -> ClientIO ()
- Network.HTTP2.Client: [_rst] :: Http2Stream -> ErrorCodeId -> IO ()
+ Network.HTTP2.Client: [_rst] :: Http2Stream -> ErrorCodeId -> ClientIO ()
- Network.HTTP2.Client: [_sendDataChunk] :: Http2Stream -> (FrameFlags -> FrameFlags) -> ByteString -> IO ()
+ Network.HTTP2.Client: [_sendDataChunk] :: Http2Stream -> (FrameFlags -> FrameFlags) -> ByteString -> ClientIO ()
- Network.HTTP2.Client: [_settings] :: Http2Client -> SettingsList -> IO (IO (FrameHeader, FramePayload))
+ Network.HTTP2.Client: [_settings] :: Http2Client -> SettingsList -> ClientIO (ClientIO (FrameHeader, FramePayload))
- Network.HTTP2.Client: [_updateWindow] :: IncomingFlowControl -> IO Bool
+ Network.HTTP2.Client: [_updateWindow] :: IncomingFlowControl -> ClientIO Bool
- Network.HTTP2.Client: [_waitEvent] :: Http2Stream -> IO StreamEvent
+ Network.HTTP2.Client: [_waitEvent] :: Http2Stream -> ClientIO StreamEvent
- Network.HTTP2.Client: [_waitSettingsAsync] :: Http2ClientAsyncs -> Async (FrameHeader, FramePayload)
+ Network.HTTP2.Client: [_waitSettingsAsync] :: Http2ClientAsyncs -> Async (Either ClientError (FrameHeader, FramePayload))
- Network.HTTP2.Client: [_withdrawCredit] :: OutgoingFlowControl -> WindowSize -> IO WindowSize
+ Network.HTTP2.Client: [_withdrawCredit] :: OutgoingFlowControl -> WindowSize -> ClientIO WindowSize
- Network.HTTP2.Client: _gtfo :: Http2Client -> ErrorCodeId -> ByteString -> IO ()
+ Network.HTTP2.Client: _gtfo :: Http2Client -> ErrorCodeId -> ByteString -> ClientIO ()
- Network.HTTP2.Client: headers :: Http2Stream -> HeaderList -> FlagSetter -> IO StreamThread
+ Network.HTTP2.Client: headers :: Http2Stream -> HeaderList -> FlagSetter -> ClientIO StreamThread
- Network.HTTP2.Client: linkAsyncs :: Http2Client -> IO ()
+ Network.HTTP2.Client: linkAsyncs :: Http2Client -> ClientIO ()
- Network.HTTP2.Client: newHttp2Client :: Http2FrameConnection -> Int -> Int -> SettingsList -> GoAwayHandler -> FallBackFrameHandler -> IO Http2Client
+ Network.HTTP2.Client: newHttp2Client :: Http2FrameConnection -> Int -> Int -> SettingsList -> GoAwayHandler -> FallBackFrameHandler -> ClientIO Http2Client
- Network.HTTP2.Client: runHttp2Client :: Http2FrameConnection -> Int -> Int -> SettingsList -> GoAwayHandler -> FallBackFrameHandler -> (Http2Client -> IO a) -> IO a
+ Network.HTTP2.Client: runHttp2Client :: Http2FrameConnection -> Int -> Int -> SettingsList -> GoAwayHandler -> FallBackFrameHandler -> (Http2Client -> ClientIO a) -> ClientIO a
- Network.HTTP2.Client: sendData :: Http2Client -> Http2Stream -> FlagSetter -> ByteString -> IO ()
+ Network.HTTP2.Client: sendData :: Http2Client -> Http2Stream -> FlagSetter -> ByteString -> ClientIO ()
- Network.HTTP2.Client: trailers :: Http2Stream -> HeaderList -> (FrameFlags -> FrameFlags) -> IO ()
+ Network.HTTP2.Client: trailers :: Http2Stream -> HeaderList -> (FrameFlags -> FrameFlags) -> ClientIO ()
- Network.HTTP2.Client: type FallBackFrameHandler = (FrameHeader, FramePayload) -> IO ()
+ Network.HTTP2.Client: type FallBackFrameHandler = (FrameHeader, FramePayload) -> ClientIO ()
- Network.HTTP2.Client: type GoAwayHandler = RemoteSentGoAwayFrame -> IO ()
+ Network.HTTP2.Client: type GoAwayHandler = RemoteSentGoAwayFrame -> ClientIO ()
- Network.HTTP2.Client: type PushPromiseHandler = StreamId -> Http2Stream -> HeaderList -> IncomingFlowControl -> OutgoingFlowControl -> IO ()
+ Network.HTTP2.Client: type PushPromiseHandler = StreamId -> Http2Stream -> HeaderList -> IncomingFlowControl -> OutgoingFlowControl -> ClientIO ()
- Network.HTTP2.Client: type StreamStarter a = (Http2Stream -> StreamDefinition a) -> IO (Either TooMuchConcurrency a)
+ Network.HTTP2.Client: type StreamStarter a = (Http2Stream -> StreamDefinition a) -> ClientIO (Either TooMuchConcurrency a)
- Network.HTTP2.Client.FrameConnection: Http2FrameClientStream :: IO [(FrameFlags -> FrameFlags, FramePayload)] -> IO () -> StreamId -> Http2FrameClientStream
+ Network.HTTP2.Client.FrameConnection: Http2FrameClientStream :: (ClientIO [(FrameFlags -> FrameFlags, FramePayload)] -> ClientIO ()) -> StreamId -> Http2FrameClientStream
- Network.HTTP2.Client.FrameConnection: Http2FrameConnection :: StreamId -> Http2FrameClientStream -> Http2ServerStream -> IO () -> Http2FrameConnection
+ Network.HTTP2.Client.FrameConnection: Http2FrameConnection :: (StreamId -> Http2FrameClientStream) -> Http2ServerStream -> ClientIO () -> Http2FrameConnection
- Network.HTTP2.Client.FrameConnection: Http2ServerStream :: IO (FrameHeader, Either HTTP2Error FramePayload) -> Http2ServerStream
+ Network.HTTP2.Client.FrameConnection: Http2ServerStream :: ClientIO (FrameHeader, Either HTTP2Error FramePayload) -> Http2ServerStream
- Network.HTTP2.Client.FrameConnection: [_closeConnection] :: Http2FrameConnection -> IO ()
+ Network.HTTP2.Client.FrameConnection: [_closeConnection] :: Http2FrameConnection -> ClientIO ()
- Network.HTTP2.Client.FrameConnection: [_nextHeaderAndFrame] :: Http2ServerStream -> IO (FrameHeader, Either HTTP2Error FramePayload)
+ Network.HTTP2.Client.FrameConnection: [_nextHeaderAndFrame] :: Http2ServerStream -> ClientIO (FrameHeader, Either HTTP2Error FramePayload)
- Network.HTTP2.Client.FrameConnection: [_sendFrames] :: Http2FrameClientStream -> IO [(FrameFlags -> FrameFlags, FramePayload)] -> IO ()
+ Network.HTTP2.Client.FrameConnection: [_sendFrames] :: Http2FrameClientStream -> ClientIO [(FrameFlags -> FrameFlags, FramePayload)] -> ClientIO ()
- Network.HTTP2.Client.FrameConnection: closeConnection :: Http2FrameConnection -> IO ()
+ Network.HTTP2.Client.FrameConnection: closeConnection :: Http2FrameConnection -> ClientIO ()
- Network.HTTP2.Client.FrameConnection: newHttp2FrameConnection :: HostName -> PortNumber -> Maybe ClientParams -> IO Http2FrameConnection
+ Network.HTTP2.Client.FrameConnection: newHttp2FrameConnection :: HostName -> PortNumber -> Maybe ClientParams -> ClientIO Http2FrameConnection
- Network.HTTP2.Client.FrameConnection: next :: Http2FrameConnection -> IO (FrameHeader, Either HTTP2Error FramePayload)
+ Network.HTTP2.Client.FrameConnection: next :: Http2FrameConnection -> ClientIO (FrameHeader, Either HTTP2Error FramePayload)
- Network.HTTP2.Client.FrameConnection: sendBackToBack :: Http2FrameClientStream -> [(FrameFlags -> FrameFlags, FramePayload)] -> IO ()
+ Network.HTTP2.Client.FrameConnection: sendBackToBack :: Http2FrameClientStream -> [(FrameFlags -> FrameFlags, FramePayload)] -> ClientIO ()
- Network.HTTP2.Client.FrameConnection: sendOne :: Http2FrameClientStream -> (FrameFlags -> FrameFlags) -> FramePayload -> IO ()
+ Network.HTTP2.Client.FrameConnection: sendOne :: Http2FrameClientStream -> (FrameFlags -> FrameFlags) -> FramePayload -> ClientIO ()
- Network.HTTP2.Client.Helpers: ping :: Http2Client -> Int -> ByteString -> IO PingReply
+ Network.HTTP2.Client.Helpers: ping :: Http2Client -> Int -> ByteString -> ClientIO PingReply
- Network.HTTP2.Client.Helpers: upload :: ByteString -> (FrameFlags -> FrameFlags) -> Http2Client -> OutgoingFlowControl -> Http2Stream -> OutgoingFlowControl -> IO ()
+ Network.HTTP2.Client.Helpers: upload :: ByteString -> (FrameFlags -> FrameFlags) -> Http2Client -> OutgoingFlowControl -> Http2Stream -> OutgoingFlowControl -> ClientIO ()
- Network.HTTP2.Client.Helpers: waitStream :: Http2Stream -> IncomingFlowControl -> PushPromiseHandler -> IO StreamResult
+ Network.HTTP2.Client.Helpers: waitStream :: Http2Stream -> IncomingFlowControl -> PushPromiseHandler -> ClientIO StreamResult
- Network.HTTP2.Client.RawConnection: RawHttp2Connection :: [ByteString] -> IO () -> Int -> IO ByteString -> IO () -> RawHttp2Connection
+ Network.HTTP2.Client.RawConnection: RawHttp2Connection :: ([ByteString] -> ClientIO ()) -> (Int -> ClientIO ByteString) -> ClientIO () -> RawHttp2Connection
- Network.HTTP2.Client.RawConnection: [_close] :: RawHttp2Connection -> IO ()
+ Network.HTTP2.Client.RawConnection: [_close] :: RawHttp2Connection -> ClientIO ()
- Network.HTTP2.Client.RawConnection: [_nextRaw] :: RawHttp2Connection -> Int -> IO ByteString
+ Network.HTTP2.Client.RawConnection: [_nextRaw] :: RawHttp2Connection -> Int -> ClientIO ByteString
- Network.HTTP2.Client.RawConnection: [_sendRaw] :: RawHttp2Connection -> [ByteString] -> IO ()
+ Network.HTTP2.Client.RawConnection: [_sendRaw] :: RawHttp2Connection -> [ByteString] -> ClientIO ()
- Network.HTTP2.Client.RawConnection: newRawHttp2Connection :: HostName -> PortNumber -> Maybe ClientParams -> IO RawHttp2Connection
+ Network.HTTP2.Client.RawConnection: newRawHttp2Connection :: HostName -> PortNumber -> Maybe ClientParams -> ClientIO RawHttp2Connection
- Network.HTTP2.Client.RawConnection: newRawHttp2ConnectionSocket :: Socket -> Maybe ClientParams -> IO RawHttp2Connection
+ Network.HTTP2.Client.RawConnection: newRawHttp2ConnectionSocket :: Socket -> Maybe ClientParams -> ClientIO RawHttp2Connection

Files

ChangeLog.md view
@@ -2,6 +2,10 @@  ## Unreleased changes +## v0.9.0.0++- introduce ClientIO as an error-carrying IO-monad for performing http2-calls+ ## v0.8.0.2  - first Changelog!
http2-client.cabal view
@@ -1,5 +1,5 @@ name:                http2-client-version:             0.8.0.2+version:             0.9.0.0 synopsis:            A native HTTP2 client library. description:         Please read the README.md at the homepage. homepage:            https://github.com/lucasdicioccio/http2-client@@ -16,6 +16,7 @@ library   hs-source-dirs:      src   exposed-modules:     Network.HTTP2.Client+                     , Network.HTTP2.Client.Exceptions                      , Network.HTTP2.Client.FrameConnection                      , Network.HTTP2.Client.Helpers                      , Network.HTTP2.Client.RawConnection@@ -27,10 +28,14 @@                      , containers >= 0.5 && < 1                      , deepseq >= 1.4 && < 2                      , http2 >= 1.6 && < 2+                     , lifted-async >= 0.10 && < 0.11+                     , lifted-base >= 0.2 && < 0.3+                     , mtl >= 2.2 && < 3                      , network >= 2.6 && < 3                      , stm >= 2.4 && < 3                      , time >= 1.8 && < 2                      , tls >= 1.4 && < 2+                     , transformers-base >= 0.4 && < 0.5   default-language:    Haskell2010  -- Commented-out to avoid distribution
src/Network/HTTP2/Client.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE FlexibleContexts   #-} {-# LANGUAGE RecordWildCards    #-} {-# LANGUAGE RankNTypes         #-} {-# LANGUAGE OverloadedStrings  #-}@@ -40,18 +41,20 @@     -- * Convenience re-exports     , StreamEvent(..)     , module Network.HTTP2.Client.FrameConnection+    , module Network.HTTP2.Client.Exceptions     , module Network.Socket     , module Network.TLS     ) where -import           Control.Concurrent.Async (Async, async, race, withAsync, link)-import           Control.Exception (bracket, throwIO, SomeException, catch)-import           Control.Concurrent.MVar (newEmptyMVar, newMVar, putMVar, takeMVar, tryPutMVar)-import           Control.Concurrent (threadDelay)+import           Control.Concurrent.Async.Lifted (Async, async, race, withAsync, link)+import           Control.Exception.Lifted (bracket, throwIO, SomeException, catch)+import           Control.Concurrent.MVar.Lifted (newEmptyMVar, newMVar, putMVar, takeMVar, tryPutMVar)+import           Control.Concurrent.Lifted (threadDelay) import           Control.Monad (forever, void, when, forM_)+import           Control.Monad.IO.Class (liftIO) import           Data.ByteString (ByteString) import qualified Data.ByteString as ByteString-import           Data.IORef (newIORef, atomicModifyIORef', readIORef)+import           Data.IORef.Lifted (newIORef, atomicModifyIORef', readIORef) import           Data.Maybe (fromMaybe) import           Network.HPACK as HPACK import           Network.HTTP2 as HTTP2@@ -60,6 +63,7 @@  import           Network.HTTP2.Client.Channels import           Network.HTTP2.Client.Dispatch+import           Network.HTTP2.Client.Exceptions import           Network.HTTP2.Client.FrameConnection  -- | Offers credit-based flow-control.@@ -78,7 +82,7 @@   -- only does accounting, the IO only does mutable changes. See '_updateWindow'.   , _consumeCredit :: WindowSize -> IO Int   -- ^ Consumes some credit and returns the credit left.-  , _updateWindow :: IO Bool+  , _updateWindow :: ClientIO Bool   -- ^ Sends a WINDOW_UPDATE frame crediting it with the whole amount credited   -- since the last _updateWindow call. The boolean tells whether an update was   -- actually sent or not. A reason for not sending an update is if there is no@@ -101,7 +105,7 @@ data OutgoingFlowControl = OutgoingFlowControl {     _receiveCredit  :: WindowSize -> IO ()   -- ^ Add credit (using a hidden mutable reference underneath).-  , _withdrawCredit :: WindowSize -> IO WindowSize+  , _withdrawCredit :: WindowSize -> ClientIO WindowSize   -- ^ Wait until we can take credit from stash. The returned value correspond   -- to the amount that could be withdrawn, which is min(current, wanted). A   -- caller should withdraw credit to send DATA chunks and put back any unused@@ -112,12 +116,12 @@ -- -- Please red the doc for this record fields and then see 'StreamStarter'. data StreamDefinition a = StreamDefinition {-    _initStream   :: IO StreamThread+    _initStream   :: ClientIO StreamThread   -- ^ Function to initialize a new client stream. This function runs in a   -- exclusive-access section of the code and may prevent other threads to   -- initialize new streams. Hence, you should ensure this IO does not wait for   -- long periods of time.-  , _handleStream :: IncomingFlowControl -> OutgoingFlowControl -> IO a+  , _handleStream :: IncomingFlowControl -> OutgoingFlowControl -> ClientIO a   -- ^ Function to operate with the stream. IncomingFlowControl currently is   -- credited on your behalf as soon as a DATA frame arrives (and before you   -- handle it with '_waitData'). However we do not send WINDOW_UPDATE with@@ -139,7 +143,7 @@ -- implementation of the critical region, meanwhile, the 'StreamDefinition' -- delimits this critical region. type StreamStarter a =-     (Http2Stream -> StreamDefinition a) -> IO (Either TooMuchConcurrency a)+     (Http2Stream -> StreamDefinition a) -> ClientIO (Either TooMuchConcurrency a)  -- | Whether or not the client library believes the server will reject the new -- stream. The Int content corresponds to the number of streams that should end@@ -151,18 +155,18 @@  -- | Record holding functions one can call while in an HTTP2 client session. data Http2Client = Http2Client {-    _ping             :: ByteString -> IO (IO (FrameHeader, FramePayload))+    _ping             :: ByteString -> ClientIO (ClientIO (FrameHeader, FramePayload))   -- ^ Send a PING, the payload size must be exactly eight bytes.   -- Returns an IO to wait for a ping reply. No timeout is provided. Only the   -- first call to this IO will return if a reply is received. Hence we   -- recommend wrapping this IO in an Async (e.g., with @race (threadDelay   -- timeout)@.)-  , _settings         :: SettingsList -> IO (IO (FrameHeader, FramePayload))+  , _settings         :: SettingsList -> ClientIO (ClientIO (FrameHeader, FramePayload))   -- ^ Sends a SETTINGS. Returns an IO to wait for a settings reply. No timeout   -- is provided. Only the first call to this IO will return if a reply is   -- received. Hence we recommend wrapping this IO in an Async (e.g., with   -- @race (threadDelay timeout)@.)-  , _goaway           :: ErrorCodeId -> ByteString -> IO ()+  , _goaway           :: ErrorCodeId -> ByteString -> ClientIO ()   -- ^ Sends a GOAWAY.   , _startStream      :: forall a. StreamStarter a   -- ^ Spawns new streams. See 'StreamStarter'.@@ -176,22 +180,22 @@   -- ^ Returns a function to split a payload.   , _asyncs         :: !Http2ClientAsyncs   -- ^ Asynchronous operations threads.-  , _close           :: IO ()+  , _close           :: ClientIO ()   -- ^ Immediately stop processing incoming frames and closes the network   -- connection.   }  data InitHttp2Client = InitHttp2Client {-    _initPing                :: ByteString -> IO (IO (FrameHeader, FramePayload))-  , _initSettings            :: SettingsList -> IO (IO (FrameHeader, FramePayload))-  , _initGoaway              :: ErrorCodeId -> ByteString -> IO ()+    _initPing                :: ByteString -> ClientIO (ClientIO (FrameHeader, FramePayload))+  , _initSettings            :: SettingsList -> ClientIO (ClientIO (FrameHeader, FramePayload))+  , _initGoaway              :: ErrorCodeId -> ByteString -> ClientIO ()   , _initStartStream         :: forall a. StreamStarter a   , _initIncomingFlowControl :: IncomingFlowControl   , _initOutgoingFlowControl :: OutgoingFlowControl   , _initPaylodSplitter      :: IO PayloadSplitter-  , _initClose               :: IO ()+  , _initClose               :: ClientIO ()   -- ^ Immediately closes the connection.-  , _initStop                :: IO Bool+  , _initStop                :: ClientIO Bool   -- ^ Stops receiving frames.   } @@ -201,9 +205,9 @@ -- If you modify this structure to add more Async, please also modify -- 'linkAsyncs' accordingly. data Http2ClientAsyncs = Http2ClientAsyncs {-    _waitSettingsAsync   :: Async (FrameHeader, FramePayload)+    _waitSettingsAsync   :: Async (Either ClientError (FrameHeader, FramePayload))   -- ^ Async waiting for the initial settings ACK.-  , _incomingFramesAsync :: Async ()+  , _incomingFramesAsync :: Async (Either ClientError ())   -- ^ Async responsible for ingesting all frames, increasing the   -- maximum-received streamID and starting the frame dispatch. See   -- 'dispatchFrames'.@@ -211,7 +215,7 @@  -- | Links all client's asyncs to current thread using: -- @ link someUnderlyingAsync @ .-linkAsyncs :: Http2Client -> IO ()+linkAsyncs :: Http2Client -> ClientIO () linkAsyncs client =     let Http2ClientAsyncs{..} = _asyncs client in do             link _waitSettingsAsync@@ -220,7 +224,7 @@ -- | Synonym of '_goaway'. -- -- https://github.com/http2/http2-spec/pull/366-_gtfo :: Http2Client -> ErrorCodeId -> ByteString -> IO ()+_gtfo :: Http2Client -> ErrorCodeId -> ByteString -> ClientIO () _gtfo = _goaway  -- | Opaque proof that a client stream was initialized.@@ -233,29 +237,29 @@ data Http2Stream = Http2Stream {     _headers      :: HPACK.HeaderList                   -> (FrameFlags -> FrameFlags)-                  -> IO StreamThread+                  -> ClientIO StreamThread   -- ^ Starts the stream with HTTP headers. Flags modifier can use   -- 'setEndStream' if no data is required passed the last block of headers.   -- Usually, this is the only call needed to build an '_initStream'.-  , _prio         :: Priority -> IO ()+  , _prio         :: Priority -> ClientIO ()   -- ^ Changes the PRIORITY of this stream.-  , _rst          :: ErrorCodeId -> IO ()+  , _rst          :: ErrorCodeId -> ClientIO ()   -- ^ Resets this stream with a RST frame. You should not use this stream past this call.-  , _waitEvent    :: IO StreamEvent+  , _waitEvent    :: ClientIO StreamEvent   -- ^ Waits for the next event on the stream.-  , _sendDataChunk     :: (FrameFlags -> FrameFlags) -> ByteString -> IO ()+  , _sendDataChunk     :: (FrameFlags -> FrameFlags) -> ByteString -> ClientIO ()   -- ^ Sends a DATA frame chunk. You can use send empty frames with only   -- headers modifiers to close streams. This function is oblivious to framing   -- and hence does not respect the RFC if sending large blocks. Use 'sendData'   -- to chunk and send naively according to server\'s preferences. This function   -- can be useful if you intend to handle the framing yourself.-  , _handlePushPromise :: StreamId -> HeaderList -> PushPromiseHandler -> IO ()+  , _handlePushPromise :: StreamId -> HeaderList -> PushPromiseHandler -> ClientIO ()   }  -- | Sends HTTP trailers. -- -- Trailers should be the last thing sent over a stream.-trailers :: Http2Stream -> HPACK.HeaderList -> (FrameFlags -> FrameFlags) -> IO ()+trailers :: Http2Stream -> HPACK.HeaderList -> (FrameFlags -> FrameFlags) -> ClientIO () trailers stream hdrs flagmod = void $ _headers stream hdrs flagmod  -- | Handler upon receiving a PUSH_PROMISE from the server.@@ -268,7 +272,7 @@ -- client-initiated stream. Longer term we may move passing this handler to the -- '_startStream' instead of 'newHttp2Client' (as it is for now). type PushPromiseHandler =-    StreamId -> Http2Stream -> HeaderList -> IncomingFlowControl -> OutgoingFlowControl -> IO ()+    StreamId -> Http2Stream -> HeaderList -> IncomingFlowControl -> OutgoingFlowControl -> ClientIO ()  -- | Starts a new stream (i.e., one HTTP request + server-pushes). --@@ -305,7 +309,7 @@ -- Note that we currently enforce the 'HTTP2.setEndHeader' but this design -- choice may change in the future. Hence, we recommend you use -- 'HTTP2.setEndHeader' as well.-headers :: Http2Stream -> HeaderList -> FlagSetter -> IO StreamThread+headers :: Http2Stream -> HeaderList -> FlagSetter -> ClientIO StreamThread headers = _headers  -- | Starts a new Http2Client around a frame connection.@@ -333,9 +337,9 @@   -> FallBackFrameHandler   -- ^ Actions to run when a control frame is not yet handled in http2-client   -- lib (e.g., PRIORITY frames).-  -> (Http2Client -> IO a)+  -> (Http2Client -> ClientIO a)   -- ^ Actions to run on the client.-  -> IO a+  -> ClientIO a runHttp2Client conn encoderBufSize decoderBufSize initSettings goAwayHandler fallbackHandler mainHandler = do     (incomingLoop, initClient) <- initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler     withAsync incomingLoop $ \aIncoming -> do@@ -376,7 +380,7 @@   -> FallBackFrameHandler   -- ^ Actions to run when a control frame is not yet handled in http2-client   -- lib (e.g., PRIORITY frames).-  -> IO Http2Client+  -> ClientIO Http2Client newHttp2Client conn encoderBufSize decoderBufSize initSettings goAwayHandler fallbackHandler = do     (incomingLoop, initClient) <- initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler     aIncoming <- async incomingLoop@@ -401,7 +405,7 @@   -> Int   -> GoAwayHandler   -> FallBackFrameHandler-  -> IO (IO (), InitHttp2Client)+  -> ClientIO (ClientIO (), InitHttp2Client) initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler = do     let controlStream = makeFrameClientStream conn 0     let ackPing = sendPingFrame controlStream HTTP2.setAck@@ -416,9 +420,9 @@                                             fallbackHandler      let baseWindowSize = return HTTP2.defaultInitialWindowSize-    _initIncomingFlowControl <- newIncomingFlowControl dispatchControl baseWindowSize (sendWindowUpdateFrame controlStream)+    _initIncomingFlowControl <- lift $ newIncomingFlowControl dispatchControl baseWindowSize (sendWindowUpdateFrame controlStream)     windowUpdatesChan <- newChan-    _initOutgoingFlowControl <- newOutgoingFlowControl dispatchControl windowUpdatesChan baseWindowSize+    _initOutgoingFlowControl <- lift $ newOutgoingFlowControl dispatchControl windowUpdatesChan baseWindowSize      dispatchHPACK <- newDispatchHPACKIO decoderBufSize     (incomingLoop,endIncomingLoop) <- dispatchLoop conn dispatch dispatchControl windowUpdatesChan _initIncomingFlowControl dispatchHPACK@@ -454,21 +458,21 @@                 pure v      let _initPing dat = do-            handler <- registerPingHandler dispatchControl dat+            handler <- lift $ registerPingHandler dispatchControl dat             sendPingFrame controlStream id dat-            return $ waitPingReply handler+            return $ lift $ waitPingReply handler      let _initSettings settslist = do-            handler <- registerSetSettingsHandler dispatchControl+            handler <- lift $ registerSetSettingsHandler dispatchControl             sendSettingsFrame controlStream id settslist             return $ do-                ret <- waitSetSettingsReply handler+                ret <- lift $ waitSetSettingsReply handler                 modifySettings dispatchControl                     (\(ConnectionSettings cli srv) ->                         (ConnectionSettings (HTTP2.updateSettings cli settslist) srv, ()))                 return ret     let _initGoaway err errStr = do-            sId <- readMaxReceivedStreamIdIO dispatch+            sId <- lift $ readMaxReceivedStreamIdIO dispatch             sendGTFOFrame controlStream sId err errStr      let _initPaylodSplitter = settingsPayloadSplitter <$> readSettings dispatchControl@@ -487,7 +491,7 @@   -> Chan (FrameHeader, FramePayload)   -> (Http2Stream -> StreamDefinition a)   -> StreamFSMState-  -> IO (IO a)+  -> ClientIO (ClientIO a) initializeStream conn dispatch control stream windowUpdatesChan getWork initialState = do     let sid = _dispatchStreamId stream     let frameStream = makeFrameClientStream conn sid@@ -535,9 +539,9 @@     -- lock on clientStreamIdMutex will be released.     return $ do         let baseIncomingWindowSize = initialWindowSize . _clientSettings <$> readSettings control-        isfc <- newIncomingFlowControl control baseIncomingWindowSize (sendWindowUpdateFrame frameStream)+        isfc <- lift $ newIncomingFlowControl control baseIncomingWindowSize (sendWindowUpdateFrame frameStream)         let baseOutgoingWindowSize = initialWindowSize . _serverSettings <$> readSettings control-        osfc <- newOutgoingFlowControl control windowUpdatesChan baseOutgoingWindowSize+        osfc <- lift $ newOutgoingFlowControl control windowUpdatesChan baseOutgoingWindowSize         _handleStream streamActions isfc osfc  dispatchLoop@@ -547,7 +551,7 @@   -> Chan (FrameHeader, FramePayload)   -> IncomingFlowControl   -> DispatchHPACK-  -> IO (IO (), IO Bool)+  -> ClientIO (ClientIO (), ClientIO Bool) dispatchLoop conn d dc windowUpdatesChan inFlowControl dh = do     let getNextFrame = next conn     let go = delayException . forever $ do@@ -560,19 +564,19 @@             whenFrame (hasTypeId [FrameWindowUpdate]) frame $ \got -> do                 updateWindowsStep d got             whenFrame (hasTypeId [FramePushPromise, FrameHeaders]) frame $ \got -> do-                let hpackLoop (FinishedWithHeaders curFh sId mkNewHdrs) = do+                let hpackLoop (FinishedWithHeaders curFh sId mkNewHdrs) = lift $ do                         newHdrs <- mkNewHdrs                         chan <- fmap _streamStateEvents <$> lookupStreamState d sId                         let msg = StreamHeadersEvent curFh newHdrs                         maybe (return ()) (flip writeChan msg) chan-                    hpackLoop (FinishedWithPushPromise curFh parentSid newSid mkNewHdrs) = do+                    hpackLoop (FinishedWithPushPromise curFh parentSid newSid mkNewHdrs) = lift $ do                         newHdrs <- mkNewHdrs                         chan <- fmap _streamStateEvents <$> lookupStreamState d parentSid                         let msg = StreamPushPromiseEvent curFh newSid newHdrs                         maybe (return ()) (flip writeChan msg) chan                     hpackLoop (WaitContinuation act)        =                         getNextFrame >>= act >>= hpackLoop-                    hpackLoop (FailedHeaders curFh sId err)        = do+                    hpackLoop (FailedHeaders curFh sId err)        = lift $ do                         chan <- fmap _streamStateEvents <$> lookupStreamState d sId                         let msg = StreamErrorEvent curFh err                         maybe (return ()) (flip writeChan msg) chan@@ -588,11 +592,11 @@ handleRSTStep   :: Dispatch   -> (FrameHeader, FramePayload)-  -> IO ()+  -> ClientIO () handleRSTStep d (fh, payload) = do     let sid = streamId fh     case payload of-        (RSTStreamFrame err) -> do+        (RSTStreamFrame err) -> lift $ do             chan <- fmap _streamStateEvents <$> lookupStreamState d sid             let msg = StreamErrorEvent fh (HTTP2.fromErrorCodeId err)             maybe (return ()) (flip writeChan msg) chan@@ -603,7 +607,7 @@ dispatchFramesStep   :: (FrameHeader, Either HTTP2Error FramePayload)   -> Dispatch-  -> IO ()+  -> ClientIO () dispatchFramesStep (fh,_) d = do     let sid = streamId fh     -- Remember highest streamId.@@ -612,7 +616,7 @@ finalizeFramesStep   :: (FrameHeader, Either HTTP2Error FramePayload)   -> Dispatch-  -> IO ()+  -> ClientIO () finalizeFramesStep (fh,_) d = do     let sid = streamId fh     -- Remote-close streams that match.@@ -623,7 +627,7 @@   :: Chan (FrameHeader, FramePayload)   -> (FrameHeader, FramePayload)   -> DispatchControl-  -> IO ()+  -> ClientIO () dispatchControlFramesStep windowUpdatesChan controlFrame@(fh, payload) control@(DispatchControl{..}) = do     case payload of         (SettingsFrame settsList)@@ -631,7 +635,7 @@                 atomicModifyIORef' _dispatchControlConnectionSettings                                    (\(ConnectionSettings cli srv) ->                                       (ConnectionSettings cli (HTTP2.updateSettings srv settsList), ()))-                maybe (return ())+                lift $ maybe (return ())                       (_applySettings _dispatchControlHpackEncoder)                       (lookup SettingsHeaderTableSize settsList)                 _dispatchControlAckSettings@@ -662,12 +666,12 @@   :: Dispatch   -> IncomingFlowControl   -> (FrameHeader, FramePayload)-  -> IO ()+  -> ClientIO () creditDataFramesStep d flowControl (fh,payload) = do     -- TODO: error if detect over-run. Current implementation credits     -- everything back. Hence, over-run should never happen.-    _ <- _consumeCredit flowControl (HTTP2.payloadLength fh)-    _addCredit flowControl (HTTP2.payloadLength fh)+    _ <- lift $ _consumeCredit flowControl (HTTP2.payloadLength fh)+    lift $ _addCredit flowControl (HTTP2.payloadLength fh)      -- Write to the interested streams.     let sid = streamId fh@@ -681,7 +685,7 @@ updateWindowsStep   :: Dispatch   -> (FrameHeader, FramePayload)-  -> IO ()+  -> ClientIO () updateWindowsStep d got@(fh,_) = do     let sid = HTTP2.streamId fh     chan <- fmap _streamStateWindowUpdatesChan <$> lookupStreamState d sid@@ -692,7 +696,7 @@   | OpenPushPromise !StreamId !StreamId  data HPACKStepResult =-    WaitContinuation !((FrameHeader, Either HTTP2Error FramePayload) -> IO HPACKStepResult)+    WaitContinuation !((FrameHeader, Either HTTP2Error FramePayload) -> ClientIO HPACKStepResult)   | FailedHeaders !FrameHeader !StreamId ErrorCode   | FinishedWithHeaders !FrameHeader !StreamId (IO HeaderList)   | FinishedWithPushPromise !FrameHeader !StreamId !StreamId (IO HeaderList)@@ -745,7 +749,7 @@   :: DispatchControl   -> IO Int   -- ^ Action to get the base window size.-  -> (WindowSize -> IO())+  -> (WindowSize -> ClientIO ())   -- ^ Action to send the window update to the server.   -> IO IncomingFlowControl newIncomingFlowControl control getBase doSendUpdate = do@@ -766,7 +770,7 @@             let shouldUpdate = transferred > 0              _addCredit (negate transferred)-            _ <- _consumeCredit (negate transferred)+            _ <- lift $ _consumeCredit (negate transferred)             when shouldUpdate (doSendUpdate transferred)              return shouldUpdate@@ -784,7 +788,7 @@     let receive n = atomicModifyIORef' credit (\c -> (c + n, ()))     let withdraw 0 = return 0         withdraw n = do-            base <- getBase+            base <- lift getBase             got <- atomicModifyIORef' credit (\c ->                     if base + c >= n                     then (c - n, n)@@ -821,9 +825,9 @@   -> HeaderList   -> PayloadSplitter   -> (FrameFlags -> FrameFlags)-  -> IO StreamThread+  -> ClientIO StreamThread sendHeaders s enc hdrs blockSplitter flagmod = do-    _sendFrames s mkFrames+    _sendFrames s (lift mkFrames)     return CST   where     mkFrames = do@@ -873,9 +877,9 @@ -- streams, although no priority queue exists in `http2-client` so far). -- -- Please refer to '_sendDataChunk' and '_withdrawCredit' as well.-sendData :: Http2Client -> Http2Stream -> FlagSetter -> ByteString -> IO ()+sendData :: Http2Client -> Http2Stream -> FlagSetter -> ByteString -> ClientIO () sendData conn stream flagmod dat = do-    splitter <- _payloadSplitter conn+    splitter <- lift  $ _payloadSplitter conn     let chunks = splitter dat     let pairs  = reverse $ zip (flagmod : repeat id) (reverse chunks)     when (null chunks) $ _sendDataChunk stream flagmod ""@@ -883,17 +887,17 @@  sendDataFrame   :: Http2FrameClientStream-  -> (FrameFlags -> FrameFlags) -> ByteString -> IO ()+  -> (FrameFlags -> FrameFlags) -> ByteString -> ClientIO () sendDataFrame s flagmod dat = do     sendOne s flagmod (DataFrame dat) -sendResetFrame :: Http2FrameClientStream -> ErrorCodeId -> IO ()+sendResetFrame :: Http2FrameClientStream -> ErrorCodeId -> ClientIO () sendResetFrame s err = do     sendOne s id (RSTStreamFrame err)  sendGTFOFrame   :: Http2FrameClientStream-     -> StreamId -> ErrorCodeId -> ByteString -> IO ()+     -> StreamId -> ErrorCodeId -> ByteString -> ClientIO () sendGTFOFrame s lastStreamId err errStr = do     sendOne s id (GoAwayFrame lastStreamId err errStr) @@ -904,7 +908,7 @@   :: Http2FrameClientStream   -> (FrameFlags -> FrameFlags)   -> ByteString-  -> IO ()+  -> ClientIO () sendPingFrame s flags dat   | _getStreamId s /= 0        =         rfcError "PING frames are not associated with any individual stream."@@ -913,7 +917,7 @@   | otherwise                  = sendOne s flags (PingFrame dat)  sendWindowUpdateFrame-  :: Http2FrameClientStream -> WindowSize -> IO ()+  :: Http2FrameClientStream -> WindowSize -> ClientIO () sendWindowUpdateFrame s amount = do     let payload = WindowUpdateFrame amount     sendOne s id payload@@ -921,7 +925,7 @@  sendSettingsFrame   :: Http2FrameClientStream-     -> (FrameFlags -> FrameFlags) -> SettingsList -> IO ()+     -> (FrameFlags -> FrameFlags) -> SettingsList -> ClientIO () sendSettingsFrame s flags setts   | _getStreamId s /= 0        =         rfcError "The stream identifier for a SETTINGS frame MUST be zero (0x0)."@@ -930,7 +934,7 @@     sendOne s flags payload     return () -sendPriorityFrame :: Http2FrameClientStream -> Priority -> IO ()+sendPriorityFrame :: Http2FrameClientStream -> Priority -> ClientIO () sendPriorityFrame s p = do     let payload = PriorityFrame p     sendOne s id payload@@ -949,9 +953,9 @@ -- and future design will be to inline the various loop-processes for -- dispatchFrames and GoAwayHandlers in a same thread (e.g., using -- pipe/conduit to retain composability).-delayException :: IO a -> IO a+delayException :: ClientIO a -> ClientIO a delayException act = act `catch` slowdown   where-    slowdown :: SomeException -> IO a+    slowdown :: SomeException -> ClientIO a     slowdown e = threadDelay 50000 >> throwIO e 
src/Network/HTTP2/Client/Channels.hs view
@@ -6,21 +6,23 @@  , whenFrame  , whenFrameElse  -- re-exports- , module Control.Concurrent.Chan+ , module Control.Concurrent.Chan.Lifted  ) where -import           Control.Concurrent.Chan (Chan, readChan, newChan, writeChan)-import           Control.Exception (Exception, throwIO)+import           Control.Concurrent.Chan.Lifted (Chan, readChan, newChan, writeChan)+import           Control.Exception.Lifted (Exception, throwIO) import           Network.HTTP2 (StreamId, FrameHeader, FramePayload, FrameTypeId, framePayloadToFrameTypeId, streamId) +import           Network.HTTP2.Client.Exceptions+ type FramesChan e = Chan (FrameHeader, Either e FramePayload)  whenFrame   :: Exception e   => (FrameHeader -> FramePayload -> Bool)   -> (FrameHeader, Either e FramePayload)-  -> ((FrameHeader, FramePayload) -> IO ())-  -> IO ()+  -> ((FrameHeader, FramePayload) -> ClientIO ())+  -> ClientIO () whenFrame test frame handle = do     whenFrameElse test frame handle (const $ pure ()) @@ -28,9 +30,9 @@   :: Exception e   => (FrameHeader -> FramePayload -> Bool)   -> (FrameHeader, Either e FramePayload)-  -> ((FrameHeader, FramePayload) -> IO a)-  -> ((FrameHeader, FramePayload) -> IO a)-  -> IO a+  -> ((FrameHeader, FramePayload) -> ClientIO a)+  -> ((FrameHeader, FramePayload) -> ClientIO a)+  -> ClientIO a whenFrameElse test (fHead, fPayload) handleTrue handleFalse = do     dat <- either throwIO pure fPayload     if test fHead dat
src/Network/HTTP2/Client/Dispatch.hs view
@@ -1,12 +1,14 @@+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE BangPatterns #-} module Network.HTTP2.Client.Dispatch where  import           Control.Exception (throwIO)+import           Control.Monad.Base (MonadBase, liftBase) import           Data.ByteString (ByteString) import qualified Data.ByteString.Internal as ByteString import           Foreign.Marshal.Alloc (mallocBytes, finalizerFree) import           Foreign.ForeignPtr (newForeignPtr)-import           Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)+import           Data.IORef.Lifted (IORef, atomicModifyIORef', newIORef, readIORef) import           Data.IntMap (IntMap) import qualified Data.IntMap as IntMap import           GHC.Exception (Exception)@@ -15,11 +17,12 @@ import           Network.HTTP2 as HTTP2  import           Network.HTTP2.Client.Channels+import           Network.HTTP2.Client.Exceptions  type DispatchChan = FramesChan HTTP2Error  -- | A fallback handler for frames.-type FallBackFrameHandler = (FrameHeader, FramePayload) -> IO ()+type FallBackFrameHandler = (FrameHeader, FramePayload) -> ClientIO ()  -- | Default FallBackFrameHandler that ignores frames. ignoreFallbackHandler :: FallBackFrameHandler@@ -31,7 +34,7 @@ instance Exception RemoteSentGoAwayFrame  -- | A Handler for exceptional circumstances.-type GoAwayHandler = RemoteSentGoAwayFrame -> IO ()+type GoAwayHandler = RemoteSentGoAwayFrame -> ClientIO ()  -- | Default GoAwayHandler throws a 'RemoteSentGoAwayFrame' in the current -- thread.@@ -44,7 +47,7 @@ -- server will likely close the connection which will lead to TCP errors as -- well. defaultGoAwayHandler :: GoAwayHandler-defaultGoAwayHandler = throwIO+defaultGoAwayHandler = lift . throwIO  data StreamFSMState =     Idle@@ -72,22 +75,22 @@   , _dispatchCurrentStreams :: !(IORef (IntMap StreamState))   } -newDispatchIO :: IO Dispatch+newDispatchIO :: MonadBase IO m => m Dispatch newDispatchIO = Dispatch <$> newIORef 0 <*> newIORef (IntMap.empty) -readMaxReceivedStreamIdIO :: Dispatch -> IO StreamId+readMaxReceivedStreamIdIO :: MonadBase IO m => Dispatch -> m StreamId readMaxReceivedStreamIdIO = readIORef . _dispatchMaxStreamId -registerStream :: Dispatch -> StreamId -> StreamState -> IO ()+registerStream :: MonadBase IO m => Dispatch -> StreamId -> StreamState -> m () registerStream d sid st =     atomicModifyIORef' (_dispatchCurrentStreams d) $ \xs ->       let v = (IntMap.insert sid st xs) in (v, ()) -lookupStreamState :: Dispatch -> StreamId -> IO (Maybe StreamState)+lookupStreamState :: MonadBase IO m => Dispatch -> StreamId -> m (Maybe StreamState) lookupStreamState d sid =     IntMap.lookup sid <$> readIORef (_dispatchCurrentStreams d) -closeLocalStream :: Dispatch -> StreamId -> IO ()+closeLocalStream :: MonadBase IO m => Dispatch -> StreamId -> m () closeLocalStream d sid =     atomicModifyIORef' (_dispatchCurrentStreams d) $ \xs ->       let (_,v) = IntMap.updateLookupWithKey f sid xs in (v, ())@@ -98,7 +101,7 @@         Closed           -> Nothing         _ -> Just $ st { _streamStateFSMState = HalfClosedLocal } -closeRemoteStream :: Dispatch -> StreamId -> IO ()+closeRemoteStream :: MonadBase IO m => Dispatch -> StreamId -> m () closeRemoteStream d sid =     atomicModifyIORef' (_dispatchCurrentStreams d) $ \xs ->       let (_,v) = IntMap.updateLookupWithKey f sid xs in (v, ())@@ -109,7 +112,7 @@         Closed           -> Nothing         _ -> Just $ st { _streamStateFSMState = HalfClosedRemote } -closeReleaseStream :: Dispatch -> StreamId -> IO ()+closeReleaseStream :: MonadBase IO m => Dispatch -> StreamId -> m () closeReleaseStream d sid =     atomicModifyIORef' (_dispatchCurrentStreams d) $ \xs ->       let v = (IntMap.delete sid xs) in (v, ())@@ -126,24 +129,24 @@  data PingHandler = PingHandler !(Chan (FrameHeader, FramePayload)) -newPingHandler :: IO PingHandler+newPingHandler :: MonadBase IO m => m PingHandler newPingHandler = PingHandler <$> newChan -notifyPingHandler :: (FrameHeader, FramePayload) -> PingHandler -> IO ()+notifyPingHandler :: MonadBase IO m => (FrameHeader, FramePayload) -> PingHandler -> m () notifyPingHandler dat (PingHandler c) = writeChan c dat -waitPingReply :: PingHandler -> IO (FrameHeader, FramePayload)+waitPingReply :: MonadBase IO m => PingHandler -> m (FrameHeader, FramePayload) waitPingReply (PingHandler c) = readChan c  data SetSettingsHandler = SetSettingsHandler !(Chan (FrameHeader, FramePayload)) -newSetSettingsHandler :: IO SetSettingsHandler+newSetSettingsHandler :: MonadBase IO m => m SetSettingsHandler newSetSettingsHandler = SetSettingsHandler <$> newChan -notifySetSettingsHandler :: (FrameHeader, FramePayload) -> SetSettingsHandler -> IO ()+notifySetSettingsHandler :: MonadBase IO m => (FrameHeader, FramePayload) -> SetSettingsHandler -> m () notifySetSettingsHandler dat (SetSettingsHandler c) = writeChan c dat -waitSetSettingsReply :: SetSettingsHandler -> IO (FrameHeader, FramePayload)+waitSetSettingsReply :: MonadBase IO m => SetSettingsHandler -> m (FrameHeader, FramePayload) waitSetSettingsReply (SetSettingsHandler c) = readChan c  registerPingHandler :: DispatchControl -> ByteString -> IO PingHandler@@ -153,7 +156,7 @@         ((dat,handler):xs, ()))     return handler -lookupAndReleasePingHandler :: DispatchControl -> ByteString -> IO (Maybe PingHandler)+lookupAndReleasePingHandler :: MonadBase IO m => DispatchControl -> ByteString -> m (Maybe PingHandler) lookupAndReleasePingHandler dc dat =     atomicModifyIORef' (_dispatchControlPingHandlers dc) f   where@@ -162,14 +165,14 @@     -- storing the handlers).     f xs = (filter (\x -> dat /= fst x) xs, lookup dat xs) -registerSetSettingsHandler :: DispatchControl -> IO SetSettingsHandler+registerSetSettingsHandler :: MonadBase IO m => DispatchControl -> m SetSettingsHandler registerSetSettingsHandler dc = do     handler <- newSetSettingsHandler     atomicModifyIORef' (_dispatchControlSetSettingsHandlers dc) (\xs ->         (handler:xs, ()))     return handler -lookupAndReleaseSetSettingsHandler :: DispatchControl -> IO (Maybe SetSettingsHandler)+lookupAndReleaseSetSettingsHandler :: MonadBase IO m => DispatchControl -> m (Maybe SetSettingsHandler) lookupAndReleaseSetSettingsHandler dc =     atomicModifyIORef' (_dispatchControlSetSettingsHandlers dc) f   where@@ -179,8 +182,8 @@ data DispatchControl = DispatchControl {     _dispatchControlConnectionSettings  :: !(IORef ConnectionSettings)   , _dispatchControlHpackEncoder        :: !HpackEncoderContext-  , _dispatchControlAckPing             :: !(ByteString -> IO ())-  , _dispatchControlAckSettings         :: !(IO ())+  , _dispatchControlAckPing             :: !(ByteString -> ClientIO ())+  , _dispatchControlAckSettings         :: !(ClientIO ())   , _dispatchControlOnGoAway            :: !GoAwayHandler   , _dispatchControlOnFallback          :: !FallBackFrameHandler   , _dispatchControlPingHandlers        :: !(IORef [(ByteString, PingHandler)])@@ -188,12 +191,13 @@   }  newDispatchControlIO-  :: Size-  -> (ByteString -> IO ())-  -> (IO ())+  :: MonadBase IO m+  => Size+  -> (ByteString -> ClientIO ())+  -> (ClientIO ())   -> GoAwayHandler   -> FallBackFrameHandler-  -> IO DispatchControl+  -> m DispatchControl newDispatchControlIO encoderBufSize ackPing ackSetts onGoAway onFallback =     DispatchControl <$> newIORef defaultConnectionSettings                     <*> newHpackEncoderContext encoderBufSize@@ -204,14 +208,14 @@                     <*> newIORef []                     <*> newIORef [] -newHpackEncoderContext :: Size -> IO HpackEncoderContext-newHpackEncoderContext encoderBufSize = do+newHpackEncoderContext :: MonadBase IO m => Size -> m HpackEncoderContext+newHpackEncoderContext encoderBufSize = liftBase $ do     let strategy = (HPACK.defaultEncodeStrategy { HPACK.useHuffman = True })     dt <- HPACK.newDynamicTableForEncoding HPACK.defaultDynamicTableSize     buf <- mallocBytes encoderBufSize     ptr <- newForeignPtr finalizerFree buf     return $ HpackEncoderContext-            (encoder strategy dt buf ptr)+            (\hdrs -> encoder strategy dt buf ptr hdrs)             (\n -> HPACK.setLimitForEncoding n dt)   where     encoder strategy dt buf ptr hdrs = do@@ -221,10 +225,10 @@             ([],len) -> pure $ ByteString.fromForeignPtr ptr 0 len             (_,_)  -> throwIO HPACK.BufferOverrun -readSettings :: DispatchControl -> IO ConnectionSettings+readSettings :: MonadBase IO m => DispatchControl -> m ConnectionSettings readSettings = readIORef . _dispatchControlConnectionSettings -modifySettings :: DispatchControl -> (ConnectionSettings -> (ConnectionSettings, a)) -> IO a+modifySettings :: MonadBase IO m => DispatchControl -> (ConnectionSettings -> (ConnectionSettings, a)) -> m a modifySettings d = atomicModifyIORef' (_dispatchControlConnectionSettings d)  -- | Helper to carry around the HPACK encoder for outgoing header blocks..@@ -237,8 +241,8 @@     _dispatchHPACKDynamicTable          :: !DynamicTable   } -newDispatchHPACKIO :: Size -> IO DispatchHPACK-newDispatchHPACKIO decoderBufSize =+newDispatchHPACKIO :: MonadBase IO m => Size -> m DispatchHPACK+newDispatchHPACKIO decoderBufSize = liftBase $     DispatchHPACK <$> newDecoder   where     newDecoder = newDynamicTableForDecoding@@ -250,7 +254,7 @@   , _dispatchStreamReadEvents :: !(Chan StreamEvent)   } -newDispatchStreamIO :: StreamId -> IO DispatchStream-newDispatchStreamIO sid =+newDispatchStreamIO :: MonadBase IO m => StreamId -> m DispatchStream+newDispatchStreamIO sid = liftBase $     DispatchStream <$> pure sid                    <*> newChan
+ src/Network/HTTP2/Client/Exceptions.hs view
@@ -0,0 +1,22 @@++module Network.HTTP2.Client.Exceptions (+    ClientIO+  , ClientError(..)+  , runClientIO+  , module Control.Monad.Except+  ) where++import           Control.Exception (Exception)+import           Control.Monad.Except (ExceptT, runExceptT, throwError, lift)++type ClientIO = ExceptT ClientError IO++runClientIO :: ClientIO a -> IO (Either ClientError a)+runClientIO = runExceptT++-- | A set of errors as observed from the client.+data ClientError = EarlyEndOfStream+  -- ^ We received a TCP end-of-stream and there will be no further read-IOs possible on the connection.+  deriving (Show,Ord,Eq)+instance Exception ClientError -- For people who want to rethrow using Control.Exception+
src/Network/HTTP2/Client/FrameConnection.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards  #-} {-# LANGUAGE RankNTypes  #-}@@ -16,14 +17,16 @@     ) where  import           Control.DeepSeq (deepseq)-import           Control.Exception (bracket)-import           Control.Concurrent.MVar (newMVar, takeMVar, putMVar)-import           Control.Monad ((>=>), void)+import           Control.Exception.Lifted (bracket)+import           Control.Concurrent.MVar.Lifted (newMVar, takeMVar, putMVar)+import           Control.Monad ((>=>), void, when)+import qualified Data.ByteString as ByteString import           Network.HTTP2 (FrameHeader(..), FrameFlags, FramePayload, HTTP2Error, encodeInfo, decodeFramePayload) import qualified Network.HTTP2 as HTTP2 import           Network.Socket (HostName, PortNumber) import qualified Network.TLS as TLS +import           Network.HTTP2.Client.Exceptions import           Network.HTTP2.Client.RawConnection  data Http2FrameConnection = Http2FrameConnection {@@ -31,12 +34,12 @@   -- ^ Starts a new client stream.   , _serverStream     :: Http2ServerStream   -- ^ Receives frames from a server.-  , _closeConnection  :: IO ()+  , _closeConnection  :: ClientIO ()   -- ^ Function that will close the network connection.   }  -- | Closes the Http2FrameConnection abruptly.-closeConnection :: Http2FrameConnection -> IO ()+closeConnection :: Http2FrameConnection -> ClientIO () closeConnection = _closeConnection  -- | Creates a client stream.@@ -46,7 +49,7 @@ makeFrameClientStream = _makeFrameClientStream  data Http2FrameClientStream = Http2FrameClientStream {-    _sendFrames :: IO [(FrameFlags -> FrameFlags, FramePayload)] -> IO ()+    _sendFrames :: ClientIO [(FrameFlags -> FrameFlags, FramePayload)] -> ClientIO ()   -- ^ Sends a frame to the server.   -- The first argument is a FrameFlags modifier (e.g., to sed the   -- end-of-stream flag).@@ -54,25 +57,25 @@   }  -- | Sends a frame to the server.-sendOne :: Http2FrameClientStream -> (FrameFlags -> FrameFlags) -> FramePayload -> IO ()+sendOne :: Http2FrameClientStream -> (FrameFlags -> FrameFlags) -> FramePayload -> ClientIO () sendOne client f payload = _sendFrames client (pure [(f, payload)])  -- | Sends multiple back-to-back frames to the server.-sendBackToBack :: Http2FrameClientStream -> [(FrameFlags -> FrameFlags, FramePayload)] -> IO ()+sendBackToBack :: Http2FrameClientStream -> [(FrameFlags -> FrameFlags, FramePayload)] -> ClientIO () sendBackToBack client payloads = _sendFrames client (pure payloads)  data Http2ServerStream = Http2ServerStream {-    _nextHeaderAndFrame :: IO (FrameHeader, Either HTTP2Error FramePayload)+    _nextHeaderAndFrame :: ClientIO (FrameHeader, Either HTTP2Error FramePayload)   }  -- | Waits for the next frame from the server.-next :: Http2FrameConnection -> IO (FrameHeader, Either HTTP2Error FramePayload)+next :: Http2FrameConnection -> ClientIO (FrameHeader, Either HTTP2Error FramePayload) next = _nextHeaderAndFrame . _serverStream  -- | Adds framing around a 'RawHttp2Connection'. frameHttp2RawConnection   :: RawHttp2Connection-  -> IO Http2FrameConnection+  -> ClientIO Http2FrameConnection frameHttp2RawConnection http2conn = do     -- Prepare a local mutex, this mutex should never escape the     -- function's scope. Else it might lead to bugs (e.g.,@@ -96,13 +99,16 @@              in Http2FrameClientStream putFrames streamID          nextServerFrameChunk = Http2ServerStream $ do-            (fTy, fh@FrameHeader{..}) <- HTTP2.decodeFrameHeader <$> _nextRaw http2conn 9+            b9 <- _nextRaw http2conn 9+            when (ByteString.length b9 /= 9) $ throwError $ EarlyEndOfStream+            let (fTy, fh@FrameHeader{..}) = HTTP2.decodeFrameHeader b9             let decoder = decodeFramePayload fTy+            buf <- _nextRaw http2conn payloadLength+            when (ByteString.length buf /= payloadLength) $ throwError $ EarlyEndOfStream             -- TODO: consider splitting the iteration here to give a chance to             -- _not_ decode the frame, or consider lazyness enough.-            let getNextFrame = decoder fh <$> _nextRaw http2conn payloadLength-            nf <- getNextFrame-            return (fh, nf)+            let nf = decoder fh buf+            pure (fh, nf)          gtfo = _close http2conn @@ -112,6 +118,6 @@ newHttp2FrameConnection :: HostName                         -> PortNumber                         -> Maybe TLS.ClientParams-                        -> IO Http2FrameConnection+                        -> ClientIO Http2FrameConnection newHttp2FrameConnection host port params = do     frameHttp2RawConnection =<< newRawHttp2Connection host port params
src/Network/HTTP2/Client/Helpers.hs view
@@ -25,10 +25,11 @@ import qualified Network.HPACK as HPACK import           Data.ByteString (ByteString) import qualified Data.ByteString as ByteString-import           Control.Concurrent (threadDelay)-import           Control.Concurrent.Async (race)+import           Control.Concurrent.Lifted (threadDelay)+import           Control.Concurrent.Async.Lifted (race)  import Network.HTTP2.Client+import Network.HTTP2.Client.Exceptions  -- | Opaque type to express an action which timed out. data TimedOut = TimedOut@@ -45,12 +46,12 @@      -- ^ timeout in microseconds      -> ByteString      -- ^ 8-bytes message to uniquely identify the reply-     -> IO PingReply+     -> ClientIO PingReply ping conn timeout msg = do-    t0 <- getCurrentTime+    t0 <- lift $ getCurrentTime     waitPing <- _ping conn msg     pingReply <- race (threadDelay timeout >> return TimedOut) waitPing-    t1 <- getCurrentTime+    t1 <- lift $ getCurrentTime     return $ (t0, t1, pingReply)  -- | Result containing the unpacked headers and all frames received in on a@@ -88,7 +89,7 @@        -- ^ The corresponding HTTP stream.        -> OutgoingFlowControl        -- ^ The flow control for this stream.-       -> IO ()+       -> ClientIO () upload "" flagmod conn _ stream _ = do     sendData conn stream flagmod "" upload dat flagmod conn connectionFlowControl stream streamFlowControl = do@@ -98,7 +99,7 @@     got       <- _withdrawCredit connectionFlowControl gotStream     -- Recredit the stream flow control with the excedent we cannot spend on     -- the connection.-    _receiveCredit streamFlowControl (gotStream - got)+    lift $ _receiveCredit streamFlowControl (gotStream - got)      let uploadChunks flagMod =             sendData conn stream flagMod (ByteString.take got dat)@@ -118,7 +119,7 @@ waitStream :: Http2Stream            -> IncomingFlowControl            -> PushPromiseHandler-           -> IO StreamResult+           -> ClientIO StreamResult waitStream stream streamFlowControl ppHandler = do     ev <- _waitEvent stream     case ev of@@ -141,8 +142,8 @@                 | HTTP2.testEndStream (HTTP2.flags fh) ->                     return ((Right x):xs, Nothing)                 | otherwise                            -> do-                    _ <- _consumeCredit streamFlowControl (HTTP2.payloadLength fh)-                    _addCredit streamFlowControl (HTTP2.payloadLength fh)+                    _ <- lift $ _consumeCredit streamFlowControl (HTTP2.payloadLength fh)+                    lift $ _addCredit streamFlowControl (HTTP2.payloadLength fh)                     _ <- _updateWindow $ streamFlowControl                     waitDataFrames ((Right x):xs)             StreamPushPromiseEvent _ ppSid ppHdrs -> do
src/Network/HTTP2/Client/RawConnection.hs view
@@ -9,8 +9,8 @@     ) where  import           Control.Monad (forever, when)-import           Control.Concurrent.Async (Async, async, cancel, pollSTM)-import           Control.Concurrent.STM (STM, atomically, retry, throwSTM)+import           Control.Concurrent.Async.Lifted (Async, async, cancel, pollSTM)+import           Control.Concurrent.STM (STM, atomically, check, orElse, retry, throwSTM) import           Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO, readTVar, writeTVar) import           Data.ByteString (ByteString) import qualified Data.ByteString as ByteString@@ -21,13 +21,18 @@ import           Network.Socket.ByteString import qualified Network.TLS as TLS +import           Network.HTTP2.Client.Exceptions+ -- TODO: catch connection errrors data RawHttp2Connection = RawHttp2Connection {-    _sendRaw :: [ByteString] -> IO ()+    _sendRaw :: [ByteString] -> ClientIO ()   -- ^ Function to send raw data to the server.-  , _nextRaw :: Int -> IO ByteString+  , _nextRaw :: Int -> ClientIO ByteString   -- ^ Function to block reading a datachunk of a given size from the server.-  , _close   :: IO ()+  -- An empty chunk when asking for a length larger than 0 means the underlying+  -- network session is closed. A compliant HTP2 server should have sent a+  -- GOAWAY before such an event occurs.+  , _close   :: ClientIO ()   }  -- | Initiates a RawHttp2Connection with a server.@@ -40,15 +45,17 @@                       -> Maybe TLS.ClientParams                       -- ^ TLS parameters. The 'TLS.onSuggestALPN' hook is                       -- overwritten to always return ["h2", "h2-17"].-                      -> IO RawHttp2Connection+                      -> ClientIO RawHttp2Connection newRawHttp2Connection host port mparams = do     -- Connects to TCP.     let hints = defaultHints { addrFlags = [AI_NUMERICSERV], addrSocketType = Stream }-    addr:_ <- getAddrInfo (Just hints) (Just host) (Just $ show port)-    skt <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)-    setSocketOption skt NoDelay 1-    connect skt (addrAddress addr)-    newRawHttp2ConnectionSocket skt mparams+    rSkt <- lift $ do+        addr:_ <- getAddrInfo (Just hints) (Just host) (Just $ show port)+        skt <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)+        setSocketOption skt NoDelay 1+        connect skt (addrAddress addr)+        pure skt+    newRawHttp2ConnectionSocket rSkt mparams  -- | Initiates a RawHttp2Connection with a server over a connected socket. --@@ -60,10 +67,10 @@   -> Maybe TLS.ClientParams   -- ^ TLS parameters. The 'TLS.onSuggestALPN' hook is   -- overwritten to always return ["h2", "h2-17"].-  -> IO RawHttp2Connection+  -> ClientIO RawHttp2Connection newRawHttp2ConnectionSocket skt mparams = do     -- Prepare structure with abstract API.-    conn <- maybe (plainTextRaw skt) (tlsRaw skt) mparams+    conn <- lift $ maybe (plainTextRaw skt) (tlsRaw skt) mparams      -- Initializes the HTTP2 stream.     _sendRaw conn [HTTP2.connectionPreface]@@ -74,8 +81,8 @@ plainTextRaw skt = do     (b,putRaw) <- startWriteWorker (sendMany skt)     (a,getRaw) <- startReadWorker (recv skt)-    let doClose = cancel a >> cancel b >> close skt-    return $ RawHttp2Connection (atomically . putRaw) (atomically . getRaw) doClose+    let doClose = lift $ cancel a >> cancel b >> close skt+    return $ RawHttp2Connection (lift . atomically . putRaw) (lift . atomically . getRaw) doClose  tlsRaw :: Socket -> TLS.ClientParams -> IO RawHttp2Connection tlsRaw skt params = do@@ -85,9 +92,9 @@      (b,putRaw) <- startWriteWorker (TLS.sendData tlsContext . fromChunks)     (a,getRaw) <- startReadWorker (const $ TLS.recvData tlsContext)-    let doClose       = cancel a >> cancel b >> TLS.bye tlsContext >> TLS.contextClose tlsContext+    let doClose       = lift $ cancel a >> cancel b >> TLS.bye tlsContext >> TLS.contextClose tlsContext -    return $ RawHttp2Connection (atomically . putRaw) (atomically . getRaw) doClose+    return $ RawHttp2Connection (lift . atomically . putRaw) (lift . atomically . getRaw) doClose   where     modifyParams prms = prms {         TLS.clientHooks = (TLS.clientHooks prms) {@@ -117,14 +124,23 @@   :: (Int -> IO ByteString)   -> IO (Async (), (Int -> STM ByteString)) startReadWorker get = do+    remoteClosed <- newTVarIO False+    let onEof = atomically $ writeTVar remoteClosed True+    let emptyByteStringOnEof = readTVar remoteClosed >>= check >> pure ""+     buf <- newTVarIO ""-    a <- async $ readWorkerLoop buf get-    return $ (a, getRawWorker a buf)+    a <- async $ readWorkerLoop buf get onEof -readWorkerLoop :: TVar ByteString -> (Int -> IO ByteString) -> IO ()-readWorkerLoop buf next = forever $ do-    dat <- next 4096-    atomically $ modifyTVar' buf (\bs -> (bs <> dat))+    return $ (a, \len -> getRawWorker a buf len `orElse` emptyByteStringOnEof)++readWorkerLoop :: TVar ByteString -> (Int -> IO ByteString) -> IO () -> IO ()+readWorkerLoop buf next onEof = go+  where+    go = do+        dat <- next 4096+        if ByteString.null dat+        then onEof+        else atomically (modifyTVar' buf (\bs -> (bs <> dat))) >> go  getRawWorker :: Async () -> TVar ByteString -> Int -> STM ByteString getRawWorker a buf amount = do