module Hasura.Server.Compression
( compressResponse,
CompressionType (..),
EncodingType,
identityEncoding,
contentEncodingHeader,
compressionTypeToTxt,
compressFast,
getAcceptedEncodings,
)
where
import Codec.Compression.GZip qualified as GZ
import Data.ByteString.Lazy qualified as BL
import Data.Set qualified as Set
import Data.Text qualified as T
import Hasura.Prelude
import Hasura.Server.Utils (gzipHeader)
import Network.HTTP.Types.Header qualified as NH
data CompressionType
= CTGZip
deriving (Int -> CompressionType -> ShowS
[CompressionType] -> ShowS
CompressionType -> String
(Int -> CompressionType -> ShowS)
-> (CompressionType -> String)
-> ([CompressionType] -> ShowS)
-> Show CompressionType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CompressionType -> ShowS
showsPrec :: Int -> CompressionType -> ShowS
$cshow :: CompressionType -> String
show :: CompressionType -> String
$cshowList :: [CompressionType] -> ShowS
showList :: [CompressionType] -> ShowS
Show, CompressionType -> CompressionType -> Bool
(CompressionType -> CompressionType -> Bool)
-> (CompressionType -> CompressionType -> Bool)
-> Eq CompressionType
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CompressionType -> CompressionType -> Bool
== :: CompressionType -> CompressionType -> Bool
$c/= :: CompressionType -> CompressionType -> Bool
/= :: CompressionType -> CompressionType -> Bool
Eq, Eq CompressionType
Eq CompressionType
-> (CompressionType -> CompressionType -> Ordering)
-> (CompressionType -> CompressionType -> Bool)
-> (CompressionType -> CompressionType -> Bool)
-> (CompressionType -> CompressionType -> Bool)
-> (CompressionType -> CompressionType -> Bool)
-> (CompressionType -> CompressionType -> CompressionType)
-> (CompressionType -> CompressionType -> CompressionType)
-> Ord CompressionType
CompressionType -> CompressionType -> Bool
CompressionType -> CompressionType -> Ordering
CompressionType -> CompressionType -> CompressionType
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: CompressionType -> CompressionType -> Ordering
compare :: CompressionType -> CompressionType -> Ordering
$c< :: CompressionType -> CompressionType -> Bool
< :: CompressionType -> CompressionType -> Bool
$c<= :: CompressionType -> CompressionType -> Bool
<= :: CompressionType -> CompressionType -> Bool
$c> :: CompressionType -> CompressionType -> Bool
> :: CompressionType -> CompressionType -> Bool
$c>= :: CompressionType -> CompressionType -> Bool
>= :: CompressionType -> CompressionType -> Bool
$cmax :: CompressionType -> CompressionType -> CompressionType
max :: CompressionType -> CompressionType -> CompressionType
$cmin :: CompressionType -> CompressionType -> CompressionType
min :: CompressionType -> CompressionType -> CompressionType
Ord)
type EncodingType = Maybe CompressionType
identityEncoding :: EncodingType
identityEncoding :: EncodingType
identityEncoding = EncodingType
forall a. Maybe a
Nothing
compressionTypeToTxt :: CompressionType -> Text
compressionTypeToTxt :: CompressionType -> Text
compressionTypeToTxt CompressionType
CTGZip = Text
"gzip"
contentEncodingHeader :: CompressionType -> NH.Header
CompressionType
CTGZip = Header
gzipHeader
compressResponse ::
NH.RequestHeaders ->
BL.ByteString ->
(BL.ByteString, EncodingType)
compressResponse :: RequestHeaders -> ByteString -> (ByteString, EncodingType)
compressResponse RequestHeaders
reqHeaders ByteString
unCompressedResp
| Set EncodingType
acceptedEncodings Set EncodingType -> Set EncodingType -> Bool
forall a. Eq a => a -> a -> Bool
== [EncodingType] -> Set EncodingType
forall a. Ord a => [a] -> Set a
Set.fromList [EncodingType
identityEncoding, CompressionType -> EncodingType
forall a. a -> Maybe a
Just CompressionType
CTGZip] =
if ByteString -> Bool
shouldSkipCompression ByteString
unCompressedResp
then (ByteString, EncodingType)
notCompressed
else (CompressionType -> ByteString -> ByteString
compressFast CompressionType
CTGZip ByteString
unCompressedResp, CompressionType -> EncodingType
forall a. a -> Maybe a
Just CompressionType
CTGZip)
| Set EncodingType
acceptedEncodings Set EncodingType -> Set EncodingType -> Bool
forall a. Eq a => a -> a -> Bool
== [EncodingType] -> Set EncodingType
forall a. Ord a => [a] -> Set a
Set.fromList [CompressionType -> EncodingType
forall a. a -> Maybe a
Just CompressionType
CTGZip] =
(CompressionType -> ByteString -> ByteString
compressFast CompressionType
CTGZip ByteString
unCompressedResp, CompressionType -> EncodingType
forall a. a -> Maybe a
Just CompressionType
CTGZip)
| Set EncodingType
acceptedEncodings Set EncodingType -> Set EncodingType -> Bool
forall a. Eq a => a -> a -> Bool
== [EncodingType] -> Set EncodingType
forall a. Ord a => [a] -> Set a
Set.fromList [EncodingType
identityEncoding] =
(ByteString, EncodingType)
notCompressed
| Bool
otherwise =
(ByteString, EncodingType)
notCompressed
where
acceptedEncodings :: Set EncodingType
acceptedEncodings = RequestHeaders -> Set EncodingType
getAcceptedEncodings RequestHeaders
reqHeaders
notCompressed :: (ByteString, EncodingType)
notCompressed = (ByteString
unCompressedResp, EncodingType
identityEncoding)
compressFast :: CompressionType -> BL.ByteString -> BL.ByteString
compressFast :: CompressionType -> ByteString -> ByteString
compressFast = \case
CompressionType
CTGZip -> CompressParams -> ByteString -> ByteString
GZ.compressWith CompressParams
gzipCompressionParams
where
gzipCompressionParams :: CompressParams
gzipCompressionParams =
CompressParams
GZ.defaultCompressParams {compressLevel :: CompressionLevel
GZ.compressLevel = Int -> CompressionLevel
GZ.compressionLevel Int
1}
shouldSkipCompression :: BL.ByteString -> Bool
shouldSkipCompression :: ByteString -> Bool
shouldSkipCompression ByteString
bs = ByteString -> Int64
BL.length ByteString
bs Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
< Int64
700
getAcceptedEncodings :: NH.RequestHeaders -> Set.Set EncodingType
getAcceptedEncodings :: RequestHeaders -> Set EncodingType
getAcceptedEncodings RequestHeaders
reqHeaders = [EncodingType] -> Set EncodingType
forall a. Ord a => [a] -> Set a
Set.fromList [EncodingType]
acceptedEncodingTypes
where
rawHeaderVals :: [Text]
rawHeaderVals =
(Header -> [Text]) -> RequestHeaders -> [Text]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (ByteString -> [Text]
splitHeaderVal (ByteString -> [Text])
-> (Header -> ByteString) -> Header -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Header -> ByteString
forall a b. (a, b) -> b
snd)
(RequestHeaders -> [Text]) -> RequestHeaders -> [Text]
forall a b. (a -> b) -> a -> b
$ (Header -> Bool) -> RequestHeaders -> RequestHeaders
forall a. (a -> Bool) -> [a] -> [a]
filter (\Header
h -> Header -> HeaderName
forall a b. (a, b) -> a
fst Header
h HeaderName -> HeaderName -> Bool
forall a. Eq a => a -> a -> Bool
== HeaderName
NH.hAcceptEncoding) RequestHeaders
reqHeaders
splitHeaderVal :: ByteString -> [Text]
splitHeaderVal ByteString
bs = (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
T.strip ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
"," (Text -> [Text]) -> Text -> [Text]
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
bsToTxt ByteString
bs
identityRejected :: Bool
identityRejected =
Text
"identity;q=0"
Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
rawHeaderVals
Bool -> Bool -> Bool
||
( Text
"*;q=0"
Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
rawHeaderVals
Bool -> Bool -> Bool
&& Bool -> Bool
not ((Text -> Bool) -> [Text] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text
"identity" Text -> Text -> Bool
`T.isPrefixOf`) [Text]
rawHeaderVals)
)
gzipAccepted :: Bool
gzipAccepted =
(Text -> Bool) -> [Text] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text
"gzip" Text -> Text -> Bool
`T.isPrefixOf`) [Text]
rawHeaderVals
Bool -> Bool -> Bool
&& (Text
"gzip;q=0" Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Text]
rawHeaderVals)
anyEncodingTechnicallyAcceptable :: Bool
anyEncodingTechnicallyAcceptable =
[Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
rawHeaderVals Bool -> Bool -> Bool
|| [Text]
rawHeaderVals [Text] -> [Text] -> Bool
forall a. Eq a => a -> a -> Bool
== [Text
"*"]
acceptedEncodingTypes :: [EncodingType]
acceptedEncodingTypes
| Bool
anyEncodingTechnicallyAcceptable = [EncodingType
identityEncoding]
| Bool
otherwise =
(Bool -> [()]
forall (f :: * -> *). Alternative f => Bool -> f ()
guard Bool
gzipAccepted [()] -> EncodingType -> [EncodingType]
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> CompressionType -> EncodingType
forall a. a -> Maybe a
Just CompressionType
CTGZip)
[EncodingType] -> [EncodingType] -> [EncodingType]
forall a. Semigroup a => a -> a -> a
<> (Bool -> [()]
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Bool
not Bool
identityRejected) [()] -> EncodingType -> [EncodingType]
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> EncodingType
identityEncoding)