{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} -- | A client library for HTTP/3. module Network.HTTP3.Client ( -- * Runner run, -- * Runner arguments ClientConfig (..), Config (..), allocSimpleConfig, freeSimpleConfig, Hooks (..), defaultHooks, Scheme, Authority, -- * HTTP\/3 client Client, SendRequest, Aux, -- * Request Request, -- * Creating request H2.requestNoBody, H2.requestFile, H2.requestStreaming, H2.requestBuilder, -- ** Trailers maker H2.TrailersMaker, H2.NextTrailersMaker (..), H2.defaultTrailersMaker, H2.setRequestTrailersMaker, -- * Response Response, -- ** Accessing response H2.responseStatus, H2.responseHeaders, H2.responseBodySize, H2.getResponseBodyChunk, H2.getResponseTrailers, -- * Types H2.Method, H2.Path, H2.FileSpec (..), H2.FileOffset, H2.ByteCount, -- * RecvN H2.defaultReadN, -- * Position read for files H2.PositionReadMaker, H2.PositionRead, H2.Sentinel (..), H2.defaultPositionReadMaker, ) where import Control.Concurrent import qualified Data.ByteString.Char8 as C8 import Data.IORef import Network.HTTP2.Client (Authority, Client, Scheme, SendRequest) import qualified Network.HTTP2.Client as H2 import Network.HTTP2.Client.Internal (Aux (..), Request (..), Response (..)) import Network.HTTP2.Internal (InpObj (..)) import qualified Network.HTTP2.Internal as H2 import Network.QUIC (Connection) import qualified Network.QUIC as QUIC import Network.QUIC.Internal (possibleMyStreams) import qualified UnliftIO.Exception as E import Network.HTTP3.Config import Network.HTTP3.Context import Network.HTTP3.Control import Network.HTTP3.Error import Network.HTTP3.Frame import Network.HTTP3.Recv import Network.HTTP3.Send -- | Configuration for HTTP\/3 or HQ client. For HQ, 'authority' is -- not used and an server's IP address is used in 'Request'. data ClientConfig = ClientConfig { scheme :: Scheme , authority :: Authority } -- | Running an HTTP\/3 client. run :: Connection -> ClientConfig -> Config -> H2.Client a -> IO a run conn ClientConfig{..} conf client = E.bracket open close $ \ctx -> do tid0 <- forkIO $ setupUnidirectional conn conf addThreadId ctx tid0 tid1 <- forkIO $ readerClient ctx addThreadId ctx tid1 client (sendRequest ctx scheme authority) aux where open = do ref <- newIORef IInit newContext conn conf (controlStream conn ref) close = clearContext aux = Aux { auxPossibleClientStreams = possibleMyStreams conn } readerClient :: Context -> IO () readerClient ctx = loop where loop = do accept ctx >>= process loop process strm | QUIC.isClientInitiatedUnidirectional sid = return () -- error | QUIC.isClientInitiatedBidirectional sid = return () | QUIC.isServerInitiatedUnidirectional sid = do tid <- forkIO $ unidirectional ctx strm addThreadId ctx tid | otherwise = return () -- push? where sid = QUIC.streamId strm sendRequest :: Context -> Scheme -> Authority -> Request -> (Response -> IO a) -> IO a sendRequest ctx scm auth (Request outobj) processResponse = do th <- registerThread ctx let hdr = H2.outObjHeaders outobj hdr' = (":scheme", scm) : (":authority", C8.pack auth) : hdr E.bracket (newStream ctx) closeStream $ \strm -> do sendHeader ctx strm th hdr' tid <- forkIO $ do sendBody ctx strm th outobj QUIC.shutdownStream strm addThreadId ctx tid src <- newSource strm mvt <- recvHeader ctx src case mvt of Nothing -> do QUIC.resetStream strm H3MessageError threadDelay 100000 -- just for type inference E.throwIO $ QUIC.ApplicationProtocolErrorIsSent H3MessageError "" Just vt -> do refI <- newIORef IInit refH <- newIORef Nothing let readB = recvBody ctx src refI refH rsp = Response $ InpObj vt Nothing readB refH processResponse rsp