wai-middleware-cache-redis 0.2.1 → 0.3.0
raw patch · 2 files changed
+69/−105 lines, 2 filesdep +binarydep −cerealdep −cryptohashdep −hexdep ~blaze-builder-conduitdep ~conduitdep ~hedis-pilePVP ok
version bump matches the API change (PVP)
Dependencies added: binary
Dependencies removed: cereal, cryptohash, hex, lifted-base
Dependency ranges changed: blaze-builder-conduit, conduit, hedis-pile, transformers, wai, wai-middleware-cache
API changes (from Hackage documentation)
- Network.Wai.Middleware.Cache.Redis: lookupETag :: Request -> Maybe ByteString
- Network.Wai.Middleware.Cache.Redis: redisBackend :: ConnectInfo -> Integer -> ByteString -> (Request -> Maybe Integer) -> (Request -> [ByteString]) -> (Request -> ByteString) -> (Request -> Maybe ByteString) -> (Request -> Bool) -> CacheBackend
+ Network.Wai.Middleware.Cache.Redis: redisBackend :: ConnectInfo -> Integer -> ByteString -> Maybe Integer -> [ByteString] -> (Request -> ByteString) -> (Request -> Maybe ByteString) -> CacheBackend
Files
src/Network/Wai/Middleware/Cache/Redis.hs view
@@ -1,15 +1,19 @@-{-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-} +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} -- | Redis backend for "Network.Wai.Middleware.Cache". -- --- This backend uses "Database.Redis.Pile" for low-lewel operations. +-- This backend uses "Database.Redis.Pile" for low-lewel operations. Main +-- drawback of this backend is the fact that whole response +-- will be consumed in memory. -- -- > cache -- > (redisBackend -- > -- use defaults, DB 0 and "myprefix" --- > R.defaultConnectInfo 0 "myprefix" --- > (const Nothing) -- no expiration --- > (const ["mytag"]) -- simply one tag "mytag" +-- > R.defaultConnectInfo 0 +-- > "myprefix" -- prefix for caching +-- > Nothing -- no expiration +-- > ["mytag"] -- one cache tag -- > (rawPathInfo) -- URL path as key -- > lookupETag -- And find "If-None-Match" -- > ) app -- our app @@ -17,127 +21,90 @@ module Network.Wai.Middleware.Cache.Redis ( -- * Cache backend redisBackend, - -- * Helpers - lookupETag ) where import Control.Monad (void) import Control.Monad.IO.Class (liftIO) import qualified Control.Arrow as A (first) -import qualified Data.ByteString as B -import qualified Data.ByteString.Char8 as BS8 +import Data.Maybe (fromMaybe) + +import Data.ByteString (ByteString) import Data.ByteString.Lazy (fromChunks) -import Data.CaseInsensitive (original, mk) -import qualified Data.Serialize as S +import Data.CaseInsensitive (original, mk, CI(..)) -import Data.Conduit (ResourceT, runResourceT, resourceThrow, ($$), ($=), - Flush(..)) +import Data.Conduit (ResourceT, runResourceT, ($$), ($=), Flush(..)) import qualified Data.Conduit.List as CL import Data.Conduit.Blaze (builderToByteStringFlush) -import qualified Crypto.Hash.SHA1 as SHA -import Data.Hex (hex) - import Network.Wai (Request(..), Response(..), responseSource, responseLBS) -import Network.Wai.Middleware.Cache (CacheBackend, CacheBackendError(..)) +import Network.Wai.Middleware.Cache (CacheBackend) import Network.HTTP.Types (Status(..)) import qualified Database.Redis as R import Database.Redis.Pile (pile) -import Control.Monad.Trans.Class (lift) -- | Redis backend for "Network.Wai.Middleware.Cache". --- --- This backend can't cache files. Use routing or catch 'CacheBackendError' redisBackend :: R.ConnectInfo -- ^ Redis connection info. -> Integer - -- ^ Redis DB. - -> B.ByteString + -- ^ Redis DB Index. + -> ByteString -- ^ Cache prefix for key and tags. -- See "Database.Redis.Pile" for details. - -> (Request -> Maybe Integer) - -- ^ TTL extraction. Use 'Nothing' for no expiration. - -> (Request -> [B.ByteString]) - -- ^ Tags extraction. - -- See "Database.Redis.Pile" for details. - -> (Request -> B.ByteString) - -- ^ Key extraction. - -> (Request -> Maybe B.ByteString) + -> Maybe Integer + -- ^ Cache TTL. Use 'Nothing' for no expiration. + -> [ByteString] + -- ^ Cache Tags. See "Database.Redis.Pile" for details. + -> (Request -> ByteString) + -- ^ Cache key extraction function. + -> (Request -> Maybe ByteString) -- ^ @ETag@ value extraction. To extract @If-None-Match@ header -- use 'lookupETag'. Use @(const Nothing)@ for block -- @304@-responses. - -> (Request -> Bool) - -- ^ @ETag@ creation flag. If it 'True', backend adds @ETag@ to - -- 'Response' headers with hexed @SHA1@ as value -> CacheBackend -redisBackend cInfo db cachePrefix - ttlFn tagsFn keyFn - eTagFn needEtagFn - app req = do - rawRes <- liftIO $ do +redisBackend cInfo db cachePrefix ttl tags keyFn eTagFn app req = do + rawRes <- liftIO $ do conn <- R.connect cInfo R.runRedis conn $ do void $ R.select db - pile cachePrefix key eTag (Just "response") $ - liftIO $ runResourceT $ do + pile cachePrefix key eTag $ liftIO . runResourceT $ do res <- app req - case res of - (ResponseFile _ _ fp part) -> lift $ - resourceThrow $ CacheBackendError $ BS8.pack $ - "Can't cache files : " ++ fp ++ ":" ++ show part - _ -> do - d <- parseResponse needEtag res - return (d, ttl, tags) - case buildResponse rawRes of - Left e -> lift $ resourceThrow $ CacheBackendError e - Right r -> return r + (h, d) <- buildCachedResponse res + return (d, h, tags, ttl) + parseCachedResponse rawRes where - (ttl, tags, key, needEtag) = (ttlFn req, tagsFn req, - keyFn req, needEtagFn req) - eTag = case eTagFn req of - Nothing -> Nothing - Just v -> Just ("header:ETag", v) - --- | Helper for extract @If-None-Match@ header from 'Request'. -lookupETag :: Request -> Maybe B.ByteString -lookupETag = lookup "If-None-Match" . requestHeaders + (key, eTag) = (keyFn req, eTagFn req) ---------------------------------------------------------------------------- -- Internal ---------------------------------------------------------------------------- -buildResponse :: Maybe [(B.ByteString, B.ByteString)] - -> Either B.ByteString (Maybe Response) -buildResponse Nothing = Right Nothing -buildResponse (Just raw) = decodeResp raw - where - decodeResp (("response", rawResp):[]) = case S.decode rawResp of - Left sm' -> Left $ BS8.pack sm' - Right (sc', sm', hs', bodyChunks) -> Right $ Just $ - responseLBS (Status sc' sm') - (map (A.first mk) hs') - $ fromChunks bodyChunks - decodeResp _ = Left "Data error" - -parseResponse :: Bool -> Response - -> ResourceT IO [(B.ByteString, B.ByteString)] -parseResponse needEtag res = do - bodyChunks <- b $= builderToByteStringFlush - $= CL.map fromChunk $$ CL.consume - let bodyHash = if needEtag - then hex . SHA.finalize . foldl SHA.update SHA.init $ bodyChunks - else "" - return [("response", - S.encode (sc, sm, map - (A.first original) (("ETag", bodyHash):hs), - bodyChunks)), - ("header:ETag", bodyHash)] +-- | Cached response +type CachedResponse = (Int, ByteString, + [(ByteString, ByteString)], [ByteString]) + +buildCachedResponse :: Response -> ResourceT IO (ByteString, CachedResponse) +buildCachedResponse res = do + bodyChunks <- bsrc $= builderToByteStringFlush + $= CL.map fromChunk $$ CL.consume + let bodyHash = fromMaybe "" $ lookup "etag" hs + let stripHeaders = map (A.first original) hs + return (bodyHash, (sc, sm, stripHeaders, bodyChunks)) where - (Status sc sm, hs, b) = responseSource res + (Status sc sm, hs, bsrc) = responseSource res fromChunk (Chunk a) = a fromChunk Flush = "" - + +-- | Parse cached response +parseCachedResponse :: Maybe CachedResponse -> ResourceT IO (Maybe Response) +parseCachedResponse Nothing = return Nothing +parseCachedResponse (Just (sc, sm, hs, bodyChunks)) = do + let wrappedHeaders = map (A.first mk) hs + return $ Just $ responseLBS (Status sc sm) + wrappedHeaders $ fromChunks bodyChunks + + +
wai-middleware-cache-redis.cabal view
@@ -1,5 +1,5 @@ name: wai-middleware-cache-redis -version: 0.2.1 +version: 0.3.0 cabal-version: >= 1.8 build-type: Simple stability: Experimental @@ -14,27 +14,24 @@ maintainer: Alexander Dorofeev <aka.spin@gmail.com> source-repository head - type: git - location: git://github.com/akaspin/wai-middleware-cache-redis.git + type: git + location: git://github.com/akaspin/wai-middleware-cache-redis.git library - hs-source-dirs: src - build-depends: - base >= 4 && < 5, - blaze-builder-conduit >= 0.2, + hs-source-dirs: src + build-depends: base >= 4 && < 5, + binary, + blaze-builder-conduit >= 0.4, bytestring >= 0.9 && < 0.10, case-insensitive >= 0.2, - cereal >= 0.3.1.0, - conduit >= 0.2 && < 0.3, - cryptohash, + conduit >= 0.4 && < 0.5, hedis >= 0.4 && < 0.5, - hedis-pile >= 0.4, - hex, + hedis-pile >= 0.5.1, http-types >= 0.6 && < 0.7, - transformers >= 0.2 && < 0.3, - wai >= 1.1, - wai-middleware-cache >= 0.2, - lifted-base - ghc-options: -Wall - exposed-modules: Network.Wai.Middleware.Cache.Redis + transformers >= 0.2 && < 0.4, + wai >= 1.2, + wai-middleware-cache >= 0.3 + ghc-options: -Wall + exposed-modules: Network.Wai.Middleware.Cache.Redis +