packages feed

http3-0.1.8: test/HTTP3/InformationalSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module HTTP3.InformationalSpec where

import qualified Control.Exception as E
import qualified Data.ByteString as B
import Network.HTTP.Types
import qualified Network.HTTP3.Client as C
import Network.HTTP3.Internal (
    ApplicationProtocolError (..),
    H3Frame (..),
    H3FrameType (..),
 )
import Network.HTTP3.Server
import qualified Network.QUIC as Q
import qualified Network.QUIC.Client as QUIC
import System.Timeout (timeout)
import Test.Hspec

import HTTP3.Config
import HTTP3.Server

spec :: Spec
spec = do
    -- Two informational responses, 100 (Continue) and 103 (Early Hints),
    -- each a HEADERS frame of its own, before every response.
    describe "H3 client with informational responses" $
        beforeAll (setupWith (early [continue, earlyHints]) server 4096) $
            afterAll teardown $
                it "takes the final response past informational ones" $ \_ -> do
                    -- The first header section was taken for the response,
                    -- and the body reader took the next one for trailers: the
                    -- client saw a 100 with no body, and the final response
                    -- was lost.
                    r <- get
                    case r of
                        Just (Right x) -> x `shouldBe` (Just ok200, "Hello, world!\n")
                        _ -> expectationFailure $ "unexpected: " ++ show r
    -- Twenty of them before every response.
    describe "H3 client with too many informational responses" $
        beforeAll (setupWith (early (replicate 20 earlyHints)) server 4096) $
            afterAll teardown $
                it "resets a response with too many informational ones" $ \_ -> do
                    -- RFC 9114 sets no limit, and a server could go on sending
                    -- them for ever.
                    r <- get
                    case r of
                        Just (Left (Q.ApplicationProtocolErrorIsSent H3ExcessiveLoad _)) ->
                            return ()
                        _ -> expectationFailure $ "unexpected: " ++ show r
  where
    get :: IO (Maybe (Either Q.QUICException (Maybe Status, B.ByteString)))
    get = timeout 3000000 $ E.try $ QUIC.run testClientConfig $ \conn ->
        E.bracket allocSimpleConfig freeSimpleConfig $ \conf ->
            C.run conn testH3ClientConfig conf $ \sendRequest _aux ->
                sendRequest (C.requestNoBody methodGet "/" []) $ \rsp -> do
                    let drain acc = do
                            bs <- C.getResponseBodyChunk rsp
                            if B.null bs then return acc else drain (acc <> bs)
                    body <- drain ""
                    return (C.responseStatus rsp, body)
    early frames conf =
        conf
            { confHooks =
                (confHooks conf){onHeadersFrameCreated = (frames ++)}
            }
    -- Required Insert Count 0 and Base 0, then one field line referring to
    -- the static table: :status 100 is index 63, :status 103 index 24.
    continue = H3Frame H3FrameHeaders "\x00\x00\xff\x00"
    earlyHints = H3Frame H3FrameHeaders "\x00\x00\xd8"