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
type FallBackFrameHandler = (FrameHeader, FramePayload) -> IO ()
ignoreFallbackHandler :: FallBackFrameHandler
ignoreFallbackHandler = const $ pure ()
data RemoteSentGoAwayFrame = RemoteSentGoAwayFrame !StreamId !ErrorCodeId !ByteString
deriving Show
instance Exception RemoteSentGoAwayFrame
type GoAwayHandler = RemoteSentGoAwayFrame -> IO ()
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
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)
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