http2-client 0.4.0.0 → 0.5.0.0
raw patch · 8 files changed
+580/−375 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Network.HTTP2.Client: [_HPACKAsync] :: Http2ClientAsyncs -> Async ()
- Network.HTTP2.Client: [_controlFramesAsync] :: Http2ClientAsyncs -> Async ()
- Network.HTTP2.Client: [_creditFlowsAsync] :: Http2ClientAsyncs -> Async ()
- Network.HTTP2.Client: [_paylodSplitter] :: Http2Client -> IO PayloadSplitter
- Network.HTTP2.Client: instance GHC.Exception.Exception Network.HTTP2.Client.RemoteSentGoAwayFrame
- Network.HTTP2.Client: instance GHC.Show.Show Network.HTTP2.Client.RemoteSentGoAwayFrame
+ Network.HTTP2.Client: [_payloadSplitter] :: Http2Client -> IO PayloadSplitter
+ Network.HTTP2.Client: newHttp2Client :: Http2FrameConnection -> Int -> Int -> SettingsList -> GoAwayHandler -> FallBackFrameHandler -> IO Http2Client
- Network.HTTP2.Client: Http2ClientAsyncs :: Async (FrameHeader, FramePayload) -> Async () -> Async () -> Async () -> Async () -> Http2ClientAsyncs
+ Network.HTTP2.Client: Http2ClientAsyncs :: Async (FrameHeader, FramePayload) -> Async () -> Http2ClientAsyncs
- Network.HTTP2.Client: Http2Stream :: (HeaderList -> (FrameFlags -> FrameFlags) -> IO StreamThread) -> (Priority -> IO ()) -> (ErrorCodeId -> IO ()) -> IO (FrameHeader, StreamId, Either ErrorCode HeaderList) -> IO (FrameHeader, Either ErrorCode ByteString) -> ((FrameFlags -> FrameFlags) -> ByteString -> IO ()) -> (PushPromiseHandler -> IO ()) -> Http2Stream
+ Network.HTTP2.Client: Http2Stream :: (HeaderList -> (FrameFlags -> FrameFlags) -> IO StreamThread) -> (Priority -> IO ()) -> (ErrorCodeId -> IO ()) -> IO (FrameHeader, StreamId, Either ErrorCode HeaderList) -> IO (FrameHeader, Either ErrorCode ByteString) -> ((FrameFlags -> FrameFlags) -> ByteString -> IO ()) -> Maybe (PushPromiseHandler -> IO ()) -> Http2Stream
- Network.HTTP2.Client: [_waitPushPromise] :: Http2Stream -> PushPromiseHandler -> IO ()
+ Network.HTTP2.Client: [_waitPushPromise] :: Http2Stream -> Maybe (PushPromiseHandler -> IO ())
- Network.HTTP2.Client.FrameConnection: newHttp2FrameConnection :: HostName -> PortNumber -> ClientParams -> IO Http2FrameConnection
+ Network.HTTP2.Client.FrameConnection: newHttp2FrameConnection :: HostName -> PortNumber -> Maybe ClientParams -> IO Http2FrameConnection
- Network.HTTP2.Client.RawConnection: newRawHttp2Connection :: HostName -> PortNumber -> ClientParams -> IO RawHttp2Connection
+ Network.HTTP2.Client.RawConnection: newRawHttp2Connection :: HostName -> PortNumber -> Maybe ClientParams -> IO RawHttp2Connection
Files
- app/Main.hs +1/−1
- http2-client.cabal +3/−1
- src/Network/HTTP2/Client.hs +288/−365
- src/Network/HTTP2/Client/Channels.hs +146/−0
- src/Network/HTTP2/Client/Dispatch.hs +130/−0
- src/Network/HTTP2/Client/FrameConnection.hs +1/−1
- src/Network/HTTP2/Client/Helpers.hs +6/−2
- src/Network/HTTP2/Client/RawConnection.hs +5/−5
app/Main.hs view
@@ -162,7 +162,7 @@ ] timePrint conf - frameConn <- newHttp2FrameConnection _host _port tlsParams+ frameConn <- newHttp2FrameConnection _host _port (Just tlsParams) let wrappedFrameConn = frameConn { _makeFrameClientStream = \sid -> let frameClient = (_makeFrameClientStream frameConn) sid
http2-client.cabal view
@@ -1,5 +1,5 @@ name: http2-client-version: 0.4.0.0+version: 0.5.0.0 synopsis: A native HTTP2 client library. description: Please read the README.md at the homepage. homepage: https://github.com/lucasdicioccio/http2-client@@ -19,6 +19,8 @@ , Network.HTTP2.Client.FrameConnection , Network.HTTP2.Client.Helpers , Network.HTTP2.Client.RawConnection+ other-modules: Network.HTTP2.Client.Channels+ , Network.HTTP2.Client.Dispatch build-depends: base >= 4.7 && < 5 , async , bytestring
src/Network/HTTP2/Client.hs view
@@ -9,10 +9,11 @@ module Network.HTTP2.Client ( -- * Basics runHttp2Client+ , newHttp2Client , withHttp2Stream , headers , sendData- -- * Starting streams+ -- * Starting clients , Http2Client(..) , PushPromiseHandler -- * Starting streams@@ -41,15 +42,14 @@ , module Network.TLS ) where -import Control.Concurrent.Async (Async, race, withAsync, link)+import Control.Concurrent.Async (Async, async, race, withAsync, link) import Control.Exception (bracket, throwIO, SomeException, catch) import Control.Concurrent.MVar (newMVar, takeMVar, putMVar) import Control.Concurrent (threadDelay)-import Control.Concurrent.Chan (Chan, newChan, dupChan, readChan, writeChan) import Control.Monad (forever, when, forM_) import Data.ByteString (ByteString) import qualified Data.ByteString as ByteString-import Data.IORef (IORef, newIORef, atomicModifyIORef', readIORef)+import Data.IORef (newIORef, atomicModifyIORef', readIORef) import Data.Maybe (fromMaybe) import GHC.Exception (Exception) import Network.HPACK as HPACK@@ -57,6 +57,8 @@ import Network.Socket (HostName, PortNumber) import Network.TLS (ClientParams) +import Network.HTTP2.Client.Channels+import Network.HTTP2.Client.Dispatch import Network.HTTP2.Client.FrameConnection -- | Offers credit-based flow-control.@@ -169,7 +171,7 @@ , _outgoingFlowControl :: OutgoingFlowControl -- ^ Simple getter for the 'OutgoingFlowControl' for the whole client -- connection.- , _paylodSplitter :: IO PayloadSplitter+ , _payloadSplitter :: IO PayloadSplitter -- ^ Returns a function to split a payload. , _asyncs :: !Http2ClientAsyncs -- ^ Asynchronous operations threads.@@ -177,6 +179,17 @@ -- ^ Closes the network connection. } +data InitHttp2Client = InitHttp2Client {+ _initPing :: ByteString -> IO (IO (FrameHeader, FramePayload))+ , _initSettings :: SettingsList -> IO (IO (FrameHeader, FramePayload))+ , _initGoaway :: ErrorCodeId -> ByteString -> IO ()+ , _initStartStream :: forall a. StreamStarter a+ , _initIncomingFlowControl :: IncomingFlowControl+ , _initOutgoingFlowControl :: OutgoingFlowControl+ , _initPaylodSplitter :: IO PayloadSplitter+ , _initClose :: IO ()+ }+ -- | Set of Async threads running an Http2Client. -- -- This asyncs are linked to the thread where the Http2Client is created.@@ -185,40 +198,19 @@ data Http2ClientAsyncs = Http2ClientAsyncs { _waitSettingsAsync :: Async (FrameHeader, FramePayload) -- ^ Async waiting for the initial settings ACK.- , _creditFlowsAsync :: Async ()- -- ^ Async responsible for crediting the connection incoming flow control.- -- See 'creditDataFramesLoop'.- , _HPACKAsync :: Async ()- -- ^ Async responsible for serializing every HEADERS/PUSH_PROMISE/CONTINUATION.- -- See 'incomingHPACKFramesLoop'.- , _controlFramesAsync :: Async ()- -- ^ Async responsible for handling control frames (e.g., PING).- -- See 'incomingControlFramesLoop'. , _incomingFramesAsync :: Async () -- ^ Async responsible for ingesting all frames, increasing the -- maximum-received streamID and starting the frame dispatch. See- -- 'incomingFramesLoop'.+ -- 'dispatchFrames'. } --- | Links all client's asyncs to current thread.+-- | Links all client's asyncs to current thread using:+-- @ link someUnderlyingAsync @ . linkAsyncs :: Http2Client -> IO () linkAsyncs client =- let Http2ClientAsyncs{..} = _asyncs client in do- link _waitSettingsAsync- link _creditFlowsAsync- link _HPACKAsync- link _controlFramesAsync- link _incomingFramesAsync---- | Couples client and server settings together.-data ConnectionSettings = ConnectionSettings {- _clientSettings :: !Settings- , _serverSettings :: !Settings- }--defaultConnectionSettings :: ConnectionSettings-defaultConnectionSettings =- ConnectionSettings defaultSettings defaultSettings+ let Http2ClientAsyncs{..} = _asyncs client in do+ link _waitSettingsAsync+ link _incomingFramesAsync -- | Synonym of '_goaway'. --@@ -257,7 +249,7 @@ -- 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.- , _waitPushPromise :: PushPromiseHandler -> IO ()+ , _waitPushPromise :: Maybe (PushPromiseHandler -> IO ()) } -- | Handler upon receiving a PUSH_PROMISE from the server.@@ -272,12 +264,6 @@ type PushPromiseHandler = StreamId -> Http2Stream -> HeaderList -> IncomingFlowControl -> OutgoingFlowControl -> IO () --- | Helper to carry around the HPACK encoder for outgoing header blocks..-data HpackEncoderContext = HpackEncoderContext {- _encodeHeaders :: HeaderList -> IO HeaderBlockFragment- , _applySettings :: Int -> IO ()- }- -- | Starts a new stream (i.e., one HTTP request + server-pushes). -- -- You will typically call the returned 'StreamStarter' immediately to define@@ -318,9 +304,10 @@ -- | Starts a new Http2Client around a frame connection. ----- This is mostly useful if you want to muck around the Http2FrameConnection--- (e.g., for tracing frames) as well as for unit testing purposes. If you want--- to create a new connection you should likely use 'newHttp2Client' instead.+-- This function is slightly safer than 'startHttp2Client' because it uses+-- 'Control.Concurrent.Async.withAsync' instead of+-- 'Control.Concurrent.Async.async' however, this with-pattern takes the+-- control of the thread and can be annoying at times. runHttp2Client :: Http2FrameConnection -- ^ A frame connection.@@ -339,133 +326,165 @@ -- ^ Actions to run on the client. -> IO a runHttp2Client conn encoderBufSize decoderBufSize initSettings goAwayHandler fallbackHandler mainHandler = do- let _close = closeConnection conn- -- prepare hpack contexts- hpackEncoder <- do- let strategy = (HPACK.defaultEncodeStrategy { HPACK.useHuffman = True })- dt <- HPACK.newDynamicTableForEncoding HPACK.defaultDynamicTableSize- let _encodeHeaders = HPACK.encodeHeader strategy encoderBufSize dt- let _applySettings n = HPACK.setLimitForEncoding n dt- return HpackEncoderContext{..}+ (incomingLoop, initClient) <- initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler+ withAsync incomingLoop $ \aIncoming -> do+ settsIO <- _initSettings initClient initSettings+ withAsync settsIO $ \aSettings -> do+ mainHandler $ Http2Client {+ _settings = _initSettings initClient+ , _ping = _initPing initClient+ , _goaway = _initGoaway initClient+ , _close = _initClose initClient+ , _startStream = _initStartStream initClient+ , _incomingFlowControl = _initIncomingFlowControl initClient+ , _outgoingFlowControl = _initOutgoingFlowControl initClient+ , _payloadSplitter = _initPaylodSplitter initClient+ , _asyncs = Http2ClientAsyncs aSettings aIncoming+ } - -- prepare client streams- clientStreamIdMutex <- newMVar 0- let withClientStreamId h = bracket (takeMVar clientStreamIdMutex)- (putMVar clientStreamIdMutex . succ)- (\k -> h (2 * k + 1)) -- Note: client StreamIds MUST be odd+-- | Starts a new Http2Client around a frame connection.+--+-- You may want to 'linkAsyncs' for a proper and automated cleanup of the+-- underlying threads.+newHttp2Client+ :: Http2FrameConnection+ -- ^ A frame connection.+ -> Int+ -- ^ The buffersize for the Network.HPACK encoder.+ -> Int+ -- ^ The buffersize for the Network.HPACK decoder.+ -> SettingsList+ -- ^ Initial SETTINGS that are sent as first frame.+ -> GoAwayHandler+ -- ^ Actions to run when the remote sends a GoAwayFrame+ -> FallBackFrameHandler+ -- ^ Actions to run when a control frame is not yet handled in http2-client+ -- lib (e.g., PRIORITY frames).+ -> IO Http2Client+newHttp2Client conn encoderBufSize decoderBufSize initSettings goAwayHandler fallbackHandler = do+ (incomingLoop, initClient) <- initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler+ aIncoming <- async incomingLoop+ settsIO <- _initSettings initClient initSettings+ aSettings <- async settsIO+ return $ Http2Client {+ _settings = _initSettings initClient+ , _ping = _initPing initClient+ , _goaway = _initGoaway initClient+ , _close = _initClose initClient+ , _startStream = _initStartStream initClient+ , _incomingFlowControl = _initIncomingFlowControl initClient+ , _outgoingFlowControl = _initOutgoingFlowControl initClient+ , _payloadSplitter = _initPaylodSplitter initClient+ , _asyncs = Http2ClientAsyncs aSettings aIncoming+ } +initHttp2Client+ :: Http2FrameConnection+ -> Int+ -> Int+ -> GoAwayHandler+ -> FallBackFrameHandler+ -> IO (IO (), InitHttp2Client)+initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler = do let controlStream = makeFrameClientStream conn 0 let ackPing = sendPingFrame controlStream HTTP2.setAck let ackSettings = sendSettingsFrame controlStream HTTP2.setAck [] - -- Initial thread receiving server frames.- maxReceivedStreamId <- newIORef 0- serverFrames <- newChan- let incomingLoop = incomingFramesLoop conn serverFrames maxReceivedStreamId- withAsync incomingLoop $ \aIncoming -> do-- -- Thread handling control frames.- settings <- newIORef defaultConnectionSettings- controlFrames <- dupChan serverFrames- let controlLoop = incomingControlFramesLoop controlFrames settings hpackEncoder ackPing ackSettings goAwayHandler fallbackHandler- withAsync controlLoop $ \aControl -> do- -- Thread handling push-promises and headers frames serializing the buffers.- serverStreamFrames <- dupChan serverFrames- serverHeaders <- newChan- hpackDecoder <- do- dt <- newDynamicTableForDecoding HPACK.defaultDynamicTableSize decoderBufSize- return dt-- creditFrames <- dupChan serverFrames+ {- Setup for initial thread receiving server frames. -}+ dispatch <- newDispatchIO+ dispatchControl <- newDispatchControlIO encoderBufSize+ ackPing+ ackSettings+ goAwayHandler+ fallbackHandler - _outgoingFlowControl <- newOutgoingFlowControl settings 0 creditFrames- _incomingFlowControl <- newIncomingFlowControl settings controlStream+ creditFrames <- newDispatchReadChanIO dispatch+ _initOutgoingFlowControl <- newOutgoingFlowControl dispatchControl 0 creditFrames+ _initIncomingFlowControl <- newIncomingFlowControl dispatchControl controlStream+ dispatchHPACK <- newDispatchHPACKIO decoderBufSize+ let incomingLoop = dispatchLoop conn dispatch dispatchControl _initIncomingFlowControl dispatchHPACK - serverPushPromises <- newChan+ {- Setup for client-initiated streams. -}+ conccurentStreams <- newIORef 0+ -- prepare client streams+ clientStreamIdMutex <- newMVar 0+ let withClientStreamId h = bracket (takeMVar clientStreamIdMutex)+ (putMVar clientStreamIdMutex . succ)+ (\k -> h (2 * k + 1)) -- Note: client StreamIds MUST be odd - let hpackLoop = incomingHPACKFramesLoop serverStreamFrames serverHeaders serverPushPromises hpackDecoder- withAsync hpackLoop $ \aHPACK -> do- dataFrames <- dupChan serverFrames- let creditLoop = creditDataFramesLoop _incomingFlowControl dataFrames- withAsync creditLoop $ \aCredit -> do- conccurentStreams <- newIORef 0- let _startStream getWork = do- maxConcurrency <- fromMaybe 100 . maxConcurrentStreams . _serverSettings- <$> readIORef settings- roomNeeded <- atomicModifyIORef' conccurentStreams- (\n -> if n < maxConcurrency then (n + 1, 0) else (n, 1 + n - maxConcurrency))- if roomNeeded > 0- then- return $ Left $ TooMuchConcurrency roomNeeded- else do- cont <- withClientStreamId $ \sid -> do- initializeStream conn- settings- serverFrames- serverHeaders- serverPushPromises- hpackEncoder- sid- getWork- v <- cont- atomicModifyIORef' conccurentStreams (\n -> (n - 1, ()))- return $ Right v+ let _initStartStream getWork = do+ maxConcurrency <- fromMaybe 100 . maxConcurrentStreams . _serverSettings <$> readSettings dispatchControl+ roomNeeded <- atomicModifyIORef' conccurentStreams+ (\n -> if n < maxConcurrency then (n + 1, 0) else (n, 1 + n - maxConcurrency))+ if roomNeeded > 0+ then+ return $ Left $ TooMuchConcurrency roomNeeded+ else Right <$> do+ cont <- withClientStreamId $ \sid -> do+ streamHeaders <- newDispatchHPACKReadHeadersChanIO dispatchHPACK+ streamFrames <- newDispatchReadChanIO dispatch+ streamPP <- newDispatchHPACKReadPushPromisesChanIO dispatchHPACK+ initializeStream conn+ dispatchControl+ streamFrames+ streamHeaders+ (Just streamPP)+ sid+ getWork+ v <- cont+ atomicModifyIORef' conccurentStreams (\n -> (n - 1, ()))+ pure v - let _ping dat = do- -- Need to dupChan before sending the query to avoid missing a fast- -- answer if the network is fast.- pingFrames <- dupChan serverFrames- sendPingFrame controlStream id dat- return $ waitFrame (isPingReply dat) pingFrames- let _settings settslist = do- -- Much like _ping, we need to dupChan before sending the query.- pingFrames <- dupChan serverFrames- sendSettingsFrame controlStream id settslist- return $ do- ret <- waitFrame isSettingsReply pingFrames- atomicModifyIORef' settings- (\(ConnectionSettings cli srv) ->- (ConnectionSettings (HTTP2.updateSettings cli settslist) srv, ()))- return ret- let _goaway err errStr = do- sId <- readIORef maxReceivedStreamId- sendGTFOFrame controlStream sId err errStr+ let _initPing dat = do+ -- Need to dupChan before sending the query to avoid missing a fast+ -- answer if the network is fast.+ pingFrames <- newDispatchReadChanIO dispatch+ sendPingFrame controlStream id dat+ return $ waitFrame (isPingReply dat) pingFrames+ let _initSettings settslist = do+ -- Much like _ping, we need to dupChan before sending the query.+ settingsFrames <- newDispatchReadChanIO dispatch+ sendSettingsFrame controlStream id settslist+ return $ do+ ret <- waitFrame isSettingsReply settingsFrames+ modifySettings dispatchControl+ (\(ConnectionSettings cli srv) ->+ (ConnectionSettings (HTTP2.updateSettings cli settslist) srv, ()))+ return ret+ let _initGoaway err errStr = do+ sId <- readMaxReceivedStreamIdIO dispatch+ sendGTFOFrame controlStream sId err errStr - let _paylodSplitter = settingsPayloadSplitter <$> readIORef settings+ let _initPaylodSplitter = settingsPayloadSplitter <$> readSettings dispatchControl - settsIO <- _settings initSettings- withAsync settsIO $ \aSettings -> do- let _asyncs = Http2ClientAsyncs aSettings aCredit aHPACK aControl aIncoming- mainHandler $ Http2Client{..}+ let _initClose = closeConnection conn+ return (incomingLoop, InitHttp2Client{..}) initializeStream :: Exception e => Http2FrameConnection- -> IORef ConnectionSettings+ -> DispatchControl -> FramesChan e -> HeadersChan- -> PushPromisesChan e- -> HpackEncoderContext+ -> Maybe (PushPromisesChan e)+ -- ^ Just some PushPromiseChan for new client streams. Nothing for server+ -- streams. -> StreamId -> (Http2Stream -> StreamDefinition a) -> IO (IO a)-initializeStream conn settings serverFrames serverHeaders serverPushPromises hpackEncoder sid getWork = do+initializeStream conn control frames headersFrames mPushPromises sid getWork = do let frameStream = makeFrameClientStream conn sid- -- Register interest in frames.- frames <- dupChan serverFrames- credits <- dupChan serverFrames- headersFrames <- dupChan serverHeaders- pushPromises <- dupChan serverPushPromises+ credits <- dupChan frames -- Builds a flow-control context.- incomingStreamFlowControl <- newIncomingFlowControl settings frameStream- outgoingStreamFlowControl <- newOutgoingFlowControl settings sid credits+ incomingStreamFlowControl <- newIncomingFlowControl control frameStream+ outgoingStreamFlowControl <- newOutgoingFlowControl control sid credits -- Prepare handlers. let _headers headersList flags = do- splitter <- settingsPayloadSplitter <$> readIORef settings- sendHeaders frameStream hpackEncoder headersList splitter flags+ splitter <- settingsPayloadSplitter <$> readSettings control+ sendHeaders frameStream (_dispatchControlHpackEncoder control) headersList splitter flags let _waitHeaders = waitHeadersWithStreamId sid headersFrames let _waitData = do (fh, fp) <- waitFrameWithTypeIdForStreamId sid [FrameRSTStream, FrameData] frames@@ -480,18 +499,18 @@ let _sendDataChunk = sendDataFrame frameStream let _rst = sendResetFrame frameStream let _prio = sendPriorityFrame frameStream- let _waitPushPromise ppHandler = do+ let makeWaitPushPromise pushPromises ppHandler = do (_,ppFrames,ppHeaders,ppSid,ppReadHeaders) <- waitPushPromiseWithParentStreamId sid pushPromises let mkStreamActions stream = StreamDefinition (return CST) (ppHandler sid stream ppReadHeaders) ppCont <- initializeStream conn- settings+ control ppFrames ppHeaders- serverPushPromises- hpackEncoder+ Nothing ppSid mkStreamActions ppCont+ let _waitPushPromise = fmap makeWaitPushPromise mPushPromises let streamActions = getWork $ Http2Stream{..} @@ -501,120 +520,87 @@ -- Returns 2nd action. return $ _handleStream streamActions incomingStreamFlowControl outgoingStreamFlowControl -incomingFramesLoop+dispatchLoop :: Http2FrameConnection- -> Chan (FrameHeader, Either HTTP2Error FramePayload)- -> IORef StreamId+ -> Dispatch+ -> DispatchControl+ -> IncomingFlowControl+ -> DispatchHPACK -> IO ()-incomingFramesLoop conn frames maxReceivedStreamId = delayException $ forever $ do- frame@(fh, _) <- next conn- -- Remember highest streamId.- atomicModifyIORef' maxReceivedStreamId (\n -> (max n (streamId fh), ()))- writeChan frames frame---- | Runs an action, rethrowing exception 50ms later.------ In a context where asynchronous are likely to occur this function gives a--- chance to other threads to do some work before Async linking reaps them all.------ In particular, servers are likely to close their TCP connection soon after--- sending a GoAwayFrame and we want to give a better chance to clients to--- observe the GoAwayFrame in their handlers to distinguish GoAwayFrames--- followed by TCP disconnection and plain TCP resets. As a result, this--- function is mostly used to delay 'incomingFramesLoop'. A more involved--- and future design will be to inline the various loop-processes for--- incomingFramesLoop and GoAwayHandlers in a same thread (e.g., using--- pipe/conduit to retain composability).-delayException :: IO a -> IO a-delayException act = act `catch` slowdown- where- slowdown :: SomeException -> IO a- slowdown e = threadDelay 50000 >> throwIO e+dispatchLoop conn d dc inFlowControl dh = do+ let getNextFrame = next conn+ delayException . forever $ do+ frame <- getNextFrame+ dispatchFramesStep frame d+ whenFrame (hasStreamId 0) frame $ \got ->+ dispatchControlFramesStep got dc+ whenFrame (hasTypeId [FrameData]) frame $ \got ->+ creditDataFramesStep inFlowControl got+ whenFrame (hasTypeId [FrameRSTStream, FramePushPromise, FrameHeaders]) frame $ \got -> do+ let hpackLoop (FinishedWithHeaders act) =+ act+ hpackLoop (FinishedWithPushPromise act) = do+ newDispatchReadChanIO d >>= act+ hpackLoop (WaitContinuation act) =+ getNextFrame >>= act >>= hpackLoop+ hpackLoop (dispatchHPACKFramesStep got dh) -incomingControlFramesLoop- :: Exception e- => Chan (FrameHeader, Either e FramePayload)- -> IORef ConnectionSettings- -> HpackEncoderContext- -> (ByteString -> IO ())+dispatchFramesStep+ :: (FrameHeader, Either HTTP2Error FramePayload)+ -> Dispatch -> IO ()- -> GoAwayHandler- -> FallBackFrameHandler+dispatchFramesStep frame@(fh,_) (Dispatch{..}) = do+ -- Remember highest streamId.+ atomicModifyIORef' _dispatchMaxStreamId (\n -> (max n (streamId fh), ()))+ writeChan _dispatchWriteChan frame++dispatchControlFramesStep+ :: (FrameHeader, FramePayload)+ -> DispatchControl -> IO ()-incomingControlFramesLoop frames settings hpackEncoder ackPing ackSettings goAwayHandler fallbackHandler = forever $ do- controlFrame@(fh, payload) <- waitFrameWithStreamId 0 frames+dispatchControlFramesStep controlFrame@(fh, payload) (DispatchControl{..}) = do case payload of (SettingsFrame settsList) | not . testAck . flags $ fh -> do- atomicModifyIORef' settings+ atomicModifyIORef' _dispatchControlConnectionSettings (\(ConnectionSettings cli srv) -> (ConnectionSettings cli (HTTP2.updateSettings srv settsList), ())) maybe (return ())- (_applySettings hpackEncoder)+ (_applySettings _dispatchControlHpackEncoder) (lookup SettingsHeaderTableSize settsList)- ackSettings+ _dispatchControlAckSettings | otherwise -> do ignore "TODO: settings ack should be taken into account only after reception, we should return a waitSettingsAck in the _settings function" (PingFrame pingMsg) | not . testAck . flags $ fh ->- ackPing pingMsg+ _dispatchControlAckPing pingMsg | otherwise -> do ignore "PingFrame replies waited for in the requestor thread" (WindowUpdateFrame _ ) -> ignore "connection-wide WindowUpdateFrame waited for in OutgoingFlowControl threads" (GoAwayFrame lastSid errCode reason) ->- goAwayHandler $ RemoteSentGoAwayFrame lastSid errCode reason+ _dispatchControlOnGoAway $ RemoteSentGoAwayFrame lastSid errCode reason _ ->- fallbackHandler controlFrame+ _dispatchControlOnFallback controlFrame where ignore :: String -> IO () ignore _ = return () --- | A fallback handler for frames.-type FallBackFrameHandler = (FrameHeader, FramePayload) -> IO ()---- | Default FallBackFrameHandler that ignores frames.-ignoreFallbackHandler :: FallBackFrameHandler-ignoreFallbackHandler = const $ pure ()---- | A Handler for exceptional circumstances.-type GoAwayHandler = RemoteSentGoAwayFrame -> IO ()---- | Default GoAwayHandler throws a 'RemoteSentGoAwayFrame' in the current--- thread.------ A probably sharper handler if you want to abruptly stop any operation is to--- get the 'ThreadId' of the main client thread and using--- 'Control.Exception.Base.throwTo'.------ There's an inherent race condition when receiving a GoAway frame because the--- server will likely close the connection which will lead to TCP errors as--- well.-defaultGoAwayHandler :: GoAwayHandler-defaultGoAwayHandler = throwIO---- | An exception thrown when the server sends a GoAwayFrame.-data RemoteSentGoAwayFrame = RemoteSentGoAwayFrame !StreamId !ErrorCodeId !ByteString- deriving Show-instance Exception RemoteSentGoAwayFrame---- | We currently need a specific loop for crediting streams because a client--- user may programmatically reset and stop listening for a stream and stop--- calling waitData (which credits streams).+-- | We currently need a specific step in the main loop for crediting streams+-- because a client user may programmatically reset and stop listening for a+-- stream and stop calling waitData (which credits streams). -- -- TODO: modify the '_rst' function to wait and credit all the remaining data -- that could have been sent in flight-creditDataFramesLoop- :: Exception e- => IncomingFlowControl- -> Chan (FrameHeader, Either e FramePayload)+creditDataFramesStep+ :: IncomingFlowControl+ -> (FrameHeader, FramePayload) -> IO ()-creditDataFramesLoop flowControl frames = forever $ do- (fh,_) <- waitFrameWithTypeId [FrameData] frames- -- TODO: error if detect over-run, current implementation credits- -- everything back so that should never happen+creditDataFramesStep flowControl (fh,_) = 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) @@ -622,78 +608,74 @@ ForwardHeader !StreamId | OpenPushPromise !StreamId !StreamId -type FramesChan e = Chan (FrameHeader, Either e FramePayload)-type HeadersChanContent = (FrameHeader, StreamId, Either ErrorCode HeaderList)-type HeadersChan = Chan HeadersChanContent-type PushPromisesChanContent e = (StreamId, FramesChan e, HeadersChan, StreamId, HeaderList)-type PushPromisesChan e = Chan (PushPromisesChanContent e)+data HPACKStepResult =+ WaitContinuation !((FrameHeader, Either HTTP2Error FramePayload) -> IO HPACKStepResult)+ | FinishedWithHeaders !(IO ())+ | FinishedWithPushPromise !(DispatchChan -> IO ()) -incomingHPACKFramesLoop- :: Exception e- => FramesChan e- -> HeadersChan- -> PushPromisesChan e- -> DynamicTable- -> IO ()-incomingHPACKFramesLoop frames hdrs pushPromises hpackDecoder = forever $ do- (fh, fp) <- waitFrameWithTypeId [ FrameRSTStream- , FramePushPromise- , FrameHeaders- ]- frames- let sid = HTTP2.streamId fh- (decision, pattern) <- case fp of+dispatchHPACKFramesStep+ :: (FrameHeader, FramePayload)+ -> DispatchHPACK+ -> HPACKStepResult+dispatchHPACKFramesStep (fh,fp) (DispatchHPACK{..}) =+ let (decision, pattern) = case fp of PushPromiseFrame ppSid hbf -> do- return (OpenPushPromise sid ppSid, Right hbf)+ (OpenPushPromise sid ppSid, Right hbf) HeadersFrame _ hbf -> -- TODO: handle priority- return (ForwardHeader sid, Right hbf)+ (ForwardHeader sid, Right hbf) RSTStreamFrame err ->- return (ForwardHeader sid, Left err)- _ -> error "wrong TypeId"+ (ForwardHeader sid, Left err)+ _ ->+ error "wrong TypeId"+ in go fh decision pattern+ where+ sid :: StreamId+ sid = HTTP2.streamId fh - let go curFh (Right buffer) =- if not $ HTTP2.testEndHeader (HTTP2.flags curFh)- then do- (lastFh, lastFp) <- waitFrameWithTypeId [ FrameRSTStream- , FrameContinuation- ]- frames+ go :: FrameHeader -> HPACKLoopDecision -> Either ErrorCodeId ByteString -> HPACKStepResult+ go curFh decision (Right buffer) =+ if not $ HTTP2.testEndHeader (HTTP2.flags curFh)+ then WaitContinuation $ \frame -> do+ let interrupted fh2 fp2 =+ not $ hasTypeId [ FrameRSTStream , FrameContinuation ] fh2 fp2+ whenFrameElse interrupted frame (\_ ->+ error "invalid frame type while waiting for CONTINUATION")+ (\(lastFh, lastFp) -> case lastFp of ContinuationFrame chbf ->- go lastFh (Right (ByteString.append buffer chbf))+ return $ go lastFh decision (Right (ByteString.append buffer chbf)) RSTStreamFrame err ->- go lastFh (Left err)- _ -> error "waitFrameWithTypeIdForStreamId returned an unknown frame"- else do- newHdrs <- decodeHeader hpackDecoder buffer- case decision of- ForwardHeader sId ->- writeChan hdrs (curFh, sId, Right newHdrs)- OpenPushPromise parentSid newSid -> do- -- Important: We duplicate the channel here or we risk- -- losing DATA or HEADERS+CONTINUATION frames sent over- -- the main stream because even though- -- 'initializeStream' dupes frame channels, this- -- function will has no guarantee that the parent- -- stream and the push-promise handler that will- -- initiate the new stream are synchronous.- ppChan <- dupChan frames- ppHeaders <- dupChan hdrs- writeChan pushPromises (parentSid, ppChan, ppHeaders, newSid, newHdrs)+ return $ go lastFh decision (Left err)+ _ ->+ error "continued frame has invalid type")+ else case decision of+ ForwardHeader sId -> FinishedWithHeaders $ do+ newHdrs <- decodeHeader _dispatchHPACKDynamicTable buffer+ writeChan _dispatchHPACKWriteHeadersChan (curFh, sId, Right newHdrs)+ OpenPushPromise parentSid newSid -> FinishedWithPushPromise $ \ppChan -> do+ newHdrs <- decodeHeader _dispatchHPACKDynamicTable buffer+ -- Important: We duplicate the channel here or we risk+ -- losing DATA or HEADERS+CONTINUATION frames sent over+ -- the main stream because even though+ -- 'initializeStream' dupes frame channels, this+ -- function will has no guarantee that the parent+ -- stream and the push-promise handler that will+ -- initiate the new stream are synchronous.+ ppHeaders <- dupChan _dispatchHPACKWriteHeadersChan+ writeChan _dispatchHPACKWritePushPromisesChan (parentSid, ppChan, ppHeaders, newSid, newHdrs) - go curFh (Left err) =- writeChan hdrs (curFh, sid, (Left $ HTTP2.fromErrorCodeId err))+ go curFh _ (Left err) = FinishedWithHeaders $+ writeChan _dispatchHPACKWriteHeadersChan (curFh, sid, (Left $ HTTP2.fromErrorCodeId err)) - go fh pattern newIncomingFlowControl- :: IORef ConnectionSettings+ :: DispatchControl -> Http2FrameClientStream -> IO IncomingFlowControl-newIncomingFlowControl settings stream = do+newIncomingFlowControl control stream = do let getBase = if _getStreamId stream == 0 then return HTTP2.defaultInitialWindowSize- else initialWindowSize . _clientSettings <$> readIORef settings+ else initialWindowSize . _clientSettings <$> readSettings control creditAdded <- newIORef 0 creditConsumed <- newIORef 0 let _addCredit n = atomicModifyIORef' creditAdded (\c -> (c + n, ()))@@ -703,7 +685,7 @@ extra <- readIORef creditAdded return $ base + extra - conso let _updateWindow = do- base <- initialWindowSize . _clientSettings <$> readIORef settings+ base <- initialWindowSize . _clientSettings <$> readSettings control added <- readIORef creditAdded consumed <- readIORef creditConsumed @@ -720,15 +702,15 @@ newOutgoingFlowControl :: Exception e- => IORef ConnectionSettings+ => DispatchControl -> StreamId -> Chan (FrameHeader, Either e FramePayload) -> IO OutgoingFlowControl-newOutgoingFlowControl settings sid frames = do+newOutgoingFlowControl control sid frames = do credit <- newIORef 0 let getBase = if sid == 0 then return HTTP2.defaultInitialWindowSize- else initialWindowSize . _serverSettings <$> readIORef settings+ else initialWindowSize . _serverSettings <$> readSettings control let receive n = atomicModifyIORef' credit (\c -> (c + n, ())) let withdraw 0 = return 0 withdraw n = do@@ -753,7 +735,7 @@ -- an hasted client asks for X > initialWindowSize before the server has -- sent its initial SETTINGS frame. waitSettingsChange prev = do- new <- initialWindowSize . _serverSettings <$> readIORef settings+ new <- initialWindowSize . _serverSettings <$> readSettings control if new == prev then threadDelay 1000000 >> waitSettingsChange prev else return () waitSomeCredit = do (_, fp) <- waitFrameWithTypeIdForStreamId sid [FrameWindowUpdate] frames@@ -818,7 +800,7 @@ -- Please refer to '_sendDataChunk' and '_withdrawCredit' as well. sendData :: Http2Client -> Http2Stream -> FlagSetter -> ByteString -> IO () sendData conn stream flagmod dat = do- splitter <- _paylodSplitter conn+ splitter <- _payloadSplitter conn let chunks = splitter dat let pairs = reverse $ zip (flagmod : repeat id) (reverse chunks) when (null chunks) $ _sendDataChunk stream flagmod ""@@ -879,81 +861,22 @@ sendOne s id payload return () -waitFrameWithStreamId- :: Exception e- => StreamId- -> FramesChan e- -> IO (FrameHeader, FramePayload)-waitFrameWithStreamId sid = waitFrame (\h _ -> streamId h == sid)--waitFrameWithTypeId- :: (Exception e)- => [FrameTypeId]- -> FramesChan e- -> IO (FrameHeader, FramePayload)-waitFrameWithTypeId tids = waitFrame (\_ p -> HTTP2.framePayloadToFrameTypeId p `elem` tids)--waitFrameWithTypeIdForStreamId- :: (Exception e)- => StreamId- -> [FrameTypeId]- -> FramesChan e- -> IO (FrameHeader, FramePayload)-waitFrameWithTypeIdForStreamId sid tids =- waitFrame (\h p -> streamId h == sid && HTTP2.framePayloadToFrameTypeId p `elem` tids)--waitFrame- :: Exception e- => (FrameHeader -> FramePayload -> Bool)- -> FramesChan e- -> IO (FrameHeader, FramePayload)-waitFrame test chan =- loop- where- loop = do- (fHead, fPayload) <- readChan chan- dat <- either throwIO pure fPayload- if test fHead dat- then return (fHead, dat)- else loop--isPingReply :: ByteString -> FrameHeader -> FramePayload -> Bool-isPingReply datSent _ (PingFrame datRcv) = datSent == datRcv-isPingReply _ _ _ = False--isSettingsReply :: FrameHeader -> FramePayload -> Bool-isSettingsReply fh (SettingsFrame _) = HTTP2.testAck (flags fh)-isSettingsReply _ _ = False--waitHeadersWithStreamId- :: StreamId- -> HeadersChan- -> IO HeadersChanContent-waitHeadersWithStreamId sid =- waitHeaders (\_ s _ -> s == sid)--waitHeaders- :: (FrameHeader -> StreamId -> Either ErrorCode HeaderList -> Bool)- -> HeadersChan- -> IO HeadersChanContent-waitHeaders test chan =- loop+-- | Runs an action, rethrowing exception 50ms later.+--+-- In a context where asynchronous are likely to occur this function gives a+-- chance to other threads to do some work before Async linking reaps them all.+--+-- In particular, servers are likely to close their TCP connection soon after+-- sending a GoAwayFrame and we want to give a better chance to clients to+-- observe the GoAwayFrame in their handlers to distinguish GoAwayFrames+-- followed by TCP disconnection and plain TCP resets. As a result, this+-- function is mostly used to delay 'dispatchFrames'. A more involved+-- 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 act = act `catch` slowdown where- loop = do- tuple@(fH, sId, hdrs) <- readChan chan- if test fH sId hdrs- then return tuple- else loop+ slowdown :: SomeException -> IO a+ slowdown e = threadDelay 50000 >> throwIO e -waitPushPromiseWithParentStreamId- :: StreamId- -> PushPromisesChan e- -> IO (PushPromisesChanContent e)-waitPushPromiseWithParentStreamId sid chan =- loop- where- loop = do- tuple@(parentSid,_,_,_,_) <- readChan chan- if parentSid == sid- then return tuple- else loop
+ src/Network/HTTP2/Client/Channels.hs view
@@ -0,0 +1,146 @@++module Network.HTTP2.Client.Channels (+ FramesChan+ , HeadersChan+ , PushPromisesChan+ , waitHeaders+ , waitHeadersWithStreamId+ , waitFrame+ , waitFrameWithStreamId+ , waitPushPromiseWithParentStreamId+ , waitFrameWithTypeId+ , waitFrameWithTypeIdForStreamId+ , isPingReply+ , isSettingsReply+ , hasStreamId+ , hasTypeId+ , whenFrame+ , whenFrameElse+ , module Control.Concurrent.Chan+ ) where++import Control.Concurrent.Chan (Chan, readChan, newChan, dupChan, writeChan)+import Control.Exception (Exception, throwIO)+import Data.ByteString (ByteString)+import Network.HPACK as HPACK+import Network.HTTP2 as HTTP2++type FramesChan e = Chan (FrameHeader, Either e FramePayload)++type HeadersChanContent = (FrameHeader, StreamId, Either ErrorCode HeaderList)++type HeadersChan = Chan HeadersChanContent++type PushPromisesChanContent e = (StreamId, FramesChan e, HeadersChan, StreamId, HeaderList)++type PushPromisesChan e = Chan (PushPromisesChanContent e)++waitFrameWithStreamId+ :: Exception e+ => StreamId+ -> FramesChan e+ -> IO (FrameHeader, FramePayload)+waitFrameWithStreamId sid = waitFrame (\h _ -> streamId h == sid)++waitFrameWithTypeId+ :: (Exception e)+ => [FrameTypeId]+ -> FramesChan e+ -> IO (FrameHeader, FramePayload)+waitFrameWithTypeId tids = waitFrame (\_ p -> HTTP2.framePayloadToFrameTypeId p `elem` tids)++waitFrameWithTypeIdForStreamId+ :: (Exception e)+ => StreamId+ -> [FrameTypeId]+ -> FramesChan e+ -> IO (FrameHeader, FramePayload)+waitFrameWithTypeIdForStreamId sid tids =+ waitFrame (\h p -> streamId h == sid && HTTP2.framePayloadToFrameTypeId p `elem` tids)++waitFrame+ :: Exception e+ => (FrameHeader -> FramePayload -> Bool)+ -> FramesChan e+ -> IO (FrameHeader, FramePayload)+waitFrame test chan =+ loop+ where+ loop = do+ (fHead, fPayload) <- readChan chan+ dat <- either throwIO pure fPayload+ if test fHead dat+ then return (fHead, dat)+ else loop++whenFrame+ :: Exception e+ => (FrameHeader -> FramePayload -> Bool)+ -> (FrameHeader, Either e FramePayload)+ -> ((FrameHeader, FramePayload) -> IO ())+ -> IO ()+whenFrame test (fHead, fPayload) handle = do+ dat <- either throwIO pure fPayload+ if test fHead dat+ then handle (fHead, dat)+ else pure ()++whenFrameElse+ :: Exception e+ => (FrameHeader -> FramePayload -> Bool)+ -> (FrameHeader, Either e FramePayload)+ -> ((FrameHeader, FramePayload) -> IO a)+ -> ((FrameHeader, FramePayload) -> IO a)+ -> IO a+whenFrameElse test (fHead, fPayload) handleTrue handleFalse = do+ dat <- either throwIO pure fPayload+ if test fHead dat+ then handleTrue (fHead, dat)+ else handleFalse (fHead, dat)++hasStreamId :: StreamId -> FrameHeader -> FramePayload -> Bool+hasStreamId sid h _ = streamId h == sid++hasTypeId :: [FrameTypeId] -> FrameHeader -> FramePayload -> Bool+hasTypeId tids _ p = HTTP2.framePayloadToFrameTypeId p `elem` tids++isPingReply :: ByteString -> FrameHeader -> FramePayload -> Bool+isPingReply datSent _ (PingFrame datRcv) = datSent == datRcv+isPingReply _ _ _ = False++isSettingsReply :: FrameHeader -> FramePayload -> Bool+isSettingsReply fh (SettingsFrame _) = HTTP2.testAck (flags fh)+isSettingsReply _ _ = False++waitHeadersWithStreamId+ :: StreamId+ -> HeadersChan+ -> IO HeadersChanContent+waitHeadersWithStreamId sid =+ waitHeaders (\_ s _ -> s == sid)++waitHeaders+ :: (FrameHeader -> StreamId -> Either ErrorCode HeaderList -> Bool)+ -> HeadersChan+ -> IO HeadersChanContent+waitHeaders test chan =+ loop+ where+ loop = do+ tuple@(fH, sId, hdrs) <- readChan chan+ if test fH sId hdrs+ then return tuple+ else loop++waitPushPromiseWithParentStreamId+ :: StreamId+ -> PushPromisesChan e+ -> IO (PushPromisesChanContent e)+waitPushPromiseWithParentStreamId sid chan =+ loop+ where+ loop = do+ tuple@(parentSid,_,_,_,_) <- readChan chan+ if parentSid == sid+ then return tuple+ else loop
+ src/Network/HTTP2/Client/Dispatch.hs view
@@ -0,0 +1,130 @@++module Network.HTTP2.Client.Dispatch where++import Control.Exception (throwIO)+import Data.ByteString (ByteString)+import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)+import GHC.Exception (Exception)+import Network.HPACK as HPACK+import Network.HTTP2 as HTTP2++import Network.HTTP2.Client.Channels++type DispatchChan = FramesChan HTTP2Error++-- | A fallback handler for frames.+type FallBackFrameHandler = (FrameHeader, FramePayload) -> IO ()++-- | Default FallBackFrameHandler that ignores frames.+ignoreFallbackHandler :: FallBackFrameHandler+ignoreFallbackHandler = const $ pure ()++-- | An exception thrown when the server sends a GoAwayFrame.+data RemoteSentGoAwayFrame = RemoteSentGoAwayFrame !StreamId !ErrorCodeId !ByteString+ deriving Show+instance Exception RemoteSentGoAwayFrame++-- | A Handler for exceptional circumstances.+type GoAwayHandler = RemoteSentGoAwayFrame -> IO ()++-- | Default GoAwayHandler throws a 'RemoteSentGoAwayFrame' in the current+-- thread.+--+-- A probably sharper handler if you want to abruptly stop any operation is to+-- get the 'ThreadId' of the main client thread and using+-- 'Control.Exception.Base.throwTo'.+--+-- There's an inherent race condition when receiving a GoAway frame because the+-- server will likely close the connection which will lead to TCP errors as+-- well.+defaultGoAwayHandler :: GoAwayHandler+defaultGoAwayHandler = throwIO++data Dispatch = Dispatch {+ _dispatchWriteChan :: !DispatchChan+ , _dispatchMaxStreamId :: !(IORef StreamId)+ }++newDispatchIO :: IO Dispatch+newDispatchIO = Dispatch <$> newChan <*> newIORef 0++newDispatchReadChanIO :: Dispatch -> IO DispatchChan+newDispatchReadChanIO = dupChan . _dispatchWriteChan++readMaxReceivedStreamIdIO :: Dispatch -> IO StreamId+readMaxReceivedStreamIdIO = readIORef . _dispatchMaxStreamId++-- | Couples client and server settings together.+data ConnectionSettings = ConnectionSettings {+ _clientSettings :: !Settings+ , _serverSettings :: !Settings+ }++defaultConnectionSettings :: ConnectionSettings+defaultConnectionSettings =+ ConnectionSettings defaultSettings defaultSettings++data DispatchControl = DispatchControl {+ _dispatchControlConnectionSettings :: !(IORef ConnectionSettings)+ , _dispatchControlHpackEncoder :: !HpackEncoderContext+ , _dispatchControlAckPing :: !(ByteString -> IO ())+ , _dispatchControlAckSettings :: !(IO ())+ , _dispatchControlOnGoAway :: !GoAwayHandler+ , _dispatchControlOnFallback :: !FallBackFrameHandler+ }++newDispatchControlIO+ :: Size+ -> (ByteString -> IO ())+ -> (IO ())+ -> GoAwayHandler+ -> FallBackFrameHandler+ -> IO DispatchControl+newDispatchControlIO encoderBufSize ackPing ackSetts onGoAway onFallback =+ DispatchControl <$> newIORef defaultConnectionSettings+ <*> hpackEncoder+ <*> pure ackPing+ <*> pure ackSetts+ <*> pure onGoAway+ <*> pure onFallback+ where+ hpackEncoder = do+ let strategy = (HPACK.defaultEncodeStrategy { HPACK.useHuffman = True })+ dt <- HPACK.newDynamicTableForEncoding HPACK.defaultDynamicTableSize+ return $ HpackEncoderContext+ (HPACK.encodeHeader strategy encoderBufSize dt)+ (\n -> HPACK.setLimitForEncoding n dt)++readSettings :: DispatchControl -> IO ConnectionSettings+readSettings = readIORef . _dispatchControlConnectionSettings++modifySettings :: DispatchControl -> (ConnectionSettings -> (ConnectionSettings, a)) -> IO a+modifySettings d = atomicModifyIORef' (_dispatchControlConnectionSettings d)++-- | Helper to carry around the HPACK encoder for outgoing header blocks..+data HpackEncoderContext = HpackEncoderContext {+ _encodeHeaders :: HeaderList -> IO HeaderBlockFragment+ , _applySettings :: Size -> IO ()+ }++data DispatchHPACK = DispatchHPACK {+ _dispatchHPACKWriteHeadersChan :: !HeadersChan+ , _dispatchHPACKWritePushPromisesChan :: !(PushPromisesChan HTTP2Error)+ , _dispatchHPACKDynamicTable :: !DynamicTable+ }++newDispatchHPACKIO :: Size -> IO DispatchHPACK+newDispatchHPACKIO decoderBufSize =+ DispatchHPACK <$> newChan <*> newChan <*> newDecoder+ where+ newDecoder = newDynamicTableForDecoding+ HPACK.defaultDynamicTableSize+ decoderBufSize++newDispatchHPACKReadHeadersChanIO :: DispatchHPACK -> IO HeadersChan+newDispatchHPACKReadHeadersChanIO =+ dupChan . _dispatchHPACKWriteHeadersChan++newDispatchHPACKReadPushPromisesChanIO :: DispatchHPACK -> IO (PushPromisesChan HTTP2Error)+newDispatchHPACKReadPushPromisesChanIO =+ dupChan . _dispatchHPACKWritePushPromisesChan
src/Network/HTTP2/Client/FrameConnection.hs view
@@ -71,7 +71,7 @@ -- | Creates a new 'Http2FrameConnection' to a given host for a frame-to-frame communication. newHttp2FrameConnection :: HostName -> PortNumber- -> TLS.ClientParams+ -> Maybe TLS.ClientParams -> IO Http2FrameConnection newHttp2FrameConnection host port params = do -- Spawns an HTTP2 connection.
src/Network/HTTP2/Client/Helpers.hs view
@@ -141,6 +141,10 @@ -- | Sequentially wait for every push-promise with a handler. -- -- This function runs forever and you should wrap it with 'withAsync'.+-- This function immediately returns if run on a stream that has no PushPromise+-- handler (i.e., server-streams when already waiting for push promises). onPushPromise :: Http2Stream -> PushPromiseHandler -> IO ()-onPushPromise stream handler = forever $ do- _waitPushPromise stream handler+onPushPromise stream handler =+ case _waitPushPromise stream of+ Nothing -> pure ()+ Just w -> forever $ w handler
src/Network/HTTP2/Client/RawConnection.hs view
@@ -30,11 +30,11 @@ -- ^ Server's hostname. -> PortNumber -- ^ Server's port to connect to.- -> TLS.ClientParams+ -> Maybe TLS.ClientParams -- ^ TLS parameters. The 'TLS.onSuggestALPN' hook is -- overwritten to always return ["h2", "h2-17"]. -> IO RawHttp2Connection-newRawHttp2Connection host port params = do+newRawHttp2Connection host port mparams = do -- Connects to SSL. ctx <- initConnectionContext conn <- connectTo ctx connParams@@ -49,8 +49,8 @@ return $ RawHttp2Connection putRaw getRaw doClose where- overwrittenALPNHook = (TLS.clientHooks params) {+ overwriteALPNHook params = (TLS.clientHooks params) { TLS.onSuggestALPN = return $ Just [ "h2", "h2-17" ] }- modifiedParams = params { TLS.clientHooks = overwrittenALPNHook }- connParams = ConnectionParams host port (Just . TLSSettings $ modifiedParams) Nothing+ modifyParams params = params { TLS.clientHooks = overwriteALPNHook params }+ connParams = ConnectionParams host port (TLSSettings . modifyParams <$> mparams) Nothing