module Network.Wai.Middleware.Gzip
(
gzip
, GzipSettings
, gzipFiles
, gzipCheckMime
, gzipSizeThreshold
, GzipFiles (..)
, defaultCheckMime
, def
) where
import Control.Exception (IOException, SomeException, fromException, throwIO, try)
import Control.Monad (unless)
import qualified Data.ByteString as S
import Data.ByteString.Builder (byteString)
import qualified Data.ByteString.Builder.Extra as Blaze (flush)
import qualified Data.ByteString.Char8 as S8
import Data.ByteString.Lazy.Internal (defaultChunkSize)
import Data.Default.Class (Default (..))
import Data.Function (fix)
import Data.Maybe (isJust)
import qualified Data.Set as Set
import qualified Data.Streaming.ByteString.Builder as B
import qualified Data.Streaming.Zlib as Z
import Data.Word8 as W8 (toLower, _semicolon)
import Network.HTTP.Types (
Header,
Status (statusCode),
hContentEncoding,
hContentLength,
hContentType,
hUserAgent,
)
import Network.HTTP.Types.Header (hAcceptEncoding, hETag, hVary)
import Network.Wai
import Network.Wai.Internal (Response (..))
import System.Directory (createDirectoryIfMissing, doesFileExist)
import qualified System.IO as IO
import Network.Wai.Header (contentLength, parseQValueList, replaceHeader)
import Network.Wai.Util (splitCommas, trimWS)
data GzipSettings = GzipSettings
{
GzipSettings -> GzipFiles
gzipFiles :: GzipFiles
, GzipSettings -> ByteString -> Bool
gzipCheckMime :: S.ByteString -> Bool
, GzipSettings -> Integer
gzipSizeThreshold :: Integer
}
data GzipFiles
=
GzipIgnore
|
GzipCompress
|
GzipCacheFolder FilePath
|
GzipCacheETag FilePath
|
GzipPreCompressed GzipFiles
deriving (Int -> GzipFiles -> ShowS
[GzipFiles] -> ShowS
GzipFiles -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [GzipFiles] -> ShowS
$cshowList :: [GzipFiles] -> ShowS
show :: GzipFiles -> String
$cshow :: GzipFiles -> String
showsPrec :: Int -> GzipFiles -> ShowS
$cshowsPrec :: Int -> GzipFiles -> ShowS
Show, GzipFiles -> GzipFiles -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: GzipFiles -> GzipFiles -> Bool
$c/= :: GzipFiles -> GzipFiles -> Bool
== :: GzipFiles -> GzipFiles -> Bool
$c== :: GzipFiles -> GzipFiles -> Bool
Eq, ReadPrec [GzipFiles]
ReadPrec GzipFiles
Int -> ReadS GzipFiles
ReadS [GzipFiles]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [GzipFiles]
$creadListPrec :: ReadPrec [GzipFiles]
readPrec :: ReadPrec GzipFiles
$creadPrec :: ReadPrec GzipFiles
readList :: ReadS [GzipFiles]
$creadList :: ReadS [GzipFiles]
readsPrec :: Int -> ReadS GzipFiles
$creadsPrec :: Int -> ReadS GzipFiles
Read)
instance Default GzipSettings where
def :: GzipSettings
def = GzipFiles -> (ByteString -> Bool) -> Integer -> GzipSettings
GzipSettings GzipFiles
GzipIgnore ByteString -> Bool
defaultCheckMime Integer
minimumLength
defaultCheckMime :: S.ByteString -> Bool
defaultCheckMime :: ByteString -> Bool
defaultCheckMime ByteString
bs =
ByteString -> ByteString -> Bool
S8.isPrefixOf ByteString
"text/" ByteString
bs Bool -> Bool -> Bool
|| ByteString
bs' forall a. Ord a => a -> Set a -> Bool
`Set.member` Set ByteString
toCompress
where
bs' :: ByteString
bs' = forall a b. (a, b) -> a
fst forall a b. (a -> b) -> a -> b
$ (Word8 -> Bool) -> ByteString -> (ByteString, ByteString)
S.break (forall a. Eq a => a -> a -> Bool
== Word8
_semicolon) ByteString
bs
toCompress :: Set ByteString
toCompress = forall a. Ord a => [a] -> Set a
Set.fromList
[ ByteString
"application/json"
, ByteString
"application/javascript"
, ByteString
"application/ecmascript"
, ByteString
"image/x-icon"
]
gzip :: GzipSettings -> Middleware
gzip :: GzipSettings -> Middleware
gzip GzipSettings
set Application
app Request
req Response -> IO ResponseReceived
sendResponse'
| Bool
skipCompress = Application
app Request
req Response -> IO ResponseReceived
sendResponse
| Bool
otherwise = Application
app Request
req forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Response -> IO ResponseReceived)
-> Response -> IO ResponseReceived
checkCompress forall a b. (a -> b) -> a -> b
$ \Response
res ->
let runAction :: (Response, GzipFiles) -> IO ResponseReceived
runAction (Response, GzipFiles)
x = case (Response, GzipFiles)
x of
(ResponseRaw{}, GzipFiles
_) -> Response -> IO ResponseReceived
sendResponse Response
res
(ResponseFile {}, GzipFiles
GzipIgnore) -> Response -> IO ResponseReceived
sendResponse Response
res
(ResponseFile Status
s ResponseHeaders
hs String
file Maybe FilePart
Nothing, GzipPreCompressed GzipFiles
nextAction) ->
let compressedVersion :: String
compressedVersion = String
file forall a. [a] -> [a] -> [a]
++ String
".gz"
in String -> IO Bool
doesFileExist String
compressedVersion forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Bool
y ->
if Bool
y
then Response -> IO ResponseReceived
sendResponse forall a b. (a -> b) -> a -> b
$ Status -> ResponseHeaders -> String -> Maybe FilePart -> Response
ResponseFile Status
s (ResponseHeaders -> ResponseHeaders
fixHeaders ResponseHeaders
hs) String
compressedVersion forall a. Maybe a
Nothing
else (Response, GzipFiles) -> IO ResponseReceived
runAction (Status -> ResponseHeaders -> String -> Maybe FilePart -> Response
ResponseFile Status
s ResponseHeaders
hs String
file forall a. Maybe a
Nothing, GzipFiles
nextAction)
(Response, GzipFiles)
_ | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ ResponseHeaders -> Bool
isCorrectMime (Response -> ResponseHeaders
responseHeaders Response
res) -> Response -> IO ResponseReceived
sendResponse Response
res
(ResponseFile Status
s ResponseHeaders
hs String
file Maybe FilePart
Nothing, GzipCacheFolder String
cache) ->
forall a.
Status
-> ResponseHeaders
-> String
-> Maybe ByteString
-> String
-> (Response -> IO a)
-> IO a
compressFile Status
s ResponseHeaders
hs String
file forall a. Maybe a
Nothing String
cache Response -> IO ResponseReceived
sendResponse
(ResponseFile Status
s ResponseHeaders
hs String
file Maybe FilePart
Nothing, GzipCacheETag String
cache) ->
let mETag :: Maybe ByteString
mETag = forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
hETag ResponseHeaders
hs
in forall a.
Status
-> ResponseHeaders
-> String
-> Maybe ByteString
-> String
-> (Response -> IO a)
-> IO a
compressFile Status
s ResponseHeaders
hs String
file Maybe ByteString
mETag String
cache Response -> IO ResponseReceived
sendResponse
(Response, GzipFiles)
_ -> Response
-> (Response -> IO ResponseReceived) -> IO ResponseReceived
compressE Response
res Response -> IO ResponseReceived
sendResponse
in (Response, GzipFiles) -> IO ResponseReceived
runAction (Response
res, GzipSettings -> GzipFiles
gzipFiles GzipSettings
set)
where
isCorrectMime :: ResponseHeaders -> Bool
isCorrectMime =
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (GzipSettings -> ByteString -> Bool
gzipCheckMime GzipSettings
set) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
hContentType
sendResponse :: Response -> IO ResponseReceived
sendResponse = Response -> IO ResponseReceived
sendResponse' forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ResponseHeaders -> ResponseHeaders) -> Response -> Response
mapResponseHeaders ResponseHeaders -> ResponseHeaders
mAddVary
acceptEncoding :: ByteString
acceptEncoding = ByteString
"Accept-Encoding"
acceptEncodingLC :: ByteString
acceptEncodingLC = ByteString
"accept-encoding"
mAddVary :: ResponseHeaders -> ResponseHeaders
mAddVary [] = [(HeaderName
hVary, ByteString
acceptEncoding)]
mAddVary (h :: (HeaderName, ByteString)
h@(HeaderName
nm, ByteString
val) : ResponseHeaders
hs)
| HeaderName
nm forall a. Eq a => a -> a -> Bool
== HeaderName
hVary =
let vals :: [ByteString]
vals = ByteString -> [ByteString]
splitCommas ByteString
val
lowercase :: ByteString -> ByteString
lowercase = (Word8 -> Word8) -> ByteString -> ByteString
S.map Word8 -> Word8
W8.toLower
hasAccEnc :: Bool
hasAccEnc = ByteString
acceptEncodingLC forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ByteString -> ByteString
lowercase [ByteString]
vals
newH :: (HeaderName, ByteString)
newH | Bool
hasAccEnc = (HeaderName, ByteString)
h
| Bool
otherwise = (HeaderName
hVary, ByteString
acceptEncoding forall a. Semigroup a => a -> a -> a
<> ByteString
", " forall a. Semigroup a => a -> a -> a
<> ByteString
val)
in (HeaderName, ByteString)
newH forall a. a -> [a] -> [a]
: ResponseHeaders
hs
| Bool
otherwise = (HeaderName, ByteString)
h forall a. a -> [a] -> [a]
: ResponseHeaders -> ResponseHeaders
mAddVary ResponseHeaders
hs
skipCompress :: Bool
skipCompress =
Bool -> Bool
not Bool
acceptsGZipEncoding Bool -> Bool -> Bool
|| Bool
isMSIE6
where
reqHdrs :: ResponseHeaders
reqHdrs = Request -> ResponseHeaders
requestHeaders Request
req
acceptsGZipEncoding :: Bool
acceptsGZipEncoding =
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any forall {a} {a}.
(IsString a, Eq a, Eq a, Num a) =>
(a, Maybe a) -> Bool
isGzip forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [(ByteString, Maybe Int)]
parseQValueList) forall a b. (a -> b) -> a -> b
$ HeaderName
hAcceptEncoding forall a b. Eq a => a -> [(a, b)] -> Maybe b
`lookup` ResponseHeaders
reqHdrs
isGzip :: (a, Maybe a) -> Bool
isGzip (a
bs, Maybe a
q) =
a
bs forall a. Eq a => a -> a -> Bool
== a
"gzip" Bool -> Bool -> Bool
&& forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (forall a. Eq a => a -> a -> Bool
/= a
0) Maybe a
q
isMSIE6 :: Bool
isMSIE6 =
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (ByteString
"MSIE 6" ByteString -> ByteString -> Bool
`S.isInfixOf`) forall a b. (a -> b) -> a -> b
$ HeaderName
hUserAgent forall a b. Eq a => a -> [(a, b)] -> Maybe b
`lookup` ResponseHeaders
reqHdrs
checkCompress :: (Response -> IO ResponseReceived) -> Response -> IO ResponseReceived
checkCompress :: (Response -> IO ResponseReceived)
-> Response -> IO ResponseReceived
checkCompress Response -> IO ResponseReceived
continue Response
res =
if Bool
isEncodedAlready Bool -> Bool -> Bool
|| Bool
isPartial Bool -> Bool -> Bool
|| Bool
tooSmall
then Response -> IO ResponseReceived
sendResponse Response
res
else Response -> IO ResponseReceived
continue Response
res
where
resHdrs :: ResponseHeaders
resHdrs = Response -> ResponseHeaders
responseHeaders Response
res
isPartial :: Bool
isPartial = Status -> Int
statusCode (Response -> Status
responseStatus Response
res) forall a. Eq a => a -> a -> Bool
== Int
206
isEncodedAlready :: Bool
isEncodedAlready = forall a. Maybe a -> Bool
isJust forall a b. (a -> b) -> a -> b
$ HeaderName
hContentEncoding forall a b. Eq a => a -> [(a, b)] -> Maybe b
`lookup` ResponseHeaders
resHdrs
tooSmall :: Bool
tooSmall =
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
Bool
False
(forall a. Ord a => a -> a -> Bool
< GzipSettings -> Integer
gzipSizeThreshold GzipSettings
set)
forall a b. (a -> b) -> a -> b
$ ResponseHeaders -> Maybe Integer
contentLength ResponseHeaders
resHdrs
minimumLength :: Integer
minimumLength :: Integer
minimumLength = Integer
860
compressFile :: Status -> [Header] -> FilePath -> Maybe S.ByteString -> FilePath -> (Response -> IO a) -> IO a
compressFile :: forall a.
Status
-> ResponseHeaders
-> String
-> Maybe ByteString
-> String
-> (Response -> IO a)
-> IO a
compressFile Status
s ResponseHeaders
hs String
file Maybe ByteString
mETag String
cache Response -> IO a
sendResponse = do
Bool
e <- String -> IO Bool
doesFileExist String
tmpfile
if Bool
e
then IO a
onSucc
else do
Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
cache
Either SomeException ()
x <- forall e a. Exception e => IO a -> IO (Either e a)
try forall a b. (a -> b) -> a -> b
$
forall r. String -> IOMode -> (Handle -> IO r) -> IO r
IO.withBinaryFile String
file IOMode
IO.ReadMode forall a b. (a -> b) -> a -> b
$ \Handle
inH ->
forall r. String -> IOMode -> (Handle -> IO r) -> IO r
IO.withBinaryFile String
tmpfile IOMode
IO.WriteMode forall a b. (a -> b) -> a -> b
$ \Handle
outH -> do
Deflate
deflate <- Int -> WindowBits -> IO Deflate
Z.initDeflate Int
7 forall a b. (a -> b) -> a -> b
$ Int -> WindowBits
Z.WindowBits Int
31
let goPopper :: IO PopperRes -> IO ()
goPopper IO PopperRes
popper = forall a. (a -> a) -> a
fix forall a b. (a -> b) -> a -> b
$ \IO ()
loop -> do
PopperRes
res <- IO PopperRes
popper
case PopperRes
res of
PopperRes
Z.PRDone -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
Z.PRNext ByteString
bs -> do
Handle -> ByteString -> IO ()
S.hPut Handle
outH ByteString
bs
IO ()
loop
Z.PRError ZlibException
ex -> forall e a. Exception e => e -> IO a
throwIO ZlibException
ex
forall a. (a -> a) -> a
fix forall a b. (a -> b) -> a -> b
$ \IO ()
loop -> do
ByteString
bs <- Handle -> Int -> IO ByteString
S.hGetSome Handle
inH Int
defaultChunkSize
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (ByteString -> Bool
S.null ByteString
bs) forall a b. (a -> b) -> a -> b
$ do
Deflate -> ByteString -> IO (IO PopperRes)
Z.feedDeflate Deflate
deflate ByteString
bs forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO PopperRes -> IO ()
goPopper
IO ()
loop
IO PopperRes -> IO ()
goPopper forall a b. (a -> b) -> a -> b
$ Deflate -> IO PopperRes
Z.finishDeflate Deflate
deflate
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either SomeException -> IO a
onErr (forall a b. a -> b -> a
const IO a
onSucc) (Either SomeException ()
x :: Either SomeException ())
where
onSucc :: IO a
onSucc = Response -> IO a
sendResponse forall a b. (a -> b) -> a -> b
$ Status -> ResponseHeaders -> String -> Maybe FilePart -> Response
responseFile Status
s (ResponseHeaders -> ResponseHeaders
fixHeaders ResponseHeaders
hs) String
tmpfile forall a. Maybe a
Nothing
reportError :: String -> IO ()
reportError String
err =
Handle -> String -> IO ()
IO.hPutStrLn Handle
IO.stderr forall a b. (a -> b) -> a -> b
$
String
"Network.Wai.Middleware.Gzip: compression failed: " forall a. Semigroup a => a -> a -> a
<> String
err
onErr :: SomeException -> IO a
onErr SomeException
e
| Just IOException
ioe <- forall e. Exception e => SomeException -> Maybe e
fromException SomeException
e = do
String -> IO ()
reportError forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> String
show (IOException
ioe :: IOException)
Response -> IO a
sendResponse forall a b. (a -> b) -> a -> b
$ Status -> ResponseHeaders -> String -> Maybe FilePart -> Response
responseFile Status
s ResponseHeaders
hs String
file forall a. Maybe a
Nothing
| Just ZlibException
zlibe <- forall e. Exception e => SomeException -> Maybe e
fromException SomeException
e = do
String -> IO ()
reportError forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> String
show (ZlibException
zlibe :: Z.ZlibException)
Response -> IO a
sendResponse forall a b. (a -> b) -> a -> b
$ Status -> ResponseHeaders -> String -> Maybe FilePart -> Response
responseFile Status
s ResponseHeaders
hs String
file forall a. Maybe a
Nothing
| Bool
otherwise = forall e a. Exception e => e -> IO a
throwIO SomeException
e
eTag :: String
eTag = forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" (forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
safe forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> String
S8.unpack forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
trimWS) Maybe ByteString
mETag
tmpfile :: String
tmpfile = String
cache forall a. [a] -> [a] -> [a]
++ Char
'/' forall a. a -> [a] -> [a]
: forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
safe String
file forall a. [a] -> [a] -> [a]
++ String
eTag
safe :: Char -> Char
safe Char
c
| Char
'A' forall a. Ord a => a -> a -> Bool
<= Char
c Bool -> Bool -> Bool
&& Char
c forall a. Ord a => a -> a -> Bool
<= Char
'Z' = Char
c
| Char
'a' forall a. Ord a => a -> a -> Bool
<= Char
c Bool -> Bool -> Bool
&& Char
c forall a. Ord a => a -> a -> Bool
<= Char
'z' = Char
c
| Char
'0' forall a. Ord a => a -> a -> Bool
<= Char
c Bool -> Bool -> Bool
&& Char
c forall a. Ord a => a -> a -> Bool
<= Char
'9' = Char
c
safe Char
'-' = Char
'-'
safe Char
'_' = Char
'_'
safe Char
_ = Char
'_'
compressE :: Response
-> (Response -> IO ResponseReceived)
-> IO ResponseReceived
compressE :: Response
-> (Response -> IO ResponseReceived) -> IO ResponseReceived
compressE Response
res Response -> IO ResponseReceived
sendResponse =
forall {a}. (StreamingBody -> IO a) -> IO a
wb forall a b. (a -> b) -> a -> b
$ \StreamingBody
body -> Response -> IO ResponseReceived
sendResponse forall a b. (a -> b) -> a -> b
$
Status -> ResponseHeaders -> StreamingBody -> Response
responseStream Status
s (ResponseHeaders -> ResponseHeaders
fixHeaders ResponseHeaders
hs) forall a b. (a -> b) -> a -> b
$ \Builder -> IO ()
sendChunk IO ()
flush -> do
(BuilderRecv
blazeRecv, BuilderFinish
_) <- BufferAllocStrategy -> IO (BuilderRecv, BuilderFinish)
B.newBuilderRecv BufferAllocStrategy
B.defaultStrategy
Deflate
deflate <- Int -> WindowBits -> IO Deflate
Z.initDeflate Int
1 (Int -> WindowBits
Z.WindowBits Int
31)
let sendBuilder :: Builder -> IO ()
sendBuilder Builder
builder = do
IO ByteString
popper <- BuilderRecv
blazeRecv Builder
builder
forall a. (a -> a) -> a
fix forall a b. (a -> b) -> a -> b
$ \IO ()
loop -> do
ByteString
bs <- IO ByteString
popper
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (ByteString -> Bool
S.null ByteString
bs) forall a b. (a -> b) -> a -> b
$ do
ByteString -> IO ()
sendBS ByteString
bs
IO ()
loop
sendBS :: ByteString -> IO ()
sendBS ByteString
bs = Deflate -> ByteString -> IO (IO PopperRes)
Z.feedDeflate Deflate
deflate ByteString
bs forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO PopperRes -> IO ()
deflatePopper
flushBuilder :: IO ()
flushBuilder = do
Builder -> IO ()
sendBuilder Builder
Blaze.flush
IO PopperRes -> IO ()
deflatePopper forall a b. (a -> b) -> a -> b
$ Deflate -> IO PopperRes
Z.flushDeflate Deflate
deflate
IO ()
flush
deflatePopper :: IO PopperRes -> IO ()
deflatePopper IO PopperRes
popper = forall a. (a -> a) -> a
fix forall a b. (a -> b) -> a -> b
$ \IO ()
loop -> do
PopperRes
result <- IO PopperRes
popper
case PopperRes
result of
PopperRes
Z.PRDone -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
Z.PRNext ByteString
bs' -> do
Builder -> IO ()
sendChunk forall a b. (a -> b) -> a -> b
$ ByteString -> Builder
byteString ByteString
bs'
IO ()
loop
Z.PRError ZlibException
e -> forall e a. Exception e => e -> IO a
throwIO ZlibException
e
StreamingBody
body Builder -> IO ()
sendBuilder IO ()
flushBuilder
Builder -> IO ()
sendBuilder Builder
Blaze.flush
IO PopperRes -> IO ()
deflatePopper forall a b. (a -> b) -> a -> b
$ Deflate -> IO PopperRes
Z.finishDeflate Deflate
deflate
where
(Status
s, ResponseHeaders
hs, (StreamingBody -> IO a) -> IO a
wb) = forall a.
Response
-> (Status, ResponseHeaders, (StreamingBody -> IO a) -> IO a)
responseToStream Response
res
fixHeaders :: [Header] -> [Header]
=
HeaderName -> ByteString -> ResponseHeaders -> ResponseHeaders
replaceHeader HeaderName
hContentEncoding ByteString
"gzip" forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter forall {b}. (HeaderName, b) -> Bool
notLength
where
notLength :: (HeaderName, b) -> Bool
notLength (HeaderName
x, b
_) = HeaderName
x forall a. Eq a => a -> a -> Bool
/= HeaderName
hContentLength