packages feed

wai-session-redis (empty) → 0.1.0.0

raw patch · 6 files changed

+342/−0 lines, 6 filesdep +basedep +bytestringdep +cereal

Dependencies added: base, bytestring, cereal, data-default, hedis, hspec, http-types, vault, wai, wai-session, wai-session-redis, warp

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright t4ccer++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of t4ccer nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,12 @@+# wai-session-redis+Provides Redis based session store for [Network.Wai.Session](https://hackage.haskell.org/package/wai-session)  +For example usage view [example/Main.hs](https://github.com/t4ccer/wai-session-redis/tree/main/example/Main.hs)++## Tests+To run tests `wai-session-redis` must have access to running `redis` instance.+### Using docker+```bash+docker run --name redis-session-tests -p 6379:6379 -d redis+stack test+docker rm -f redis-session-tests+```
+ example/Main.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import qualified Data.ByteString.Char8     as SBS+import           Data.Default+import           Data.String               (fromString)+import qualified Data.Vault.Lazy           as Vault+import           Network.HTTP.Types+import           Network.Wai+import           Network.Wai.Handler.Warp+import           Network.Wai.Session       (Session, withSession)++import           Network.Wai.Session.Redis++app :: Vault.Key (Session IO String String) -> Application+app session env = (>>=) $ do+  u <- sessionLookup "u"+  sessionInsert "u" insertThis+  return $ responseLBS ok200 [] $ maybe (fromString "Nothing") fromString u+    where+      insertThis = show $ pathInfo env+      Just (sessionLookup, sessionInsert) = Vault.lookup session (vault env)++main :: IO ()+main = do+  let s = def+  session <- Vault.newKey+  store <- dbStore s+  run 1337 $ withSession store (fromString "SESSION") def session $ app session+
+ src/Network/Wai/Session/Redis.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE BlockArguments    #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}++-- |+-- Module: Network.Wai.Session.Redis+-- Copyright: (c) 2021, t4ccer+-- License: BSD3+-- Stability: experimental+-- Portability: portable+--+-- Simple Redis backed wai-session backend. This module allows you to store+-- session data of wai-sessions in a Redis database.+module Network.Wai.Session.Redis+  ( dbStore+  , clearSession+  , SessionSettings(..)+  ) where++import           Control.Monad+import           Control.Monad.IO.Class+import           Data.ByteString        (ByteString)+import           Data.Default+import           Data.Either+import           Data.Serialize         (Serialize, decode, encode)+import           Database.Redis         hiding (decode)+import           Network.Wai.Session++-- | Settings to control session store+data SessionSettings = SessionSettings+  { redisConnectionInfo :: ConnectInfo+  , expiratinTime       :: Integer+  -- ^ Session expiration time in seconds+  }++instance Default SessionSettings where+  def = SessionSettings+    { redisConnectionInfo = defaultConnectInfo+    , expiratinTime       = 60*60*24*7 -- One week+    }++eitherToMaybe :: Either a b -> Maybe b+eitherToMaybe (Left _)  = Nothing+eitherToMaybe (Right a) = Just a++connectAndRunRedis :: MonadIO m => ConnectInfo -> Redis b -> m b+connectAndRunRedis ci cmd = liftIO do+  conn <- connect ci+  res  <- runRedis conn cmd+  disconnect conn+  return res++createSession :: MonadIO m => SessionSettings -> m ByteString+createSession SessionSettings{..} = liftIO do+  sesId <- genSessionId+  connectAndRunRedis redisConnectionInfo $ do+    hset sesId "" ""+    expire sesId expiratinTime+  return sesId++isSesIdValid :: MonadIO m => SessionSettings -> ByteString -> m Bool+isSesIdValid SessionSettings{..} sesId = liftIO do+  res <- connectAndRunRedis redisConnectionInfo $ do+    exists sesId+  return $ fromRight False res++insertIntoSession :: MonadIO m => SessionSettings+  -> ByteString -- ^ Sessionn id+  -> ByteString -- ^ Key+  -> ByteString -- ^ Value+  -> m ()+insertIntoSession SessionSettings{..} sesId key value = do+  connectAndRunRedis redisConnectionInfo $ do+    hset sesId key value+    expire sesId expiratinTime+  return ()++lookupFromSession :: MonadIO m => SessionSettings+  -> ByteString -- ^ Session id+  -> ByteString -- ^ Key+  -> m (Maybe ByteString)+lookupFromSession SessionSettings{..} sesId key = do+  v <- connectAndRunRedis redisConnectionInfo $ do+    v <- hget sesId key+    expire sesId expiratinTime+    return v+  return $ join $ eitherToMaybe v++-- | Invalidate session id+clearSession :: MonadIO m => SessionSettings+  -> ByteString -- ^ Session id+  -> m ()+clearSession SessionSettings{..} sessionId = do+  connectAndRunRedis redisConnectionInfo $ do+    del [sessionId]+  return ()++-- | Create new redis backend wai session store+dbStore :: (MonadIO m1, MonadIO m2, Eq k, Serialize k, Serialize v) => SessionSettings -> m2 (SessionStore m1 k v)+dbStore s = do+  return $ dbStore' s++dbStore' :: (MonadIO m1, MonadIO m2, Eq k, Serialize k, Serialize v, Monad m2) => SessionSettings -> Maybe ByteString -> m2 (Session m1 k v, m2 ByteString)+dbStore' s (Just sesId) = do+  isValid <- isSesIdValid s sesId+  if isValid+    then return (mkSessionFromSesId s sesId, return sesId)+    else dbStore' s Nothing+dbStore' s Nothing = do+  sesId <- createSession s+  return (mkSessionFromSesId s sesId, return sesId)++mkSessionFromSesId :: (MonadIO m1, Eq k, Serialize k, Serialize v) => SessionSettings -> ByteString -> Session m1 k v+mkSessionFromSesId s sesId = (mkLookup, mkInsert)+  where+    mkLookup k = liftIO $ fmap (join . fmap (eitherToMaybe . decode)) $ lookupFromSession s sesId (encode k)+    mkInsert k v = liftIO $ insertIntoSession s sesId (encode k) (encode v)+
+ test/Spec.hs view
@@ -0,0 +1,64 @@+-- Adapted from https://github.com/hce/postgresql-session/blob/master/test/Spec.hs++{-# LANGUAGE OverloadedStrings #-}+module Main where++import           Control.Concurrent+import qualified Data.ByteString           as B+import           Data.Default+import           Test.Hspec++import           Network.Wai.Session.Redis++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Network.Wai.Session.Redis" $ it "handles sessions" $ do+    store <- dbStore testSettings++    -- new session+    ((lookupSess1, insertSess1), mknewsessid) <- store Nothing+    sessid <- mknewsessid++    -- insert+    insertSess1 ("foo" :: B.ByteString) ("foo" :: B.ByteString)+    lookupSess1 "foo" `shouldReturn` Just "foo"++    -- update+    insertSess1 ("foo" :: B.ByteString) ("bar" :: B.ByteString)+    lookupSess1 "foo" `shouldReturn` Just "bar"++    -- non-existing key+    lookupSess1 "bar" `shouldReturn` Nothing++    -- existing session+    ((lookupSess2, insertSess2), mknewsessid) <- store $ Just sessid+    newsessid <- mknewsessid++    lookupSess2 "foo" `shouldReturn` Just "bar"++    newsessid `shouldBe` sessid++    -- invalid session+    let invalidsessid = "foobar"+    ((lookupSess3, insertSess3), mknewsessid) <- store $ Just invalidsessid+    newsessid2 <- mknewsessid++    newsessid2 `shouldNotBe` newsessid+    newsessid2 `shouldNotBe` invalidsessid++    lookupSess3 "foo" `shouldReturn` Nothing++    -- re-accessing session+    ((lookupSess4, insertSess4), mknewsessid) <- store $ Just sessid+    lookupSess4 "foo" `shouldReturn` Just "bar"++    -- purged session+    threadDelay 2000000+    ((lookupSess5, insertSess5), mknewsessid) <- store $ Just sessid+    lookupSess5 "foo" `shouldReturn` Nothing++testSettings :: SessionSettings+testSettings = def {expiratinTime = 1}+
+ wai-session-redis.cabal view
@@ -0,0 +1,87 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 95001b72a04fd4250644f116fc3194bf3d3428b8a8078183501dba42e3ca9b8d++name:           wai-session-redis+version:        0.1.0.0+synopsis:       Simple Redis backed wai-session backend.+description:    Simple Redis backed wai-session backend. This module allows you to store session data of wai-sessions in a Redis database.+category:       Web+homepage:       https://github.com/t4ccer/wai-session-redis#readme+bug-reports:    https://github.com/t4ccer/wai-session-redis/issues+author:         t4ccer+maintainer:     t4ccer@gmail.com+copyright:      (c) 2021 t4ccer+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://github.com/t4ccer/wai-session-redis++library+  exposed-modules:+      Network.Wai.Session.Redis+  other-modules:+      Paths_wai_session_redis+  hs-source-dirs:+      src+  build-depends:+      base >=4.10.0 && <5+    , bytestring >=0.10 && <0.11+    , cereal >=0.5.8 && <0.5.9+    , data-default >=0.7 && <0.8+    , hedis >=0.14 && <0.15+    , vault >=0.3.1 && <0.3.2+    , wai >=3.2 && <3.3+    , wai-session >=0.3.3 && <0.3.4+  default-language: Haskell2010++executable wai-session-redis-example-exe+  main-is: Main.hs+  other-modules:+      Paths_wai_session_redis+  hs-source-dirs:+      example+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.10.0 && <5+    , bytestring >=0.10 && <0.11+    , cereal >=0.5.8 && <0.5.9+    , data-default >=0.7 && <0.8+    , hedis >=0.14 && <0.15+    , http-types <0.13+    , vault >=0.3.1 && <0.3.2+    , wai >=3.2 && <3.3+    , wai-session >=0.3.3 && <0.3.4+    , wai-session-redis+    , warp <3.3.15+  default-language: Haskell2010++test-suite wai-session-redis-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_wai_session_redis+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.10.0 && <5+    , bytestring >=0.10 && <0.11+    , cereal >=0.5.8 && <0.5.9+    , data-default >=0.7 && <0.8+    , hedis >=0.14 && <0.15+    , hspec <2.8+    , vault >=0.3.1 && <0.3.2+    , wai >=3.2 && <3.3+    , wai-session >=0.3.3 && <0.3.4+    , wai-session-redis+  default-language: Haskell2010