{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.HTTP2.Client (
runHttp2Client
, newHttp2Client
, withHttp2Stream
, headers
, trailers
, sendData
, Http2Client(..)
, PushPromiseHandler
, StreamDefinition(..)
, StreamStarter
, TooMuchConcurrency(..)
, StreamThread
, Http2Stream(..)
, IncomingFlowControl(..)
, OutgoingFlowControl(..)
, linkAsyncs
, RemoteSentGoAwayFrame(..)
, GoAwayHandler
, defaultGoAwayHandler
, FallBackFrameHandler
, ignoreFallbackHandler
, FlagSetter
, Http2ClientAsyncs(..)
, _gtfo
, StreamEvent(..)
, module Network.HTTP2.Client.FrameConnection
, 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.Monad (forever, void, when, forM_)
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import Data.IORef (newIORef, atomicModifyIORef', readIORef)
import Data.Maybe (fromMaybe)
import Network.HPACK as HPACK
import Network.HTTP2 as HTTP2
import Network.Socket (HostName, PortNumber)
import Network.TLS (ClientParams)
import Network.HTTP2.Client.Channels
import Network.HTTP2.Client.Dispatch
import Network.HTTP2.Client.FrameConnection
data IncomingFlowControl = IncomingFlowControl {
_addCredit :: WindowSize -> IO ()
, _consumeCredit :: WindowSize -> IO Int
, _updateWindow :: IO Bool
}
data OutgoingFlowControl = OutgoingFlowControl {
_receiveCredit :: WindowSize -> IO ()
, _withdrawCredit :: WindowSize -> IO WindowSize
}
data StreamDefinition a = StreamDefinition {
_initStream :: IO StreamThread
, _handleStream :: IncomingFlowControl -> OutgoingFlowControl -> IO a
}
type StreamStarter a =
(Http2Stream -> StreamDefinition a) -> IO (Either TooMuchConcurrency a)
newtype TooMuchConcurrency = TooMuchConcurrency { _getStreamRoomNeeded :: Int }
deriving Show
data Http2Client = Http2Client {
_ping :: ByteString -> IO (IO (FrameHeader, FramePayload))
, _settings :: SettingsList -> IO (IO (FrameHeader, FramePayload))
, _goaway :: ErrorCodeId -> ByteString -> IO ()
, _startStream :: forall a. StreamStarter a
, _incomingFlowControl :: IncomingFlowControl
, _outgoingFlowControl :: OutgoingFlowControl
, _payloadSplitter :: IO PayloadSplitter
, _asyncs :: !Http2ClientAsyncs
, _close :: IO ()
}
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 ()
, _initStop :: IO Bool
}
data Http2ClientAsyncs = Http2ClientAsyncs {
_waitSettingsAsync :: Async (FrameHeader, FramePayload)
, _incomingFramesAsync :: Async ()
}
linkAsyncs :: Http2Client -> IO ()
linkAsyncs client =
let Http2ClientAsyncs{..} = _asyncs client in do
link _waitSettingsAsync
link _incomingFramesAsync
_gtfo :: Http2Client -> ErrorCodeId -> ByteString -> IO ()
_gtfo = _goaway
data StreamThread = CST
data Http2Stream = Http2Stream {
_headers :: HPACK.HeaderList
-> (FrameFlags -> FrameFlags)
-> IO StreamThread
, _prio :: Priority -> IO ()
, _rst :: ErrorCodeId -> IO ()
, _waitEvent :: IO StreamEvent
, _sendDataChunk :: (FrameFlags -> FrameFlags) -> ByteString -> IO ()
, _handlePushPromise :: StreamId -> HeaderList -> PushPromiseHandler -> IO ()
}
trailers :: Http2Stream -> HPACK.HeaderList -> (FrameFlags -> FrameFlags) -> IO ()
trailers stream hdrs flagmod = void $ _headers stream hdrs flagmod
type PushPromiseHandler =
StreamId -> Http2Stream -> HeaderList -> IncomingFlowControl -> OutgoingFlowControl -> IO ()
withHttp2Stream :: Http2Client -> StreamStarter a
withHttp2Stream = _startStream
type FlagSetter = FrameFlags -> FrameFlags
headers :: Http2Stream -> HeaderList -> FlagSetter -> IO StreamThread
headers = _headers
runHttp2Client
:: Http2FrameConnection
-> Int
-> Int
-> SettingsList
-> GoAwayHandler
-> FallBackFrameHandler
-> (Http2Client -> IO a)
-> IO a
runHttp2Client conn encoderBufSize decoderBufSize initSettings goAwayHandler fallbackHandler mainHandler = do
(incomingLoop, initClient) <- initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler
withAsync incomingLoop $ \aIncoming -> do
settsIO <- _initSettings initClient initSettings
withAsync settsIO $ \aSettings -> do
let client = Http2Client {
_settings = _initSettings initClient
, _ping = _initPing initClient
, _goaway = _initGoaway initClient
, _close =
_initStop initClient >> _initClose initClient
, _startStream = _initStartStream initClient
, _incomingFlowControl = _initIncomingFlowControl initClient
, _outgoingFlowControl = _initOutgoingFlowControl initClient
, _payloadSplitter = _initPaylodSplitter initClient
, _asyncs = Http2ClientAsyncs aSettings aIncoming
}
linkAsyncs client
ret <- mainHandler client
_close client
return ret
newHttp2Client
:: Http2FrameConnection
-> Int
-> Int
-> SettingsList
-> GoAwayHandler
-> FallBackFrameHandler
-> 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 =
_initStop initClient >> _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 []
dispatch <- newDispatchIO
dispatchControl <- newDispatchControlIO encoderBufSize
ackPing
ackSettings
goAwayHandler
fallbackHandler
_initIncomingFlowControl <- newIncomingFlowControl dispatchControl controlStream
(_initOutgoingFlowControl,windowUpdatesChan) <- newOutgoingFlowControl dispatchControl 0
dispatchHPACK <- newDispatchHPACKIO decoderBufSize
(incomingLoop,endIncomingLoop) <- dispatchLoop conn dispatch dispatchControl windowUpdatesChan _initIncomingFlowControl dispatchHPACK
conccurentStreams <- newIORef 0
clientStreamIdMutex <- newMVar 0
let withClientStreamId h = bracket (takeMVar clientStreamIdMutex)
(putMVar clientStreamIdMutex . succ)
(\k -> h (2 * k + 1))
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
dispatchStream <- newDispatchStreamIO sid
initializeStream conn
dispatch
dispatchControl
dispatchStream
getWork
Idle
v <- cont
atomicModifyIORef' conccurentStreams (\n -> (n - 1, ()))
pure v
let _initPing dat = do
handler <- registerPingHandler dispatchControl dat
sendPingFrame controlStream id dat
return $ waitPingReply handler
let _initSettings settslist = do
handler <- registerSetSettingsHandler dispatchControl
sendSettingsFrame controlStream id settslist
return $ do
ret <- waitSetSettingsReply handler
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 _initPaylodSplitter = settingsPayloadSplitter <$> readSettings dispatchControl
let _initStop = endIncomingLoop
let _initClose = closeConnection conn
return (incomingLoop, InitHttp2Client{..})
initializeStream
:: Http2FrameConnection
-> Dispatch
-> DispatchControl
-> DispatchStream
-> (Http2Stream -> StreamDefinition a)
-> StreamFSMState
-> IO (IO a)
initializeStream conn dispatch control stream getWork initialState = do
let sid = _dispatchStreamId stream
let frameStream = makeFrameClientStream conn sid
let events = _dispatchStreamReadEvents stream
incomingStreamFlowControl <- newIncomingFlowControl control frameStream
(outgoingStreamFlowControl, windowUpdatesChan) <- newOutgoingFlowControl control sid
registerStream dispatch sid (StreamState windowUpdatesChan events initialState)
let _headers headersList flags = do
splitter <- settingsPayloadSplitter <$> readSettings control
cst <- sendHeaders frameStream (_dispatchControlHpackEncoder control) headersList splitter flags
when (testEndStream $ flags 0) $ do
closeLocalStream dispatch sid
return cst
let _waitEvent = readChan events
let _sendDataChunk flags dat = do
sendDataFrame frameStream flags dat
when (testEndStream $ flags 0) $ do
closeLocalStream dispatch sid
let _rst = \err -> do
sendResetFrame frameStream err
closeReleaseStream dispatch sid
let _prio = sendPriorityFrame frameStream
let _handlePushPromise ppSid ppHeaders ppHandler = do
let mkStreamActions s = StreamDefinition (return CST) (ppHandler sid s ppHeaders)
newStream <- newDispatchStreamIO ppSid
ppCont <- initializeStream conn
dispatch
control
newStream
mkStreamActions
ReservedRemote
ppCont
let streamActions = getWork $ Http2Stream{..}
_ <- _initStream streamActions
return $ _handleStream streamActions incomingStreamFlowControl outgoingStreamFlowControl
dispatchLoop
:: Http2FrameConnection
-> Dispatch
-> DispatchControl
-> Chan (FrameHeader, FramePayload)
-> IncomingFlowControl
-> DispatchHPACK
-> IO (IO (), IO Bool)
dispatchLoop conn d dc windowUpdatesChan inFlowControl dh = do
let getNextFrame = next conn
let go = delayException . forever $ do
frame <- getNextFrame
dispatchFramesStep frame d
whenFrame (hasStreamId 0) frame $ \got ->
dispatchControlFramesStep windowUpdatesChan got dc
whenFrame (hasTypeId [FrameData]) frame $ \got ->
creditDataFramesStep d inFlowControl got
whenFrame (hasTypeId [FrameWindowUpdate]) frame $ \got -> do
updateWindowsStep d got
whenFrame (hasTypeId [FramePushPromise, FrameHeaders]) frame $ \got -> do
let hpackLoop (FinishedWithHeaders curFh sId mkNewHdrs) = 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
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
chan <- fmap _streamStateEvents <$> lookupStreamState d sId
let msg = StreamErrorEvent curFh err
maybe (return ()) (flip writeChan msg) chan
hpackLoop (dispatchHPACKFramesStep got dh)
whenFrame (hasTypeId [FrameRSTStream]) frame $ \got -> do
handleRSTStep d got
finalizeFramesStep frame d
end <- newEmptyMVar
let run = void $ race go (takeMVar end)
let stop = tryPutMVar end ()
return (run, stop)
handleRSTStep
:: Dispatch
-> (FrameHeader, FramePayload)
-> IO ()
handleRSTStep d (fh, payload) = do
let sid = streamId fh
case payload of
(RSTStreamFrame err) -> do
chan <- fmap _streamStateEvents <$> lookupStreamState d sid
let msg = StreamErrorEvent fh (HTTP2.fromErrorCodeId err)
maybe (return ()) (flip writeChan msg) chan
closeReleaseStream d sid
_ ->
error $ "expecting RSTFrame but got " ++ show payload
dispatchFramesStep
:: (FrameHeader, Either HTTP2Error FramePayload)
-> Dispatch
-> IO ()
dispatchFramesStep (fh,_) d = do
let sid = streamId fh
atomicModifyIORef' (_dispatchMaxStreamId d) (\n -> (max n sid, ()))
finalizeFramesStep
:: (FrameHeader, Either HTTP2Error FramePayload)
-> Dispatch
-> IO ()
finalizeFramesStep (fh,_) d = do
let sid = streamId fh
when (testEndStream $ flags fh) $ do
closeRemoteStream d sid
dispatchControlFramesStep
:: Chan (FrameHeader, FramePayload)
-> (FrameHeader, FramePayload)
-> DispatchControl
-> IO ()
dispatchControlFramesStep windowUpdatesChan controlFrame@(fh, payload) control@(DispatchControl{..}) = do
case payload of
(SettingsFrame settsList)
| not . testAck . flags $ fh -> do
atomicModifyIORef' _dispatchControlConnectionSettings
(\(ConnectionSettings cli srv) ->
(ConnectionSettings cli (HTTP2.updateSettings srv settsList), ()))
maybe (return ())
(_applySettings _dispatchControlHpackEncoder)
(lookup SettingsHeaderTableSize settsList)
_dispatchControlAckSettings
| otherwise -> do
handler <- lookupAndReleaseSetSettingsHandler control
maybe (return ()) (notifySetSettingsHandler controlFrame) handler
(PingFrame pingMsg)
| not . testAck . flags $ fh ->
_dispatchControlAckPing pingMsg
| otherwise -> do
handler <- lookupAndReleasePingHandler control pingMsg
maybe (return ()) (notifyPingHandler controlFrame) handler
(WindowUpdateFrame _ ) ->
writeChan windowUpdatesChan controlFrame
(GoAwayFrame lastSid errCode reason) ->
_dispatchControlOnGoAway $ RemoteSentGoAwayFrame lastSid errCode reason
_ ->
_dispatchControlOnFallback controlFrame
creditDataFramesStep
:: Dispatch
-> IncomingFlowControl
-> (FrameHeader, FramePayload)
-> IO ()
creditDataFramesStep d flowControl (fh,payload) = do
_ <- _consumeCredit flowControl (HTTP2.payloadLength fh)
_addCredit flowControl (HTTP2.payloadLength fh)
let sid = streamId fh
case payload of
(DataFrame dat) -> do
chan <- fmap _streamStateEvents <$> lookupStreamState d sid
maybe (return ()) (flip writeChan $ StreamDataEvent fh dat) chan
_ ->
error $ "expecting DataFrame but got " ++ show payload
updateWindowsStep
:: Dispatch
-> (FrameHeader, FramePayload)
-> IO ()
updateWindowsStep d got@(fh,_) = do
let sid = HTTP2.streamId fh
chan <- fmap _streamStateWindowUpdatesChan <$> lookupStreamState d sid
maybe (return ()) (flip writeChan got) chan
data HPACKLoopDecision =
ForwardHeader !StreamId
| OpenPushPromise !StreamId !StreamId
data HPACKStepResult =
WaitContinuation !((FrameHeader, Either HTTP2Error FramePayload) -> IO HPACKStepResult)
| FailedHeaders !FrameHeader !StreamId ErrorCode
| FinishedWithHeaders !FrameHeader !StreamId (IO HeaderList)
| FinishedWithPushPromise !FrameHeader !StreamId !StreamId (IO HeaderList)
dispatchHPACKFramesStep
:: (FrameHeader, FramePayload)
-> DispatchHPACK
-> HPACKStepResult
dispatchHPACKFramesStep (fh,fp) (DispatchHPACK{..}) =
let (decision, pattern) = case fp of
PushPromiseFrame ppSid hbf -> do
(OpenPushPromise sid ppSid, Right hbf)
HeadersFrame _ hbf ->
(ForwardHeader sid, Right hbf)
RSTStreamFrame err ->
(ForwardHeader sid, Left err)
_ ->
error "wrong TypeId"
in go fh decision pattern
where
sid :: StreamId
sid = HTTP2.streamId fh
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 ->
return $ go lastFh decision (Right (ByteString.append buffer chbf))
RSTStreamFrame err ->
return $ go lastFh decision (Left err)
_ ->
error "continued frame has invalid type")
else case decision of
ForwardHeader sId ->
FinishedWithHeaders curFh sId (decodeHeader _dispatchHPACKDynamicTable buffer)
OpenPushPromise parentSid newSid ->
FinishedWithPushPromise curFh parentSid newSid (decodeHeader _dispatchHPACKDynamicTable buffer)
go curFh _ (Left err) =
FailedHeaders curFh sid (HTTP2.fromErrorCodeId err)
newIncomingFlowControl
:: DispatchControl
-> Http2FrameClientStream
-> IO IncomingFlowControl
newIncomingFlowControl control stream = do
let getBase = if _getStreamId stream == 0
then return HTTP2.defaultInitialWindowSize
else initialWindowSize . _clientSettings <$> readSettings control
creditAdded <- newIORef 0
creditConsumed <- newIORef 0
let _addCredit n = atomicModifyIORef' creditAdded (\c -> (c + n, ()))
let _consumeCredit n = do
conso <- atomicModifyIORef' creditConsumed (\c -> (c + n, c + n))
base <- getBase
extra <- readIORef creditAdded
return $ base + extra - conso
let _updateWindow = do
base <- initialWindowSize . _clientSettings <$> readSettings control
added <- readIORef creditAdded
consumed <- readIORef creditConsumed
let transferred = min added (HTTP2.maxWindowSize - base + consumed)
let shouldUpdate = transferred > 0
_addCredit (negate transferred)
_ <- _consumeCredit (negate transferred)
when shouldUpdate (sendWindowUpdateFrame stream transferred)
return shouldUpdate
return $ IncomingFlowControl _addCredit _consumeCredit _updateWindow
newOutgoingFlowControl ::
DispatchControl
-> StreamId
-> IO (OutgoingFlowControl, Chan (FrameHeader, FramePayload))
newOutgoingFlowControl control sid = do
credit <- newIORef 0
frames <- newChan
let getBase = if sid == 0
then return HTTP2.defaultInitialWindowSize
else initialWindowSize . _serverSettings <$> readSettings control
let receive n = atomicModifyIORef' credit (\c -> (c + n, ()))
let withdraw 0 = return 0
withdraw n = do
base <- getBase
got <- atomicModifyIORef' credit (\c ->
if base + c >= n
then (c - n, n)
else (0 - base, base + c))
if got > 0
then return got
else do
amount <- race (waitSettingsChange base) (waitSomeCredit frames)
receive (either (const 0) id amount)
withdraw n
return $ (OutgoingFlowControl receive withdraw, frames)
where
waitSettingsChange prev = do
new <- initialWindowSize . _serverSettings <$> readSettings control
if new == prev then threadDelay 1000000 >> waitSettingsChange prev else return ()
waitSomeCredit frames = do
got <- readChan frames
case got of
(_, WindowUpdateFrame amt) ->
return amt
_ ->
error "got forwarded an unknown frame"
sendHeaders
:: Http2FrameClientStream
-> HpackEncoderContext
-> HeaderList
-> PayloadSplitter
-> (FrameFlags -> FrameFlags)
-> IO StreamThread
sendHeaders s enc hdrs blockSplitter flagmod = do
_sendFrames s mkFrames
return CST
where
mkFrames = do
headerBlockFragments <- blockSplitter <$> _encodeHeaders enc hdrs
let framers = (HeadersFrame Nothing) : repeat ContinuationFrame
let frames = zipWith ($) framers headerBlockFragments
let modifiersReversed = (HTTP2.setEndHeader . flagmod) : repeat id
let arrangedFrames = reverse $ zip modifiersReversed (reverse frames)
return arrangedFrames
type PayloadSplitter = ByteString -> [ByteString]
settingsPayloadSplitter :: ConnectionSettings -> PayloadSplitter
settingsPayloadSplitter (ConnectionSettings _ srv) =
fixedSizeChunks (maxFrameSize srv)
fixedSizeChunks :: Int -> ByteString -> [ByteString]
fixedSizeChunks 0 _ = error "cannot chunk by zero-length blocks"
fixedSizeChunks _ "" = []
fixedSizeChunks len bstr =
let
(chunk, rest) = ByteString.splitAt len bstr
in
chunk : fixedSizeChunks len rest
sendData :: Http2Client -> Http2Stream -> FlagSetter -> ByteString -> IO ()
sendData conn stream flagmod dat = do
splitter <- _payloadSplitter conn
let chunks = splitter dat
let pairs = reverse $ zip (flagmod : repeat id) (reverse chunks)
when (null chunks) $ _sendDataChunk stream flagmod ""
forM_ pairs $ \(flags, chunk) -> _sendDataChunk stream flags chunk
sendDataFrame
:: Http2FrameClientStream
-> (FrameFlags -> FrameFlags) -> ByteString -> IO ()
sendDataFrame s flagmod dat = do
sendOne s flagmod (DataFrame dat)
sendResetFrame :: Http2FrameClientStream -> ErrorCodeId -> IO ()
sendResetFrame s err = do
sendOne s id (RSTStreamFrame err)
sendGTFOFrame
:: Http2FrameClientStream
-> StreamId -> ErrorCodeId -> ByteString -> IO ()
sendGTFOFrame s lastStreamId err errStr = do
sendOne s id (GoAwayFrame lastStreamId err errStr)
rfcError :: String -> a
rfcError msg = error (msg ++ "draft-ietf-httpbis-http2-17")
sendPingFrame
:: Http2FrameClientStream
-> (FrameFlags -> FrameFlags)
-> ByteString
-> IO ()
sendPingFrame s flags dat
| _getStreamId s /= 0 =
rfcError "PING frames are not associated with any individual stream."
| ByteString.length dat /= 8 =
rfcError "PING frames MUST contain 8 octets"
| otherwise = sendOne s flags (PingFrame dat)
sendWindowUpdateFrame
:: Http2FrameClientStream -> WindowSize -> IO ()
sendWindowUpdateFrame s amount = do
let payload = WindowUpdateFrame amount
sendOne s id payload
return ()
sendSettingsFrame
:: Http2FrameClientStream
-> (FrameFlags -> FrameFlags) -> SettingsList -> IO ()
sendSettingsFrame s flags setts
| _getStreamId s /= 0 =
rfcError "The stream identifier for a SETTINGS frame MUST be zero (0x0)."
| otherwise = do
let payload = SettingsFrame setts
sendOne s flags payload
return ()
sendPriorityFrame :: Http2FrameClientStream -> Priority -> IO ()
sendPriorityFrame s p = do
let payload = PriorityFrame p
sendOne s id payload
return ()
delayException :: IO a -> IO a
delayException act = act `catch` slowdown
where
slowdown :: SomeException -> IO a
slowdown e = threadDelay 50000 >> throwIO e