packages feed

webapp-0.3.5: Web/App/Middleware/Gzip.hs

{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
{-|
Module      : Web.App.Middleware.Gzip
Copyright   : (c) Nathaniel Symer, 2015
License     : MIT
Maintainer  : nate@symer.io
Stability   : experimental
Portability : Cross-Platform

WAI middleware to GZIP HTTP responses.
-}

module Web.App.Middleware.Gzip
(
  gzip
)
where

import Network.Wai
import Network.Wai.Internal
import Data.Maybe (fromMaybe)
import qualified Codec.Compression.GZip as GZIP (compress)
import qualified Data.ByteString.Char8 as B (readInt,isInfixOf)
import Blaze.ByteString.Builder (toLazyByteString)
import Blaze.ByteString.Builder.ByteString (fromLazyByteString)

-- | Creates a 'Middleware' that GZIPs HTTP responses
gzip :: Int -- ^ Minimum response length that's GZIP'd
     -> Middleware
gzip minLen app env sendResponse = app env f
  where
    f res@ResponseRaw{} = sendResponse res
    f res
      | isCompressable = sendCompressed res sendResponse
      | otherwise = sendResponse res
      where
        isCompressable = isAccepted && not isIE6 && isBigEnough && not isEncoded
        isAccepted = headerContains "gzip" "Accept-Encoding" $ requestHeaders env
        isIE6 = headerContains "MSIE 6" "User-Agent" $ requestHeaders env
        isEncoded = headerContains "gzip" "Content-Encoding" $ responseHeaders res
        isBigEnough = maybe True ((<=) minLen) contentLength
        contentLength = getResHeader "Content-Length" >>= fmap fst . B.readInt
        getResHeader k = lookup k $ responseHeaders res
        headerContains s k hdrs = fromMaybe False $ B.isInfixOf s <$> lookup k hdrs

sendCompressed :: Response -> (Response -> IO a) -> IO a
sendCompressed res f = maybe (f res) (const send) $ lookup "Content-Type" hs
  where
    (s,hs,wb) = responseToStream res
    invariants = [("Vary","Accept-Encoding"),("Content-Encoding","gzip")]
    hs' = invariants ++ (filter ((/=) "Content-Length" . fst) hs)
    send = wb $ \b -> f $ responseStream s hs' (b . (. compress))
    compress = fromLazyByteString . GZIP.compress . toLazyByteString