Spock 0.7.1.0 → 0.7.2.0
raw patch · 13 files changed
+205/−152 lines, 13 filesdep −conduitdep −regex-compatdep −safedep ~reroutePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies removed: conduit, regex-compat, safe, vector, xsd
Dependency ranges changed: reroute
API changes (from Hackage documentation)
- Web.Spock.Safe: ConnBuilder :: IO a -> (a -> IO ()) -> PoolCfg -> ConnBuilder a
- Web.Spock.Safe: PCConn :: (ConnBuilder a) -> PoolOrConn a
- Web.Spock.Safe: PCPool :: (Pool a) -> PoolOrConn a
- Web.Spock.Safe: PoolCfg :: Int -> Int -> NominalDiffTime -> PoolCfg
- Web.Spock.Safe: SessionCfg :: Text -> NominalDiffTime -> Int -> a -> SessionCfg a
- Web.Spock.Safe: UploadedFile :: Text -> Text -> FilePath -> UploadedFile
- Web.Spock.Safe: blaze :: MonadIO m => Html -> ActionT m a
- Web.Spock.Safe: body :: MonadIO m => ActionT m ByteString
- Web.Spock.Safe: bytes :: MonadIO m => ByteString -> ActionT m a
- Web.Spock.Safe: cb_createConn :: ConnBuilder a -> IO a
- Web.Spock.Safe: cb_destroyConn :: ConnBuilder a -> a -> IO ()
- Web.Spock.Safe: cb_poolConfiguration :: ConnBuilder a -> PoolCfg
- Web.Spock.Safe: class HasSpock m where type family SpockConn m :: * type family SpockState m :: * type family SpockSession m :: *
- Web.Spock.Safe: clearAllSessions :: SpockAction conn sess st ()
- Web.Spock.Safe: cookie :: MonadIO m => Text -> ActionT m (Maybe Text)
- Web.Spock.Safe: data ConnBuilder a
- Web.Spock.Safe: data PoolCfg
- Web.Spock.Safe: data PoolOrConn a
- Web.Spock.Safe: data SessionCfg a
- Web.Spock.Safe: data UploadedFile
- Web.Spock.Safe: data WebState conn sess st
- Web.Spock.Safe: data WebStateM conn sess st a
- Web.Spock.Safe: file :: MonadIO m => Text -> FilePath -> ActionT m a
- Web.Spock.Safe: files :: MonadIO m => ActionT m (HashMap Text UploadedFile)
- Web.Spock.Safe: getSpockHeart :: MonadTrans t => t (WebStateM conn sess st) (WebState conn sess st)
- Web.Spock.Safe: getState :: HasSpock m => m (SpockState m)
- Web.Spock.Safe: header :: MonadIO m => Text -> ActionT m (Maybe Text)
- Web.Spock.Safe: html :: MonadIO m => Text -> ActionT m a
- Web.Spock.Safe: json :: (ToJSON a, MonadIO m) => a -> ActionT m b
- Web.Spock.Safe: jsonBody :: (MonadIO m, FromJSON a) => ActionT m (Maybe a)
- Web.Spock.Safe: jsonBody' :: (MonadIO m, FromJSON a) => ActionT m a
- Web.Spock.Safe: jumpNext :: MonadIO m => ActionT m a
- Web.Spock.Safe: lazyBytes :: MonadIO m => ByteString -> ActionT m a
- Web.Spock.Safe: modifySession :: (sess -> sess) -> SpockAction conn sess st ()
- Web.Spock.Safe: param :: (PathPiece p, MonadIO m) => Text -> ActionT m (Maybe p)
- Web.Spock.Safe: param' :: (PathPiece p, MonadIO m) => Text -> ActionT m p
- Web.Spock.Safe: params :: MonadIO m => ActionT m [(Text, Text)]
- Web.Spock.Safe: pc_keepOpenTime :: PoolCfg -> NominalDiffTime
- Web.Spock.Safe: pc_resPerStripe :: PoolCfg -> Int
- Web.Spock.Safe: pc_stripes :: PoolCfg -> Int
- Web.Spock.Safe: readSession :: SpockAction conn sess st sess
- Web.Spock.Safe: redirect :: MonadIO m => Text -> ActionT m a
- Web.Spock.Safe: request :: MonadIO m => ActionT m Request
- Web.Spock.Safe: requireBasicAuth :: MonadIO m => Text -> (Text -> Text -> m Bool) -> ActionT m a -> ActionT m a
- Web.Spock.Safe: runForm :: (Functor m, MonadIO m) => Text -> Form v (ActionT m) a -> ActionT m (View v, Maybe a)
- Web.Spock.Safe: runQuery :: HasSpock m => (SpockConn m -> IO a) -> m a
- Web.Spock.Safe: runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a
- Web.Spock.Safe: sc_cookieName :: SessionCfg a -> Text
- Web.Spock.Safe: sc_emptySession :: SessionCfg a -> a
- Web.Spock.Safe: sc_sessionIdEntropy :: SessionCfg a -> Int
- Web.Spock.Safe: sc_sessionTTL :: SessionCfg a -> NominalDiffTime
- Web.Spock.Safe: setCookie :: MonadIO m => Text -> Text -> NominalDiffTime -> ActionT m ()
- Web.Spock.Safe: setCookie' :: MonadIO m => Text -> Text -> UTCTime -> ActionT m ()
- Web.Spock.Safe: setHeader :: MonadIO m => Text -> Text -> ActionT m ()
- Web.Spock.Safe: setStatus :: MonadIO m => Status -> ActionT m ()
- Web.Spock.Safe: text :: MonadIO m => Text -> ActionT m a
- Web.Spock.Safe: uf_contentType :: UploadedFile -> Text
- Web.Spock.Safe: uf_name :: UploadedFile -> Text
- Web.Spock.Safe: uf_tempLocation :: UploadedFile -> FilePath
- Web.Spock.Safe: writeSession :: sess -> SpockAction conn sess st ()
- Web.Spock.Simple: ConnBuilder :: IO a -> (a -> IO ()) -> PoolCfg -> ConnBuilder a
- Web.Spock.Simple: PCConn :: (ConnBuilder a) -> PoolOrConn a
- Web.Spock.Simple: PCPool :: (Pool a) -> PoolOrConn a
- Web.Spock.Simple: PoolCfg :: Int -> Int -> NominalDiffTime -> PoolCfg
- Web.Spock.Simple: SessionCfg :: Text -> NominalDiffTime -> Int -> a -> SessionCfg a
- Web.Spock.Simple: UploadedFile :: Text -> Text -> FilePath -> UploadedFile
- Web.Spock.Simple: blaze :: MonadIO m => Html -> ActionT m a
- Web.Spock.Simple: body :: MonadIO m => ActionT m ByteString
- Web.Spock.Simple: bytes :: MonadIO m => ByteString -> ActionT m a
- Web.Spock.Simple: cb_createConn :: ConnBuilder a -> IO a
- Web.Spock.Simple: cb_destroyConn :: ConnBuilder a -> a -> IO ()
- Web.Spock.Simple: cb_poolConfiguration :: ConnBuilder a -> PoolCfg
- Web.Spock.Simple: class HasSpock m where type family SpockConn m :: * type family SpockState m :: * type family SpockSession m :: *
- Web.Spock.Simple: clearAllSessions :: SpockAction conn sess st ()
- Web.Spock.Simple: cookie :: MonadIO m => Text -> ActionT m (Maybe Text)
- Web.Spock.Simple: data ConnBuilder a
- Web.Spock.Simple: data PoolCfg
- Web.Spock.Simple: data PoolOrConn a
- Web.Spock.Simple: data SessionCfg a
- Web.Spock.Simple: data UploadedFile
- Web.Spock.Simple: data WebState conn sess st
- Web.Spock.Simple: data WebStateM conn sess st a
- Web.Spock.Simple: file :: MonadIO m => Text -> FilePath -> ActionT m a
- Web.Spock.Simple: files :: MonadIO m => ActionT m (HashMap Text UploadedFile)
- Web.Spock.Simple: getSpockHeart :: MonadTrans t => t (WebStateM conn sess st) (WebState conn sess st)
- Web.Spock.Simple: getState :: HasSpock m => m (SpockState m)
- Web.Spock.Simple: header :: MonadIO m => Text -> ActionT m (Maybe Text)
- Web.Spock.Simple: html :: MonadIO m => Text -> ActionT m a
- Web.Spock.Simple: json :: (ToJSON a, MonadIO m) => a -> ActionT m b
- Web.Spock.Simple: jsonBody :: (MonadIO m, FromJSON a) => ActionT m (Maybe a)
- Web.Spock.Simple: jsonBody' :: (MonadIO m, FromJSON a) => ActionT m a
- Web.Spock.Simple: jumpNext :: MonadIO m => ActionT m a
- Web.Spock.Simple: lazyBytes :: MonadIO m => ByteString -> ActionT m a
- Web.Spock.Simple: modifySession :: (sess -> sess) -> SpockAction conn sess st ()
- Web.Spock.Simple: param :: (PathPiece p, MonadIO m) => Text -> ActionT m (Maybe p)
- Web.Spock.Simple: param' :: (PathPiece p, MonadIO m) => Text -> ActionT m p
- Web.Spock.Simple: params :: MonadIO m => ActionT m [(Text, Text)]
- Web.Spock.Simple: pc_keepOpenTime :: PoolCfg -> NominalDiffTime
- Web.Spock.Simple: pc_resPerStripe :: PoolCfg -> Int
- Web.Spock.Simple: pc_stripes :: PoolCfg -> Int
- Web.Spock.Simple: readSession :: SpockAction conn sess st sess
- Web.Spock.Simple: redirect :: MonadIO m => Text -> ActionT m a
- Web.Spock.Simple: request :: MonadIO m => ActionT m Request
- Web.Spock.Simple: requireBasicAuth :: MonadIO m => Text -> (Text -> Text -> m Bool) -> ActionT m a -> ActionT m a
- Web.Spock.Simple: runForm :: (Functor m, MonadIO m) => Text -> Form v (ActionT m) a -> ActionT m (View v, Maybe a)
- Web.Spock.Simple: runQuery :: HasSpock m => (SpockConn m -> IO a) -> m a
- Web.Spock.Simple: runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a
- Web.Spock.Simple: sc_cookieName :: SessionCfg a -> Text
- Web.Spock.Simple: sc_emptySession :: SessionCfg a -> a
- Web.Spock.Simple: sc_sessionIdEntropy :: SessionCfg a -> Int
- Web.Spock.Simple: sc_sessionTTL :: SessionCfg a -> NominalDiffTime
- Web.Spock.Simple: setCookie :: MonadIO m => Text -> Text -> NominalDiffTime -> ActionT m ()
- Web.Spock.Simple: setCookie' :: MonadIO m => Text -> Text -> UTCTime -> ActionT m ()
- Web.Spock.Simple: setHeader :: MonadIO m => Text -> Text -> ActionT m ()
- Web.Spock.Simple: setStatus :: MonadIO m => Status -> ActionT m ()
- Web.Spock.Simple: text :: MonadIO m => Text -> ActionT m a
- Web.Spock.Simple: uf_contentType :: UploadedFile -> Text
- Web.Spock.Simple: uf_name :: UploadedFile -> Text
- Web.Spock.Simple: uf_tempLocation :: UploadedFile -> FilePath
- Web.Spock.Simple: writeSession :: sess -> SpockAction conn sess st ()
+ Web.Spock.Safe: hookAny :: Monad m => StdMethod -> ([Text] -> ActionT m ()) -> SpockT m ()
+ Web.Spock.Safe: spockMiddleware :: MonadIO m => (forall a. m a -> IO a) -> SpockT m () -> IO Middleware
+ Web.Spock.Shared: ConnBuilder :: IO a -> (a -> IO ()) -> PoolCfg -> ConnBuilder a
+ Web.Spock.Shared: PCConn :: (ConnBuilder a) -> PoolOrConn a
+ Web.Spock.Shared: PCPool :: (Pool a) -> PoolOrConn a
+ Web.Spock.Shared: PoolCfg :: Int -> Int -> NominalDiffTime -> PoolCfg
+ Web.Spock.Shared: SessionCfg :: Text -> NominalDiffTime -> Int -> a -> SessionCfg a
+ Web.Spock.Shared: UploadedFile :: Text -> Text -> FilePath -> UploadedFile
+ Web.Spock.Shared: blaze :: MonadIO m => Html -> ActionT m a
+ Web.Spock.Shared: body :: MonadIO m => ActionT m ByteString
+ Web.Spock.Shared: bytes :: MonadIO m => ByteString -> ActionT m a
+ Web.Spock.Shared: cb_createConn :: ConnBuilder a -> IO a
+ Web.Spock.Shared: cb_destroyConn :: ConnBuilder a -> a -> IO ()
+ Web.Spock.Shared: cb_poolConfiguration :: ConnBuilder a -> PoolCfg
+ Web.Spock.Shared: class HasSpock m where type family SpockConn m :: * type family SpockState m :: * type family SpockSession m :: *
+ Web.Spock.Shared: clearAllSessions :: SpockAction conn sess st ()
+ Web.Spock.Shared: cookie :: MonadIO m => Text -> ActionT m (Maybe Text)
+ Web.Spock.Shared: data ConnBuilder a
+ Web.Spock.Shared: data PoolCfg
+ Web.Spock.Shared: data PoolOrConn a
+ Web.Spock.Shared: data SessionCfg a
+ Web.Spock.Shared: data UploadedFile
+ Web.Spock.Shared: data WebState conn sess st
+ Web.Spock.Shared: data WebStateM conn sess st a
+ Web.Spock.Shared: file :: MonadIO m => Text -> FilePath -> ActionT m a
+ Web.Spock.Shared: files :: MonadIO m => ActionT m (HashMap Text UploadedFile)
+ Web.Spock.Shared: getSessionId :: SpockAction conn sess st SessionId
+ Web.Spock.Shared: getSpockHeart :: MonadTrans t => t (WebStateM conn sess st) (WebState conn sess st)
+ Web.Spock.Shared: getState :: HasSpock m => m (SpockState m)
+ Web.Spock.Shared: header :: MonadIO m => Text -> ActionT m (Maybe Text)
+ Web.Spock.Shared: html :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Shared: json :: (ToJSON a, MonadIO m) => a -> ActionT m b
+ Web.Spock.Shared: jsonBody :: (MonadIO m, FromJSON a) => ActionT m (Maybe a)
+ Web.Spock.Shared: jsonBody' :: (MonadIO m, FromJSON a) => ActionT m a
+ Web.Spock.Shared: jumpNext :: MonadIO m => ActionT m a
+ Web.Spock.Shared: lazyBytes :: MonadIO m => ByteString -> ActionT m a
+ Web.Spock.Shared: middlewarePass :: MonadIO m => ActionT m a
+ Web.Spock.Shared: modifySession :: (sess -> sess) -> SpockAction conn sess st ()
+ Web.Spock.Shared: modifyVault :: MonadIO m => (Vault -> Vault) -> ActionT m ()
+ Web.Spock.Shared: param :: (PathPiece p, MonadIO m) => Text -> ActionT m (Maybe p)
+ Web.Spock.Shared: param' :: (PathPiece p, MonadIO m) => Text -> ActionT m p
+ Web.Spock.Shared: params :: MonadIO m => ActionT m [(Text, Text)]
+ Web.Spock.Shared: pc_keepOpenTime :: PoolCfg -> NominalDiffTime
+ Web.Spock.Shared: pc_resPerStripe :: PoolCfg -> Int
+ Web.Spock.Shared: pc_stripes :: PoolCfg -> Int
+ Web.Spock.Shared: queryVault :: MonadIO m => Key a -> ActionT m (Maybe a)
+ Web.Spock.Shared: readSession :: SpockAction conn sess st sess
+ Web.Spock.Shared: redirect :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Shared: request :: MonadIO m => ActionT m Request
+ Web.Spock.Shared: requireBasicAuth :: MonadIO m => Text -> (Text -> Text -> m Bool) -> ActionT m a -> ActionT m a
+ Web.Spock.Shared: runForm :: (Functor m, MonadIO m) => Text -> Form v (ActionT m) a -> ActionT m (View v, Maybe a)
+ Web.Spock.Shared: runQuery :: HasSpock m => (SpockConn m -> IO a) -> m a
+ Web.Spock.Shared: runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a
+ Web.Spock.Shared: sc_cookieName :: SessionCfg a -> Text
+ Web.Spock.Shared: sc_emptySession :: SessionCfg a -> a
+ Web.Spock.Shared: sc_sessionIdEntropy :: SessionCfg a -> Int
+ Web.Spock.Shared: sc_sessionTTL :: SessionCfg a -> NominalDiffTime
+ Web.Spock.Shared: setCookie :: MonadIO m => Text -> Text -> NominalDiffTime -> ActionT m ()
+ Web.Spock.Shared: setCookie' :: MonadIO m => Text -> Text -> UTCTime -> ActionT m ()
+ Web.Spock.Shared: setHeader :: MonadIO m => Text -> Text -> ActionT m ()
+ Web.Spock.Shared: setStatus :: MonadIO m => Status -> ActionT m ()
+ Web.Spock.Shared: text :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Shared: type SessionId = Text
+ Web.Spock.Shared: uf_contentType :: UploadedFile -> Text
+ Web.Spock.Shared: uf_name :: UploadedFile -> Text
+ Web.Spock.Shared: uf_tempLocation :: UploadedFile -> FilePath
+ Web.Spock.Shared: writeSession :: sess -> SpockAction conn sess st ()
+ Web.Spock.Simple: hookAny :: Monad m => StdMethod -> ([Text] -> ActionT m ()) -> SpockT m ()
+ Web.Spock.Simple: spockMiddleware :: MonadIO m => (forall a. m a -> IO a) -> SpockT m () -> IO Middleware
Files
- Spock.cabal +9/−12
- src/Web/Spock/Internal/Core.hs +1/−0
- src/Web/Spock/Internal/CoreAction.hs +22/−2
- src/Web/Spock/Internal/Monad.hs +0/−17
- src/Web/Spock/Internal/SessionManager.hs +9/−1
- src/Web/Spock/Internal/Types.hs +2/−1
- src/Web/Spock/Internal/Wire.hs +55/−17
- src/Web/Spock/Safe.hs +16/−51
- src/Web/Spock/Shared.hs +71/−0
- src/Web/Spock/Simple.hs +16/−51
- test/Web/Spock/FrameworkSpecHelper.hs +2/−0
- test/Web/Spock/SafeSpec.hs +1/−0
- test/Web/Spock/SimpleSpec.hs +1/−0
Spock.cabal view
@@ -1,21 +1,23 @@ name: Spock-version: 0.7.1.0+version: 0.7.2.0 synopsis: Another Haskell web framework for rapid development-description: This toolbox provides everything you need to get a quick start into web hacking with haskell: routing, middleware, json, blaze, sessions, cookies, database helper, csrf-protection, global state+description: This toolbox provides everything you need to get a quick start into web hacking with haskell: fast routing, middleware, json, blaze, sessions, cookies, database helper, csrf-protection Homepage: https://github.com/agrafix/Spock Bug-reports: https://github.com/agrafix/Spock/issues license: BSD3-author: Alexander Thiemann <mail@agrafix.net>-maintainer: mail@agrafix.net+author: Alexander Thiemann <mail@athiemann.net>+maintainer: Alexander Thiemann <mail@athiemann.net> copyright: (c) 2013 - 2014 Alexander Thiemann category: Web build-type: Simple cabal-version: >=1.8+tested-with: GHC==7.6.3, GHC==7.8.3 library hs-source-dirs: src exposed-modules: Web.Spock.Simple,- Web.Spock.Safe+ Web.Spock.Safe,+ Web.Spock.Shared other-modules: Web.Spock.Internal.SessionManager, Web.Spock.Internal.Monad, Web.Spock.Internal.Types,@@ -31,7 +33,6 @@ blaze-html ==0.7.*, bytestring ==0.10.*, case-insensitive >=1.1 && <1.3,- conduit >= 1.0 && <1.3, containers ==0.5.*, digestive-functors ==0.7.*, directory ==1.2.*,@@ -42,11 +43,9 @@ old-locale ==1.*, path-pieces >=0.1.4, random ==1.*,- regex-compat ==0.95.*,- reroute >=0.2 && <0.3,+ reroute >=0.2.1 && <0.3, resource-pool ==0.2.*, resourcet >= 0.4 && <1.2,- safe >=0.3, stm ==2.4.*, text >= 0.11.3.1 && <1.3, time >=1.4 && <1.6,@@ -54,11 +53,9 @@ transformers-base ==0.4.*, unordered-containers ==0.2.*, vault ==0.3.*,- vector ==0.10.*, wai >=3.0 && <4.0, wai-extra >=3.0 && <4.0,- warp >=3.0 && <4.0,- xsd ==0.4.*+ warp >=3.0 && <4.0 ghc-options: -Wall -fno-warn-orphans test-suite spocktests
src/Web/Spock/Internal/Core.hs view
@@ -8,6 +8,7 @@ , spockAllT , middleware , hookRoute+ , hookAny , subcomponent ) where
src/Web/Spock/Internal/CoreAction.hs view
@@ -6,7 +6,7 @@ , UploadedFile (..) , request, header, cookie, body, jsonBody, jsonBody' , files, params, param, param', setStatus, setHeader, redirect- , jumpNext+ , jumpNext, middlewarePass, modifyVault, queryVault , setCookie, setCookie' , bytes, lazyBytes, text, html, file, json, blaze , requireBasicAuth@@ -37,6 +37,7 @@ import qualified Data.HashMap.Strict as HM import qualified Data.Text as T import qualified Data.Text.Encoding as T+import qualified Data.Vault.Lazy as V import qualified Network.Wai as Wai -- | Get the original Wai Request object@@ -145,6 +146,25 @@ redirect :: MonadIO m => T.Text -> ActionT m a redirect = throwError . ActionRedirect +-- | If the Spock application is used as a middleware, you can use+-- this to pass request handeling to the underlying application.+-- If Spock is not uses as a middleware, or there is no underlying application+-- this will result in 404 error.+middlewarePass :: MonadIO m => ActionT m a+middlewarePass = throwError ActionMiddlewarePass++-- | Modify the vault (useful for sharing data between middleware and app)+modifyVault :: MonadIO m => (V.Vault -> V.Vault) -> ActionT m ()+modifyVault f =+ do vaultIf <- asks ri_vaultIf+ liftIO $ (vi_modifyVault vaultIf) f++-- | Query the vault+queryVault :: MonadIO m => V.Key a -> ActionT m (Maybe a)+queryVault k =+ do vaultIf <- asks ri_vaultIf+ liftIO $ (vi_lookupKey vaultIf) k+ -- | Set a cookie living for a given number of seconds setCookie :: MonadIO m => T.Text -> T.Text -> NominalDiffTime -> ActionT m () setCookie name value validSeconds =@@ -184,7 +204,7 @@ do setHeader "Content-Type" "text/plain" bytes $ T.encodeUtf8 val --- | Send a text as response body. Content-Type will be "text/plain"+-- | Send a text as response body. Content-Type will be "text/html" html :: MonadIO m => T.Text -> ActionT m a html val = do setHeader "Content-Type" "text/html"
src/Web/Spock/Internal/Monad.hs view
@@ -6,16 +6,9 @@ import Web.Spock.Internal.Types -import Control.Monad import Control.Monad.Reader import Control.Monad.Trans.Resource import Data.Pool-import Data.Time.Clock ( UTCTime(..) )-import Web.PathPieces-import qualified Data.ByteString.Lazy as BSL-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Text.XML.XSD.DateTime as XSD webM :: MonadTrans t => WebStateM conn sess st a -> t (WebStateM conn sess st) a webM = lift@@ -57,13 +50,3 @@ getSessMgrImpl :: (WebStateM conn sess st) (SessionManager conn sess st) getSessMgrImpl = asks web_sessionMgr--instance PathPiece BSL.ByteString where- fromPathPiece =- Just . BSL.fromStrict . T.encodeUtf8- toPathPiece =- T.decodeUtf8 . BSL.toStrict--instance PathPiece UTCTime where- fromPathPiece p = join $ fmap XSD.toUTCTime $ XSD.dateTime p- toPathPiece = T.pack . show . XSD.fromUTCTime
src/Web/Spock/Internal/SessionManager.hs view
@@ -34,7 +34,8 @@ vaultKey <- V.newKey _ <- forkIO (forever (housekeepSessions cacheHM)) return $ SessionManager- { sm_readSession = readSessionImpl vaultKey cacheHM+ { sm_getSessionId = getSessionIdImpl vaultKey cacheHM+ , sm_readSession = readSessionImpl vaultKey cacheHM , sm_writeSession = writeSessionImpl vaultKey cacheHM , sm_modifySession = modifySessionImpl vaultKey cacheHM , sm_clearAllSessions = clearAllSessionsImpl cacheHM@@ -43,6 +44,13 @@ , sm_lookupSafeAction = lookupSafeActionImpl vaultKey cacheHM , sm_removeSafeAction = removeSafeActionImpl vaultKey cacheHM }++getSessionIdImpl :: V.Key SessionId+ -> UserSessions conn sess st+ -> SpockAction conn sess st SessionId+getSessionIdImpl vK sessionRef =+ do sess <- readSessionBase vK sessionRef+ return $ sess_id sess modifySessionBase :: V.Key SessionId -> UserSessions conn sess st
src/Web/Spock/Internal/Types.hs view
@@ -133,7 +133,8 @@ data SessionManager conn sess st = SessionManager- { sm_readSession :: SpockAction conn sess st sess+ { sm_getSessionId :: SpockAction conn sess st SessionId+ , sm_readSession :: SpockAction conn sess st sess , sm_writeSession :: sess -> SpockAction conn sess st () , sm_modifySession :: (sess -> sess) -> SpockAction conn sess st () , sm_clearAllSessions :: SpockAction conn sess st ()
src/Web/Spock/Internal/Wire.hs view
@@ -9,6 +9,7 @@ module Web.Spock.Internal.Wire where import Control.Applicative+import Control.Concurrent.STM import Control.Exception import Control.Monad.RWS.Strict import Control.Monad.Error@@ -31,6 +32,7 @@ import qualified Data.HashMap.Strict as HM import qualified Data.Text as T import qualified Data.Text.Encoding as T+import qualified Data.Vault.Lazy as V import qualified Network.Wai as Wai import qualified Network.Wai.Parse as P @@ -44,12 +46,19 @@ , uf_tempLocation :: FilePath } +data VaultIf+ = VaultIf+ { vi_modifyVault :: (V.Vault -> V.Vault) -> IO ()+ , vi_lookupKey :: forall a. V.Key a -> IO (Maybe a)+ }+ data RequestInfo = RequestInfo { ri_request :: Wai.Request , ri_params :: HM.HashMap CaptureVar T.Text , ri_queryParams :: [(T.Text, T.Text)] , ri_files :: HM.HashMap T.Text UploadedFile+ , ri_vaultIf :: VaultIf } data ResponseBody@@ -70,6 +79,7 @@ | ActionTryNext | ActionError String | ActionDone+ | ActionMiddlewarePass deriving (Show) instance Error ActionInterupt where@@ -131,11 +141,24 @@ -> SpockAllT r m () -> IO Wai.Application buildApp registryIf registryLift spockActions =+ do mw <- buildMiddleware registryIf registryLift spockActions+ return (mw fallbackApp)+ where+ fallbackApp :: Wai.Application+ fallbackApp _ respond =+ respond $ notFound++buildMiddleware :: forall m r. (MonadIO m, AbstractRouter r, RouteAppliedAction r ~ ActionT m ())+ => r+ -> (forall a. m a -> IO a)+ -> SpockAllT r m ()+ -> IO (Wai.Application -> Wai.Application)+buildMiddleware registryIf registryLift spockActions = do (_, getMatchingRoutes, middlewares) <- registryLift $ runRegistry registryIf spockActions let spockMiddleware = foldl (.) id middlewares- app :: Wai.Application- app req respond =+ app :: Wai.Application -> Wai.Application+ app coreApp req respond = case parseMethod $ Wai.requestMethod req of Left _ -> respond invalidReq@@ -143,7 +166,13 @@ runResourceT $ withInternalState $ \st -> do (bodyParams, bodyFiles) <- P.parseRequestBody (P.tempFileBackEnd st) req- let uploadedFiles =+ vaultVar <- liftIO $ newTVarIO (Wai.vault req)+ let vaultIf =+ VaultIf+ { vi_modifyVault = \modF -> atomically $ modifyTVar' vaultVar modF+ , vi_lookupKey = \k -> V.lookup k <$> (atomically $ readTVar vaultVar)+ }+ uploadedFiles = HM.fromList $ map (\(k, fileInfo) -> ( T.decodeUtf8 k@@ -157,34 +186,43 @@ queryParams = postParams ++ getParams resp = errorResponse status200 "" allActions = getMatchingRoutes stdMethod (Wai.pathInfo req)- applyAction :: [(ParamMap, ActionT m ())] -> m ResponseState+ applyAction :: [(ParamMap, ActionT m ())] -> m (Maybe ResponseState) applyAction [] =- return $ errorResponse status404 "404 - File not found"+ return $ Just $ errorResponse status404 "404 - File not found" applyAction ((captures, selectedAction) : xs) =- do let env = RequestInfo req captures queryParams uploadedFiles+ do let env = RequestInfo req captures queryParams uploadedFiles vaultIf (r, respState, _) <- runRWST (runErrorT $ runActionT $ selectedAction) env resp case r of Left (ActionRedirect loc) ->- return $ ResponseState (rs_responseHeaders respState)+ return $ Just $ ResponseState (rs_responseHeaders respState) status302 (ResponseRedirect loc) Left ActionTryNext -> applyAction xs Left (ActionError errorMsg) -> do liftIO $ putStrLn $ "Spock Error while handeling " ++ show (Wai.pathInfo req) ++ ": " ++ errorMsg- return serverError+ return $ Just serverError Left ActionDone ->- return respState+ return $ Just respState+ Left ActionMiddlewarePass ->+ return Nothing Right () ->- return respState- respState <-+ return $ Just respState+ mRespState <- liftIO $ (registryLift $ applyAction allActions) `catch` \(e :: SomeException) -> do putStrLn $ "Spock Error while handeling " ++ show (Wai.pathInfo req) ++ ": " ++ show e- return serverError- forM_ (HM.elems uploadedFiles) $ \uploadedFile ->- do stillThere <- doesFileExist (uf_tempLocation uploadedFile)- when stillThere $ liftIO $ removeFile (uf_tempLocation uploadedFile)- liftIO $ respond $ respStateToResponse respState- return $ spockMiddleware $ app+ return $ Just serverError+ case mRespState of+ Just respState ->+ do forM_ (HM.elems uploadedFiles) $ \uploadedFile ->+ do stillThere <- doesFileExist (uf_tempLocation uploadedFile)+ when stillThere $ liftIO $ removeFile (uf_tempLocation uploadedFile)+ liftIO $ respond $ respStateToResponse respState+ Nothing ->+ liftIO $+ do newVault <- atomically $ readTVar vaultVar+ let req' = req { Wai.vault = V.union newVault (Wai.vault req) }+ coreApp req' respond+ return $ spockMiddleware . app
src/Web/Spock/Safe.hs view
@@ -18,40 +18,22 @@ , renderRoute -- * Hooking routes , subcomponent- , get, post, head, put, delete, patch, hookRoute+ , get, post, head, put, delete, patch, hookRoute, hookAny , Http.StdMethod (..)- -- * Handeling requests- , request, header, cookie, body, jsonBody, jsonBody', files, UploadedFile (..)- , params, param, param'- -- * Sending responses- , setStatus, setHeader, redirect, jumpNext, setCookie, setCookie', bytes, lazyBytes- , text, html, file, json, blaze- -- * Adding middleware+ -- * Adding Wai.Middleware , middleware- -- * Database- , PoolOrConn (..), ConnBuilder (..), PoolCfg (..)- -- * Accessing Database and State- , HasSpock (runQuery, getState), SpockConn, SpockState, SpockSession- -- * Sessions- , SessionCfg (..)- , readSession, writeSession, modifySession, clearAllSessions- -- * Basic HTTP-Auth- , requireBasicAuth+ -- * Using Spock as middleware+ , spockMiddleware -- * Safe actions , SafeAction (..) , safeActionPath- -- * Digestive Functors- , runForm- -- * Internals for extending Spock- , getSpockHeart, runSpockIO, WebStateM, WebState+ , module Web.Spock.Shared ) where +import Web.Spock.Shared import Web.Spock.Internal.CoreAction-import Web.Spock.Internal.Digestive-import Web.Spock.Internal.Monad-import Web.Spock.Internal.SessionManager import Web.Spock.Internal.Types import Web.Spock.Internal.Wrapper import qualified Web.Spock.Internal.Wire as W@@ -103,6 +85,11 @@ spockApp liftFun (SpockT app) = W.buildApp SafeRouter liftFun app +-- | Convert a Spock-App to a wai-middleware+spockMiddleware :: (MonadIO m) => (forall a. m a -> IO a) -> SpockT m () -> IO Wai.Middleware+spockMiddleware liftFun (SpockT app) =+ W.buildMiddleware SafeRouter liftFun app+ -- | Specify an action that will be run when the HTTP verb 'GET' and the given route match get :: MonadIO m => Path xs -> HVectElim xs (ActionT m ()) -> SpockT m () get = hookRoute GET@@ -131,6 +118,11 @@ hookRoute :: Monad m => StdMethod -> Path xs -> HVectElim xs (ActionT m ()) -> SpockT m () hookRoute m path action = SpockT $ C.hookRoute m (SafeRouterPath path) (HVectElim' action) +-- | Specify an action that will be run when a HTTP verb matches but no defined route matches.+-- The full path is passed as an argument+hookAny :: Monad m => StdMethod -> ([T.Text] -> ActionT m ()) -> SpockT m ()+hookAny m action = SpockT $ C.hookAny m action+ -- | Define a subcomponent. Usage example: -- -- > subcomponent "site" $@@ -147,33 +139,6 @@ -- | Hook wai middleware into Spock middleware :: Monad m => Wai.Middleware -> SpockT m () middleware = SpockT . C.middleware----- | Write to the current session. Note that all data is stored on the server.--- The user only reciedes a sessionId to be identified.-writeSession :: sess -> SpockAction conn sess st ()-writeSession d =- do mgr <- getSessMgr- (sm_writeSession mgr) d---- | Modify the stored session-modifySession :: (sess -> sess) -> SpockAction conn sess st ()-modifySession f =- do mgr <- getSessMgr- (sm_modifySession mgr) f---- | Read the stored session-readSession :: SpockAction conn sess st sess-readSession =- do mgr <- getSessMgr- sm_readSession mgr---- | Globally delete all existing sessions. This is useful for example if you want--- to require all users to relogin-clearAllSessions :: SpockAction conn sess st ()-clearAllSessions =- do mgr <- getSessMgr- sm_clearAllSessions mgr -- | Wire up a safe action: Safe actions are actions that are protected from -- csrf attacks. Here's a usage example:
@@ -0,0 +1,71 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE DataKinds #-}+module Web.Spock.Shared+ (-- * Handeling requests+ request, header, cookie, body, jsonBody, jsonBody', files, UploadedFile (..)+ , params, param, param'+ -- * Sending responses+ , setStatus, setHeader, redirect, jumpNext, setCookie, setCookie', bytes, lazyBytes+ , text, html, file, json, blaze+ -- * Middleware helpers+ , middlewarePass, modifyVault, queryVault+ -- * Database+ , PoolOrConn (..), ConnBuilder (..), PoolCfg (..)+ -- * Accessing Database and State+ , HasSpock (runQuery, getState), SpockConn, SpockState, SpockSession+ -- * Basic HTTP-Auth+ , requireBasicAuth+ -- * Sessions+ , SessionCfg (..), SessionId+ , getSessionId, readSession, writeSession, modifySession, clearAllSessions+ -- * Digestive Functors+ , runForm+ -- * Internals for extending Spock+ , getSpockHeart, runSpockIO, WebStateM, WebState+ )+where++import Web.Spock.Internal.Monad+import Web.Spock.Internal.Digestive+import Web.Spock.Internal.SessionManager+import Web.Spock.Internal.Types+import Web.Spock.Internal.CoreAction++-- | Get the current users sessionId. Note that this ID should only be+-- shown to it's owner as otherwise sessions can be hijacked.+getSessionId :: SpockAction conn sess st SessionId+getSessionId =+ getSessMgr >>= sm_getSessionId++-- | Write to the current session. Note that all data is stored on the server.+-- The user only reciedes a sessionId to be identified.+writeSession :: sess -> SpockAction conn sess st ()+writeSession d =+ do mgr <- getSessMgr+ (sm_writeSession mgr) d++-- | Modify the stored session+modifySession :: (sess -> sess) -> SpockAction conn sess st ()+modifySession f =+ do mgr <- getSessMgr+ (sm_modifySession mgr) f++-- | Read the stored session+readSession :: SpockAction conn sess st sess+readSession =+ do mgr <- getSessMgr+ sm_readSession mgr++-- | Globally delete all existing sessions. This is useful for example if you want+-- to require all users to relogin+clearAllSessions :: SpockAction conn sess st ()+clearAllSessions =+ do mgr <- getSessMgr+ sm_clearAllSessions mgr
src/Web/Spock/Simple.hs view
@@ -15,40 +15,21 @@ , SpockRoute, (<#>) -- * Hooking routes , subcomponent- , get, post, head, put, delete, patch, hookRoute+ , get, post, head, put, delete, patch, hookRoute, hookAny , Http.StdMethod (..)- -- * Handeling requests- , request, header, cookie, body, jsonBody, jsonBody', files, UploadedFile (..)- , params, param, param'- -- * Sending responses- , setStatus, setHeader, redirect, jumpNext, setCookie, setCookie', bytes, lazyBytes- , text, html, file, json, blaze- -- * Adding middleware+ -- * Adding Wai.Middleware , middleware- -- * Database- , PoolOrConn (..), ConnBuilder (..), PoolCfg (..)- -- * Accessing Database and State- , HasSpock (runQuery, getState), SpockConn, SpockState, SpockSession- -- * Sessions- , SessionCfg (..)- , readSession, writeSession, modifySession, clearAllSessions- -- * Basic HTTP-Auth- , requireBasicAuth+ -- * Using Spock as middleware+ , spockMiddleware -- * Safe actions , SafeAction (..) , safeActionPath- -- * Digestive Functors- , runForm- -- * Internals for extending Spock- , getSpockHeart, runSpockIO, WebStateM, WebState+ , module Web.Spock.Shared ) where -+import Web.Spock.Shared import Web.Spock.Internal.CoreAction-import Web.Spock.Internal.Digestive-import Web.Spock.Internal.Monad-import Web.Spock.Internal.SessionManager import Web.Spock.Internal.Types import Web.Spock.Internal.Wrapper import qualified Web.Spock.Internal.Wire as W@@ -104,6 +85,11 @@ spockApp liftFun (SpockT app) = W.buildApp TextRouter liftFun app +-- | Convert a Spock-App to a wai-middleware+spockMiddleware :: (MonadIO m) => (forall a. m a -> IO a) -> SpockT m () -> IO Wai.Middleware+spockMiddleware liftFun (SpockT app) =+ W.buildMiddleware TextRouter liftFun app+ -- | Combine two route components safely -- "/foo" <#> "/bar" ===> "/foo/bar" -- "foo" <#> "bar" ===> "/foo/bar"@@ -139,6 +125,11 @@ hookRoute :: Monad m => StdMethod -> SpockRoute -> ActionT m () -> SpockT m () hookRoute m (SpockRoute path) action = SpockT $ C.hookRoute m (TextRouterPath path) (TAction action) +-- | Specify an action that will be run when a HTTP verb matches but no defined route matches.+-- The full path is passed as an argument+hookAny :: Monad m => StdMethod -> ([T.Text] -> ActionT m ()) -> SpockT m ()+hookAny m action = SpockT $ C.hookAny m action+ -- | Define a subcomponent. Usage example: -- -- > subcomponent "/site" $@@ -155,32 +146,6 @@ -- | Hook wai middleware into Spock middleware :: Monad m => Wai.Middleware -> SpockT m () middleware = SpockT . C.middleware---- | Write to the current session. Note that all data is stored on the server.--- The user only reciedes a sessionId to be identified.-writeSession :: sess -> SpockAction conn sess st ()-writeSession d =- do mgr <- getSessMgr- (sm_writeSession mgr) d---- | Modify the stored session-modifySession :: (sess -> sess) -> SpockAction conn sess st ()-modifySession f =- do mgr <- getSessMgr- (sm_modifySession mgr) f---- | Read the stored session-readSession :: SpockAction conn sess st sess-readSession =- do mgr <- getSessMgr- sm_readSession mgr---- | Globally delete all existing sessions. This is useful for example if you want--- to require all users to relogin-clearAllSessions :: SpockAction conn sess st ()-clearAllSessions =- do mgr <- getSessMgr- sm_clearAllSessions mgr -- | Wire up a safe action: Safe actions are actions that are protected from -- csrf attacks. Here's a usage example:
test/Web/Spock/FrameworkSpecHelper.hs view
@@ -30,6 +30,8 @@ it "works with subcomponents" $ do get "/subcomponent/foo" `shouldRespondWith` "foo" { matchStatus = 200 } get "/subcomponent/subcomponent2/bar" `shouldRespondWith` "bar" { matchStatus = 200 }+ it "allows the definition of a fallback handler" $+ do get "/askldjas/aklsdj" `shouldRespondWith` "askldjas/aklsdj" { matchStatus = 200 } where verbTest verb verbVerbose = (verb "/verb-test")
test/Web/Spock/SafeSpec.hs view
@@ -25,6 +25,7 @@ do get "foo" $ text "foo" subcomponent "/subcomponent2" $ do get "bar" $ text "bar"+ hookAny GET $ text . T.intercalate "/" spec :: Spec spec = describe "SafeRouting" $ frameworkSpec (spockApp id app)
test/Web/Spock/SimpleSpec.hs view
@@ -26,6 +26,7 @@ do get "foo" $ text "foo" subcomponent "/subcomponent2" $ do get "bar" $ text "bar"+ hookAny GET $ text . T.intercalate "/" spec :: Spec spec = describe "SimpleRouting" $ frameworkSpec (spockApp id app)