{-# language DuplicateRecordFields #-} {-# language LambdaCase #-} module Http.Response ( Response(..) , StatusLine(..) , decode ) where import Control.Monad (when) import Data.Bytes (Bytes) import Data.Bytes.Parser (Parser) import Data.Bytes.Types (Bytes(Bytes)) import Data.Primitive (SmallArray,ByteArray(ByteArray)) import Data.Word (Word8,Word16) import Data.Text (Text) import Http.Header (Header) import Http.Headers (Headers) import qualified Data.Text.Internal as Text import qualified Data.Text.Array import qualified Data.Bytes.Parser as Parser import qualified Data.Bytes.Parser.Latin as Latin import qualified Http.Header as Header import qualified Http.Headers as Headers -- | The response status line and the response headers. data Response = Response { statusLine :: !StatusLine , headers :: !Headers } deriving (Show) data StatusLine = StatusLine { statusCode :: !Word16 , statusReason :: {-# UNPACK #-} !Text } deriving (Show) -- | Decode the response status line and the response headers. Fails if -- any extraneous input is present after the double CRLF sequence that -- ends the headers. decode :: Int -> Bytes -> Maybe Response decode !n !b = Parser.parseBytesMaybe (parserResponse n <* Parser.endOfInput ()) b parserResponse :: Int -- ^ Maximum number of headers -> Parser () s Response parserResponse !n = do statusLine <- parserStatusLine headers0 <- Header.parserSmallArray n let !headers = Headers.fromArray headers0 pure Response{statusLine,headers} -- Consumes the trailing CRLF parserStatusLine :: Parser () s StatusLine parserStatusLine = do Latin.char5 () 'H' 'T' 'T' 'P' '/' versionMajor <- Latin.decWord8 () when (versionMajor /= 1) (Parser.fail ()) Latin.char () '.' versionMinor <- Latin.decWord8 () when (versionMinor /= 1) (Parser.fail ()) Latin.char () ' ' statusCode <- Latin.decWord16 () when (statusCode >= 1000) (Parser.fail ()) Latin.char () ' ' -- RFC 7230: reason-phrase = *( HTAB / SP / VCHAR / obs-text ) statusReason <- Parser.takeWhile $ \c -> (c >= 0x20 && c <= 0x7e) || (c == 0x09) Latin.char2 () '\r' '\n' pure StatusLine{statusCode,statusReason=unsafeBytesToText statusReason} unsafeBytesToText :: Bytes -> Text {-# inline unsafeBytesToText #-} unsafeBytesToText (Bytes (ByteArray arr) off len) = Text.Text (Data.Text.Array.ByteArray arr) off len