packages feed

snaplet-scoped-session 0.1.0.0 → 0.1.1

raw patch · 4 files changed

+156/−18 lines, 4 files

Files

+ README.md view
@@ -0,0 +1,84 @@+# Scoped sessions++This snaplet provides a convenient way to handle typed sessions in your snap application.++It uses lenses to separate your session state into smaller parts which can then be individually managed by nested snaplets.+The session state is therefore separated and subsnaplets have no access to the full global session state.++## Session management++Session management inside your application is done by a session `Snaplet`. The Snaplet is created using `initSessionSnaplet` and it expects a `Manager` as an argument.+Usually you won't have to create this manager yourself, simply find a library function which provides you with one that suits your needs and drop it in there.+As an example there is a in memory which you can create with the function `initMemoryManager` from `Snap.Snaplet.Session.Scoped.InMemory`. You can find more information on this particular manager in the section [The InMemory Manager](#the-inmemory-manager)++### The Manager typeclass++`Manager`s are responsible for persisting the session state in whichever way he sees fit. Depending on your choice of manager therefore your session can be kept in memory or a database or persistent in files on the server.+Furthermore the manager handles the connection to the client, which in most cases will be a session cookie.++Managers are parameterised by the state they can manage. It uses the TypeInType extension to define the internal state.+++For some Managers this may be any type++```Haskell+instance Manager (MyManager a) where+    type Manages (MyManager a) = a -- any state+```++or a constrained type++```Haskell+instance Serialize a => Manager (MySerializingManager a) where+    type Manages (MySerializingManager a) = a+```++or even a concrete type++```Haskell+instance Manager MyCountingManager where+    type Manages MyCountingManager = Int+```++#### The InMemory Manager++`Snap.Snaplet.Session.Scoped.InMemory` defines a `Manager` which stores arbitrary server side session state in the form of in-memory Haskell data. Session tracking is done using a configurable cookie and sessions are automatically expired to free memory by a concurrent worker which cleans the session state.++An example of the configuration can be found in `resources/devel.cfg`. All values are optional.+++## Wiring the Application++In order to give your snaplets access to the session manager you have to specify how the manager snaplet can be reached from inside your application.+This is done by implementing a typeclass called `HasManager`.++```Haskell+data MyApp = MyApp { _sessionSnaplet :: Snaplet MyManager, ... }++makeLenses ''MyApp++instance HasManager MyApp where+    type TheManager MyApp = MyManager+    toManager = subSnaplet sessionSnaplet+```++Then, for each subsnaplet which is to have access to a part of the state you have to implement `AccessSession` to define the substate a Snaplet has access to.++```Haskell+data Session = Session+    { _snapletA :: SnapletASession+    , _snapletB :: SnapletBSession+    }++makeLenses SessionState++instance AccessSession SnapletA where+    type GlobalSession SnapletA = SessionState+    type LocalSession SnapletA = SnapletASession+    accessSession = mkAccessSessionLens snapletA++instance AccessSession SnapletB where+    type GlobalSession SnapletB = SessionState+    type LocalSession SnapletB = SnapletBSession+    accessSession = mkAccessSessionLens snapletB+```
snaplet-scoped-session.cabal view
@@ -1,6 +1,6 @@ name:                snaplet-scoped-session-version:             0.1.0.0-synopsis:            Initial project template from stack+version:             0.1.1+synopsis:            Modularised session state for Snaplets, in a Snaplet description:         Please see README.md homepage:            https://github.com/JustusAdam/snaplet-scoped-session#readme license:             BSD3@@ -8,10 +8,11 @@ author:              JustusAdam maintainer:          dev@justus.science copyright:           Copyright: (c) 2016 Justus Adam-category:            Development+category:            Web, Snap build-type:          Simple-extra-source-files:  resources/devel.cfg+extra-source-files:  resources/devel.cfg, README.md cabal-version:       >=1.10+stability:           Experimental  library   hs-source-dirs:      src
src/Snap/Snaplet/Session/Scoped.hs view
@@ -1,3 +1,12 @@+{-|+Module      : $Header$+Description : Abstract Manager class and Session Snaplet+Copyright   : (c) Justus Adam, 2016+License     : BSD3+Maintainer  : dev@justus.science+Stability   : experimental+Portability : POSIX+-} {-# LANGUAGE ConstraintKinds     #-} {-# LANGUAGE ExplicitForAll      #-} {-# LANGUAGE FlexibleContexts    #-}@@ -25,21 +34,42 @@ -- optionally persistent -- -- It should be noted that although an implementation for 'managerCommit' and--- 'managerLoad' is not required it is useful for efficiancy reasons.+-- 'managerLoad' is not required it is useful for efficiency reasons. -- -- The state of the manager is mutable within one request but reset for new requests.--- Therefore managers whould use mutable or persistent data structures like 'IORef' internally.+-- Therefore managers should use mutable or persistent data structures like 'IORef' internally. class Manager manager where+    -- | Type of the session state inside the manager. In most cases this will+    -- either be any type or a type constrained by some serialisation typeclass like+    --+    -- @+    --  instance Serialize a => Manager (WritesToFileManager a) where+    --      type Manages (WritesToFileManager a) = a+    -- @     type Manages manager+    -- | Obtain the full session state for the current client.     managerGetSession :: Handler v manager (Manages manager)     managerGetSession = managerModifySession id+    -- | Replace the state of the current sessio with the provided one     managerSetSession :: Manages manager -> Handler v manager ()     managerSetSession = managerModifySession . const >=> const (return ())++    -- | Indicate to the manager that cached mutations on state should be persisted.+    --+    -- This method is entirely optional, however it is recommended+    -- for managers where mutating the persistent state is expensive to+    -- cache mutations in the manager data structure and persist them once commit+    -- is called.     managerCommit :: Handler v manager ()     managerCommit = return ()++    -- | Again optional. Users can use this method to indicate to the manager that+    -- the session should be established (cookie read for instance) and the state+    -- cached into the manager data structure.     managerLoad :: Handler v manager ()     managerLoad = return () +    -- | Applies a function to the (cached) session state     managerModifySession :: (Manages manager -> Manages manager) -> Handler v manager (Manages manager)     managerModifySession f = managerGetSession >>= \sess -> managerSetSession (f sess) >> return sess @@ -48,25 +78,40 @@  -- | Class providing access to a Snaplet managing session state class Manager (TheManager a) => HasManager a where+    -- | Type of the manager, since this is defined in terms of the 'Manager' typeclass     type TheManager a+    -- | Like 'HasHeist', a lens to get to the manager snaplet.     toManager :: SnapletLens (Snaplet a) (TheManager a)  -type CanAccessSubsession a b = (HasManager a, AccessSession (Manages (TheManager a)) b)+-- | This is provided for convenience. Snaplets which use part of the state can+-- use this shorthand in the type signature.+--+--      * 'base' is the (internal) type of your root snaplet+--      * 'snaplet' is the (internal) type of the snaplet that wants to access the session+--      * 't' is the type the (local) session data+--+-- If, for instance, our Snaplet was called 'Files' and it needed a HashSet as state+-- and the global Snaplet type was unknown you could write the following:+--+-- @+--  initFiles :: (CanAccessSubsession b Files HashSet) => SnapletInit b Files+-- @+type CanAccessSubsession base snaplet t = (HasManager base, AccessSession (Manages (TheManager base)) snaplet, LocalSession snaplet ~ t)  --- | Type magic-type AccessSessionLens t a b = t -> Lens' a b+-- | This is just type hacking to line up lenses. Use 'mkAccessSessionLens'+-- to turn a regular lens into this type.+newtype AccessSessionLens t a b = ASLens { getASLens :: t -> Lens' a b }  --- | You should use this function to create a 'AccessSessionLens'. It ignores--- the 't' argument. The 't' ergument is only used to line up types.+-- | Use this function to create an 'AccessSessionLens'. 't' is only used to line up types. mkAccessSessionLens :: Lens' a b -> AccessSessionLens t a b-mkAccessSessionLens = const+mkAccessSessionLens = ASLens . const  --- | A reference to a LocalSession session state from the GlobalSession session state--- You have to implement this class for your subsnaplet to get access to a part of the GlobalSession session state.+-- | A reference to a LocalSession session state from the global session state ('base').+-- You have to implement this class for your subsnaplet ('t') to get access to a part of the global session state. class AccessSession base t where     type LocalSession t     accessSession :: AccessSessionLens t base (LocalSession t)@@ -83,7 +128,6 @@   -- | Tells the session state manager to load the session.--- Users should not have to call this function loadSession :: forall s v. HasManager s => Handler s v () loadSession = withTop' (toManager :: SnapletLens (Snaplet s) (TheManager s)) managerLoad @@ -98,7 +142,7 @@ getSession :: forall s t. (HasManager s, AccessSession (Manages (TheManager s)) t) => Handler s t (LocalSession t) getSession = do     fs <- getFullSession-    return $ fs^.accessSession (error "Do not evaluate!" :: t)+    return $ fs^.getASLens accessSession (error "Do not evaluate!" :: t)   modifyFullSession :: forall s v. HasManager s => (Manages (TheManager s) -> Manages (TheManager s)) -> Handler s v (Manages (TheManager s))@@ -107,7 +151,7 @@  -- | Set the LocalSession part of the session state to a new value setSession :: forall s t. (HasManager s, AccessSession (Manages (TheManager s)) t) => LocalSession t -> Handler s t ()-setSession inner = void $ modifyFullSession (accessSession (error "Do not evaluate!" :: t) .~ inner)+setSession inner = void $ modifyFullSession (getASLens accessSession (error "Do not evaluate!" :: t) .~ inner)   -- | Modify the LocalSession session state with a function, returns the altered LocalSession state@@ -117,4 +161,4 @@ --      getSession = modifySession id -- @ modifySession :: forall s t. (HasManager s, AccessSession (Manages (TheManager s)) t) => (LocalSession t -> LocalSession t) -> Handler s t (Manages (TheManager s))-modifySession f = modifyFullSession (accessSession (error "Do not evaluate!" :: t) %~ f)+modifySession f = modifyFullSession (getASLens accessSession (error "Do not evaluate!" :: t) %~ f)
src/Snap/Snaplet/Session/Scoped/InMemory.hs view
@@ -1,3 +1,12 @@+{-|+Module      : $Header$+Description : Concrete in-memory session state manager+Copyright   : (c) Justus Adam, 2016+License     : BSD3+Maintainer  : dev@justus.science+Stability   : experimental+Portability : POSIX+-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TemplateHaskell  #-} {-# LANGUAGE TypeFamilies     #-}