packages feed

Spock 0.6.6.1 → 0.7.0.0

raw patch · 29 files changed

+1616/−1559 lines, 29 filesdep +HListdep +hspec-waidep +reroutedep −QuickCheckPVP ok

version bump matches the API change (PVP)

Dependencies added: HList, hspec-wai, reroute, safe

Dependencies removed: QuickCheck

API changes (from Hackage documentation)

- Web.Spock: CONNECT :: StdMethod
- Web.Spock: ConnBuilder :: IO a -> (a -> IO ()) -> PoolCfg -> ConnBuilder a
- Web.Spock: DELETE :: StdMethod
- Web.Spock: GET :: StdMethod
- Web.Spock: HEAD :: StdMethod
- Web.Spock: OPTIONS :: StdMethod
- Web.Spock: PATCH :: StdMethod
- Web.Spock: PCConn :: (ConnBuilder a) -> PoolOrConn a
- Web.Spock: PCPool :: (Pool a) -> PoolOrConn a
- Web.Spock: POST :: StdMethod
- Web.Spock: PUT :: StdMethod
- Web.Spock: PoolCfg :: Int -> Int -> NominalDiffTime -> PoolCfg
- Web.Spock: SessionCfg :: Text -> NominalDiffTime -> Int -> a -> SessionCfg a
- Web.Spock: TRACE :: StdMethod
- Web.Spock: UploadedFile :: Text -> Text -> FilePath -> UploadedFile
- Web.Spock: blaze :: MonadIO m => Html -> ActionT m a
- Web.Spock: body :: MonadIO m => ActionT m ByteString
- Web.Spock: bytes :: MonadIO m => ByteString -> ActionT m a
- Web.Spock: cb_createConn :: ConnBuilder a -> IO a
- Web.Spock: cb_destroyConn :: ConnBuilder a -> a -> IO ()
- Web.Spock: cb_poolConfiguration :: ConnBuilder a -> PoolCfg
- Web.Spock: class HasSpock m where type family SpockConn m :: * type family SpockState m :: * type family SpockSession m :: *
- Web.Spock: class (Hashable a, Eq a, Typeable a) => SafeAction conn sess st a
- Web.Spock: clearAllSessions :: SpockAction conn sess st ()
- Web.Spock: combineRoute :: Text -> Text -> Text
- Web.Spock: cookie :: MonadIO m => Text -> ActionT m (Maybe Text)
- Web.Spock: data ActionT m a
- Web.Spock: data ConnBuilder a
- Web.Spock: data PoolCfg
- Web.Spock: data PoolOrConn a
- Web.Spock: data SessionCfg a
- Web.Spock: data SpockT (m :: * -> *) a
- Web.Spock: data StdMethod :: *
- Web.Spock: data UploadedFile
- Web.Spock: data WebState conn sess st
- Web.Spock: data WebStateM conn sess st a
- Web.Spock: defRoute :: MonadIO m => StdMethod -> Text -> ActionT m () -> SpockT m ()
- Web.Spock: delete :: MonadIO m => Text -> ActionT m () -> SpockT m ()
- Web.Spock: file :: MonadIO m => Text -> FilePath -> ActionT m a
- Web.Spock: files :: MonadIO m => ActionT m (HashMap Text UploadedFile)
- Web.Spock: get :: MonadIO m => Text -> ActionT m () -> SpockT m ()
- Web.Spock: getSpockHeart :: MonadTrans t => t (WebStateM conn sess st) (WebState conn sess st)
- Web.Spock: getState :: HasSpock m => m (SpockState m)
- Web.Spock: head :: MonadIO m => Text -> ActionT m () -> SpockT m ()
- Web.Spock: header :: MonadIO m => Text -> ActionT m (Maybe Text)
- Web.Spock: html :: MonadIO m => Text -> ActionT m a
- Web.Spock: json :: (ToJSON a, MonadIO m) => a -> ActionT m b
- Web.Spock: jsonBody :: (MonadIO m, FromJSON a) => ActionT m (Maybe a)
- Web.Spock: jsonBody' :: (MonadIO m, FromJSON a) => ActionT m a
- Web.Spock: jumpNext :: MonadIO m => ActionT m a
- Web.Spock: lazyBytes :: MonadIO m => ByteString -> ActionT m a
- Web.Spock: middleware :: MonadIO m => Middleware -> SpockT m ()
- Web.Spock: modifySession :: (sess -> sess) -> SpockAction conn sess st ()
- Web.Spock: param :: (PathPiece p, MonadIO m) => Text -> ActionT m (Maybe p)
- Web.Spock: param' :: (PathPiece p, MonadIO m) => Text -> ActionT m p
- Web.Spock: params :: MonadIO m => ActionT m [(Text, Text)]
- Web.Spock: patch :: MonadIO m => Text -> ActionT m () -> SpockT m ()
- Web.Spock: pc_keepOpenTime :: PoolCfg -> NominalDiffTime
- Web.Spock: pc_resPerStripe :: PoolCfg -> Int
- Web.Spock: pc_stripes :: PoolCfg -> Int
- Web.Spock: post :: MonadIO m => Text -> ActionT m () -> SpockT m ()
- Web.Spock: put :: MonadIO m => Text -> ActionT m () -> SpockT m ()
- Web.Spock: readSession :: SpockAction conn sess st sess
- Web.Spock: redirect :: MonadIO m => Text -> ActionT m a
- Web.Spock: request :: MonadIO m => ActionT m Request
- Web.Spock: requireBasicAuth :: MonadIO m => Text -> (Text -> Text -> m Bool) -> ActionT m a -> ActionT m a
- Web.Spock: runForm :: (Functor m, MonadIO m) => Text -> Form v (ActionT m) a -> ActionT m (View v, Maybe a)
- Web.Spock: runQuery :: HasSpock m => (SpockConn m -> IO a) -> m a
- Web.Spock: runSafeAction :: SafeAction conn sess st a => a -> SpockAction conn sess st ()
- Web.Spock: runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a
- Web.Spock: safeActionPath :: (SafeAction conn sess st a, HasSpock (SpockAction conn sess st), SpockConn (SpockAction conn sess st) ~ conn, SpockSession (SpockAction conn sess st) ~ sess, SpockState (SpockAction conn sess st) ~ st) => a -> SpockAction conn sess st Text
- Web.Spock: sc_cookieName :: SessionCfg a -> Text
- Web.Spock: sc_emptySession :: SessionCfg a -> a
- Web.Spock: sc_sessionIdEntropy :: SessionCfg a -> Int
- Web.Spock: sc_sessionTTL :: SessionCfg a -> NominalDiffTime
- Web.Spock: setCookie :: MonadIO m => Text -> Text -> NominalDiffTime -> ActionT m ()
- Web.Spock: setCookie' :: MonadIO m => Text -> Text -> UTCTime -> ActionT m ()
- Web.Spock: setHeader :: MonadIO m => Text -> Text -> ActionT m ()
- Web.Spock: setStatus :: MonadIO m => Status -> ActionT m ()
- Web.Spock: spock :: Int -> SessionCfg sess -> PoolOrConn conn -> st -> SpockM conn sess st () -> IO ()
- Web.Spock: spockT :: MonadIO m => Port -> (forall a. m a -> IO a) -> SpockT m () -> IO ()
- Web.Spock: subcomponent :: MonadIO m => Text -> SpockT m a -> SpockT m a
- Web.Spock: text :: MonadIO m => Text -> ActionT m a
- Web.Spock: type SpockAction conn sess st = ActionT (WebStateM conn sess st)
- Web.Spock: type SpockM conn sess st = SpockT (WebStateM conn sess st)
- Web.Spock: uf_contentType :: UploadedFile -> Text
- Web.Spock: uf_name :: UploadedFile -> Text
- Web.Spock: uf_tempLocation :: UploadedFile -> FilePath
- Web.Spock: writeSession :: sess -> SpockAction conn sess st ()
- Web.Spock.Routing: CaptureVar :: Text -> CaptureVar
- Web.Spock.Routing: RegexWrapper :: !Regex -> !Text -> RegexWrapper
- Web.Spock.Routing: RouteData :: !RouteNode -> Maybe a -> RouteData a
- Web.Spock.Routing: RouteNodeCapture :: !CaptureVar -> RouteNode
- Web.Spock.Routing: RouteNodeRegex :: !CaptureVar -> !RegexWrapper -> RouteNode
- Web.Spock.Routing: RouteNodeRoot :: RouteNode
- Web.Spock.Routing: RouteNodeText :: !Text -> RouteNode
- Web.Spock.Routing: RoutingTree :: !(RouteData a) -> !(Vector (RoutingTree a)) -> RoutingTree a
- Web.Spock.Routing: addToRoutingTree :: Text -> a -> RoutingTree a -> RoutingTree a
- Web.Spock.Routing: buildRegex :: Text -> RegexWrapper
- Web.Spock.Routing: data RegexWrapper
- Web.Spock.Routing: data RouteData a
- Web.Spock.Routing: data RouteNode
- Web.Spock.Routing: data RoutingTree a
- Web.Spock.Routing: emptyParamMap :: ParamMap
- Web.Spock.Routing: emptyRoutingTree :: RoutingTree a
- Web.Spock.Routing: instance Eq CaptureVar
- Web.Spock.Routing: instance Eq RegexWrapper
- Web.Spock.Routing: instance Eq RouteNode
- Web.Spock.Routing: instance Eq a => Eq (RouteData a)
- Web.Spock.Routing: instance Eq a => Eq (RoutingTree a)
- Web.Spock.Routing: instance Hashable CaptureVar
- Web.Spock.Routing: instance Show CaptureVar
- Web.Spock.Routing: instance Show RegexWrapper
- Web.Spock.Routing: instance Show RouteNode
- Web.Spock.Routing: instance Show a => Show (RouteData a)
- Web.Spock.Routing: instance Show a => Show (RoutingTree a)
- Web.Spock.Routing: matchNode :: Text -> RouteNode -> (Bool, Maybe (CaptureVar, Text))
- Web.Spock.Routing: matchRoute :: Text -> RoutingTree a -> [(ParamMap, a)]
- Web.Spock.Routing: matchRoute' :: [Text] -> RoutingTree a -> [(ParamMap, a)]
- Web.Spock.Routing: mergeData :: Maybe a -> Maybe a -> Maybe a
- Web.Spock.Routing: newtype CaptureVar
- Web.Spock.Routing: parseRouteNode :: Text -> RouteNode
- Web.Spock.Routing: rd_data :: RouteData a -> Maybe a
- Web.Spock.Routing: rd_node :: RouteData a -> !RouteNode
- Web.Spock.Routing: rt_children :: RoutingTree a -> !(Vector (RoutingTree a))
- Web.Spock.Routing: rt_node :: RoutingTree a -> !(RouteData a)
- Web.Spock.Routing: rw_original :: RegexWrapper -> !Text
- Web.Spock.Routing: rw_regex :: RegexWrapper -> !Regex
- Web.Spock.Routing: type ParamMap = HashMap CaptureVar Text
- Web.Spock.Routing: unCaptureVar :: CaptureVar -> Text
- Web.Spock.Wire: ActionDone :: ActionInterupt
- Web.Spock.Wire: ActionError :: String -> ActionInterupt
- Web.Spock.Wire: ActionRedirect :: Text -> ActionInterupt
- Web.Spock.Wire: ActionT :: ErrorT ActionInterupt (RWST RequestInfo () ResponseState m) a -> ActionT m a
- Web.Spock.Wire: ActionTryNext :: ActionInterupt
- Web.Spock.Wire: RequestInfo :: Request -> HashMap CaptureVar Text -> [(Text, Text)] -> HashMap Text UploadedFile -> RequestInfo
- Web.Spock.Wire: ResponseFile :: FilePath -> ResponseBody
- Web.Spock.Wire: ResponseLBS :: ByteString -> ResponseBody
- Web.Spock.Wire: ResponseRedirect :: Text -> ResponseBody
- Web.Spock.Wire: ResponseState :: [(Text, Text)] -> Status -> ResponseBody -> ResponseState
- Web.Spock.Wire: SpockState :: !(SpockRouteMap m) -> [Middleware] -> (forall a. m a -> IO a) -> SpockState m
- Web.Spock.Wire: SpockT :: RWST BaseRoute () (SpockState m) m a -> SpockT a
- Web.Spock.Wire: UploadedFile :: Text -> Text -> FilePath -> UploadedFile
- Web.Spock.Wire: buildApp :: MonadIO m => (forall a. m a -> IO a) -> SpockT m () -> IO Application
- Web.Spock.Wire: buildRoutingTree :: SpockRouteMap m -> SpockTreeMap m
- Web.Spock.Wire: combineRoute :: Text -> Text -> Text
- Web.Spock.Wire: data ActionInterupt
- Web.Spock.Wire: data RequestInfo
- Web.Spock.Wire: data ResponseBody
- Web.Spock.Wire: data ResponseState
- Web.Spock.Wire: data SpockState m
- Web.Spock.Wire: data UploadedFile
- Web.Spock.Wire: defRoute :: MonadIO m => StdMethod -> Text -> ActionT m () -> SpockT m ()
- Web.Spock.Wire: errorResponse :: Status -> ByteString -> ResponseState
- Web.Spock.Wire: instance (Monad m, Functor m) => Applicative (ActionT m)
- Web.Spock.Wire: instance (Monad m, Functor m) => Applicative (SpockT m)
- Web.Spock.Wire: instance Eq ResponseBody
- Web.Spock.Wire: instance Eq ResponseState
- Web.Spock.Wire: instance Error ActionInterupt
- Web.Spock.Wire: instance Functor m => Functor (ActionT m)
- Web.Spock.Wire: instance Functor m => Functor (SpockT m)
- Web.Spock.Wire: instance Hashable StdMethod
- Web.Spock.Wire: instance Monad m => Monad (ActionT m)
- Web.Spock.Wire: instance Monad m => Monad (SpockT m)
- Web.Spock.Wire: instance Monad m => MonadError ActionInterupt (ActionT m)
- Web.Spock.Wire: instance Monad m => MonadReader BaseRoute (SpockT m)
- Web.Spock.Wire: instance Monad m => MonadReader RequestInfo (ActionT m)
- Web.Spock.Wire: instance Monad m => MonadState (SpockState m) (SpockT m)
- Web.Spock.Wire: instance Monad m => MonadState ResponseState (ActionT m)
- Web.Spock.Wire: instance MonadIO m => MonadIO (ActionT m)
- Web.Spock.Wire: instance MonadIO m => MonadIO (SpockT m)
- Web.Spock.Wire: instance MonadTrans ActionT
- Web.Spock.Wire: instance MonadTrans SpockT
- Web.Spock.Wire: instance Show ActionInterupt
- Web.Spock.Wire: instance Show ResponseBody
- Web.Spock.Wire: instance Show ResponseState
- Web.Spock.Wire: invalidReq :: Response
- Web.Spock.Wire: middleware :: MonadIO m => Middleware -> SpockT m ()
- Web.Spock.Wire: newtype ActionT m a
- Web.Spock.Wire: newtype SpockT (m :: * -> *) a
- Web.Spock.Wire: notFound :: Response
- Web.Spock.Wire: respStateToResponse :: ResponseState -> Response
- Web.Spock.Wire: ri_files :: RequestInfo -> HashMap Text UploadedFile
- Web.Spock.Wire: ri_params :: RequestInfo -> HashMap CaptureVar Text
- Web.Spock.Wire: ri_queryParams :: RequestInfo -> [(Text, Text)]
- Web.Spock.Wire: ri_request :: RequestInfo -> Request
- Web.Spock.Wire: rs_responseBody :: ResponseState -> ResponseBody
- Web.Spock.Wire: rs_responseHeaders :: ResponseState -> [(Text, Text)]
- Web.Spock.Wire: rs_status :: ResponseState -> Status
- Web.Spock.Wire: runActionT :: ActionT m a -> ErrorT ActionInterupt (RWST RequestInfo () ResponseState m) a
- Web.Spock.Wire: runSpockT :: SpockT a -> RWST BaseRoute () (SpockState m) m a
- Web.Spock.Wire: serverError :: ResponseState
- Web.Spock.Wire: ss_middleware :: SpockState m -> [Middleware]
- Web.Spock.Wire: ss_spockLift :: SpockState m -> forall a. m a -> IO a
- Web.Spock.Wire: ss_treeMap :: SpockState m -> !(SpockRouteMap m)
- Web.Spock.Wire: subcomponent :: MonadIO m => Text -> SpockT m a -> SpockT m a
- Web.Spock.Wire: type BaseRoute = Text
- Web.Spock.Wire: type SpockRouteMap m = HashMap StdMethod (HashMap Text (ActionT m ()))
- Web.Spock.Wire: type SpockRoutingTree m = RoutingTree (ActionT m ())
- Web.Spock.Wire: type SpockTreeMap m = HashMap StdMethod (SpockRoutingTree m)
- Web.Spock.Wire: uf_contentType :: UploadedFile -> Text
- Web.Spock.Wire: uf_name :: UploadedFile -> Text
- Web.Spock.Wire: uf_tempLocation :: UploadedFile -> FilePath
+ Web.Spock.Safe: (</>) :: Path as -> Path bs -> Path (HAppendList * as bs)
+ Web.Spock.Safe: CONNECT :: StdMethod
+ Web.Spock.Safe: ConnBuilder :: IO a -> (a -> IO ()) -> PoolCfg -> ConnBuilder a
+ Web.Spock.Safe: DELETE :: StdMethod
+ Web.Spock.Safe: GET :: StdMethod
+ Web.Spock.Safe: HEAD :: StdMethod
+ Web.Spock.Safe: OPTIONS :: StdMethod
+ Web.Spock.Safe: PATCH :: StdMethod
+ Web.Spock.Safe: PCConn :: (ConnBuilder a) -> PoolOrConn a
+ Web.Spock.Safe: PCPool :: (Pool a) -> PoolOrConn a
+ Web.Spock.Safe: POST :: StdMethod
+ Web.Spock.Safe: PUT :: StdMethod
+ Web.Spock.Safe: PoolCfg :: Int -> Int -> NominalDiffTime -> PoolCfg
+ Web.Spock.Safe: SessionCfg :: Text -> NominalDiffTime -> Int -> a -> SessionCfg a
+ Web.Spock.Safe: TRACE :: StdMethod
+ 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: class (Hashable a, Eq a, Typeable a) => SafeAction conn sess st a
+ Web.Spock.Safe: clearAllSessions :: SpockAction conn sess st ()
+ Web.Spock.Safe: cookie :: MonadIO m => Text -> ActionT m (Maybe Text)
+ Web.Spock.Safe: data ActionT m a
+ Web.Spock.Safe: data ConnBuilder a
+ Web.Spock.Safe: data Path (as :: [*]) :: [*] -> *
+ Web.Spock.Safe: data PoolCfg
+ Web.Spock.Safe: data PoolOrConn a
+ Web.Spock.Safe: data SessionCfg a
+ Web.Spock.Safe: data SpockT m a
+ Web.Spock.Safe: data StdMethod :: *
+ 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: delete :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()
+ 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: get :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()
+ 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: head :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()
+ Web.Spock.Safe: header :: MonadIO m => Text -> ActionT m (Maybe Text)
+ Web.Spock.Safe: hookRoute :: Monad m => StdMethod -> Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()
+ Web.Spock.Safe: html :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Safe: instance (Monad m, Functor m) => Applicative (SpockT m)
+ Web.Spock.Safe: instance Functor m => Functor (SpockT m)
+ Web.Spock.Safe: instance Monad m => Monad (SpockT m)
+ Web.Spock.Safe: instance MonadIO m => MonadIO (SpockT m)
+ Web.Spock.Safe: instance MonadTrans SpockT
+ 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: middleware :: Monad m => Middleware -> SpockT m ()
+ 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: patch :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()
+ Web.Spock.Safe: pc_keepOpenTime :: PoolCfg -> NominalDiffTime
+ Web.Spock.Safe: pc_resPerStripe :: PoolCfg -> Int
+ Web.Spock.Safe: pc_stripes :: PoolCfg -> Int
+ Web.Spock.Safe: post :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()
+ Web.Spock.Safe: put :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()
+ Web.Spock.Safe: readSession :: SpockAction conn sess st sess
+ Web.Spock.Safe: redirect :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Safe: renderRoute :: Path as -> HList as -> Text
+ 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: root :: Path ([] *)
+ 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: runSafeAction :: SafeAction conn sess st a => a -> SpockAction conn sess st ()
+ Web.Spock.Safe: runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a
+ Web.Spock.Safe: safeActionPath :: (SafeAction conn sess st a, HasSpock (SpockAction conn sess st), SpockConn (SpockAction conn sess st) ~ conn, SpockSession (SpockAction conn sess st) ~ sess, SpockState (SpockAction conn sess st) ~ st) => a -> SpockAction conn sess st Text
+ 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: spock :: Int -> SessionCfg sess -> PoolOrConn conn -> st -> SpockM conn sess st () -> IO ()
+ Web.Spock.Safe: spockApp :: MonadIO m => (forall a. m a -> IO a) -> SpockT m () -> IO Application
+ Web.Spock.Safe: spockT :: MonadIO m => Port -> (forall a. m a -> IO a) -> SpockT m () -> IO ()
+ Web.Spock.Safe: static :: String -> Path ([] *)
+ Web.Spock.Safe: subcomponent :: Monad m => Path '[] -> SpockT m () -> SpockT m ()
+ Web.Spock.Safe: text :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Safe: type SpockAction conn sess st = ActionT (WebStateM conn sess st)
+ Web.Spock.Safe: type SpockM conn sess st a = SpockT (WebStateM conn sess st) 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: var :: (Typeable a, PathPiece a) => Path (: * a ([] *))
+ Web.Spock.Safe: writeSession :: sess -> SpockAction conn sess st ()
+ Web.Spock.Simple: (<#>) :: SpockRoute -> SpockRoute -> SpockRoute
+ Web.Spock.Simple: CONNECT :: StdMethod
+ Web.Spock.Simple: ConnBuilder :: IO a -> (a -> IO ()) -> PoolCfg -> ConnBuilder a
+ Web.Spock.Simple: DELETE :: StdMethod
+ Web.Spock.Simple: GET :: StdMethod
+ Web.Spock.Simple: HEAD :: StdMethod
+ Web.Spock.Simple: OPTIONS :: StdMethod
+ Web.Spock.Simple: PATCH :: StdMethod
+ Web.Spock.Simple: PCConn :: (ConnBuilder a) -> PoolOrConn a
+ Web.Spock.Simple: PCPool :: (Pool a) -> PoolOrConn a
+ Web.Spock.Simple: POST :: StdMethod
+ Web.Spock.Simple: PUT :: StdMethod
+ Web.Spock.Simple: PoolCfg :: Int -> Int -> NominalDiffTime -> PoolCfg
+ Web.Spock.Simple: SessionCfg :: Text -> NominalDiffTime -> Int -> a -> SessionCfg a
+ Web.Spock.Simple: TRACE :: StdMethod
+ 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: class (Hashable a, Eq a, Typeable a) => SafeAction conn sess st a
+ Web.Spock.Simple: clearAllSessions :: SpockAction conn sess st ()
+ Web.Spock.Simple: cookie :: MonadIO m => Text -> ActionT m (Maybe Text)
+ Web.Spock.Simple: data ActionT m a
+ 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 SpockRoute
+ Web.Spock.Simple: data SpockT m a
+ Web.Spock.Simple: data StdMethod :: *
+ 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: delete :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()
+ 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: get :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()
+ 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: head :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()
+ Web.Spock.Simple: header :: MonadIO m => Text -> ActionT m (Maybe Text)
+ Web.Spock.Simple: hookRoute :: Monad m => StdMethod -> SpockRoute -> ActionT m () -> SpockT m ()
+ Web.Spock.Simple: html :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Simple: instance (Monad m, Functor m) => Applicative (SpockT m)
+ Web.Spock.Simple: instance Eq SpockRoute
+ Web.Spock.Simple: instance Functor m => Functor (SpockT m)
+ Web.Spock.Simple: instance IsString SpockRoute
+ Web.Spock.Simple: instance Monad m => Monad (SpockT m)
+ Web.Spock.Simple: instance MonadIO m => MonadIO (SpockT m)
+ Web.Spock.Simple: instance MonadTrans SpockT
+ Web.Spock.Simple: instance Ord SpockRoute
+ Web.Spock.Simple: instance Read SpockRoute
+ Web.Spock.Simple: instance Show SpockRoute
+ 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: middleware :: Monad m => Middleware -> SpockT m ()
+ 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: patch :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()
+ Web.Spock.Simple: pc_keepOpenTime :: PoolCfg -> NominalDiffTime
+ Web.Spock.Simple: pc_resPerStripe :: PoolCfg -> Int
+ Web.Spock.Simple: pc_stripes :: PoolCfg -> Int
+ Web.Spock.Simple: post :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()
+ Web.Spock.Simple: put :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()
+ 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: runSafeAction :: SafeAction conn sess st a => a -> SpockAction conn sess st ()
+ Web.Spock.Simple: runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a
+ Web.Spock.Simple: safeActionPath :: (SafeAction conn sess st a, HasSpock (SpockAction conn sess st), SpockConn (SpockAction conn sess st) ~ conn, SpockSession (SpockAction conn sess st) ~ sess, SpockState (SpockAction conn sess st) ~ st) => a -> SpockAction conn sess st Text
+ 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: spock :: Int -> SessionCfg sess -> PoolOrConn conn -> st -> SpockM conn sess st () -> IO ()
+ Web.Spock.Simple: spockApp :: MonadIO m => (forall a. m a -> IO a) -> SpockT m () -> IO Application
+ Web.Spock.Simple: spockT :: MonadIO m => Port -> (forall a. m a -> IO a) -> SpockT m () -> IO ()
+ Web.Spock.Simple: subcomponent :: Monad m => SpockRoute -> SpockT m () -> SpockT m ()
+ Web.Spock.Simple: text :: MonadIO m => Text -> ActionT m a
+ Web.Spock.Simple: type SpockAction conn sess st = ActionT (WebStateM conn sess st)
+ Web.Spock.Simple: type SpockM conn sess st a = SpockT (WebStateM conn sess st) 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.Specs.All: allSpecs :: Spec

Files

Spock.cabal view
@@ -1,5 +1,5 @@ name:                Spock-version:             0.6.6.1+version:             0.7.0.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 Homepage:            https://github.com/agrafix/Spock@@ -14,16 +14,21 @@  library   hs-source-dirs:      src-  exposed-modules:     Web.Spock,-                       Web.Spock.Routing,-                       Web.Spock.Wire-  other-modules:       Web.Spock.SessionManager,-                       Web.Spock.Monad,-                       Web.Spock.Types,-                       Web.Spock.SafeActions,-                       Web.Spock.Digestive,-                       Web.Spock.Util,-                       Web.Spock.Core+  exposed-modules:     Web.Spock.Simple,+                       Web.Spock.Safe,+                       Web.Spock.Specs.All+  other-modules:       Web.Spock.Internal.SessionManager,+                       Web.Spock.Internal.Monad,+                       Web.Spock.Internal.Types,+                       Web.Spock.Internal.Digestive,+                       Web.Spock.Internal.Util,+                       Web.Spock.Internal.Core,+                       Web.Spock.Internal.CoreAction,+                       Web.Spock.Internal.Wrapper,+                       Web.Spock.Internal.Wire,+                       Web.Spock.Specs.SimpleSpec,+                       Web.Spock.Specs.SafeSpec,+                       Web.Spock.Specs.FrameworkSpecHelper   build-depends:       aeson >= 0.6.2.1 && <0.9,                        base >= 4 && < 5,                        base64-bytestring ==1.*,@@ -35,6 +40,9 @@                        digestive-functors ==0.7.*,                        directory ==1.2.*,                        hashable ==1.2.*,+                       hspec2 >=0.4 && <0.5,+                       hspec-wai >=0.5 && <0.6,+                       HList >=0.3,                        http-types ==0.8.*,                        monad-control ==0.3.*,                        mtl >=2.1 && <2.3,@@ -42,8 +50,10 @@                        path-pieces >=0.1.4,                        random ==1.*,                        regex-compat ==0.95.*,+                       reroute >=0.1 && <0.2,                        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,@@ -62,13 +72,9 @@   type:                exitcode-stdio-1.0   hs-source-dirs:      test   main-is:             Spec.hs-  other-modules:       Web.Spock.WireSpec, Web.Spock.RoutingSpec-  build-depends:       QuickCheck,-                       Spock,+  build-depends:       hspec2 >=0.4 && <0.5,                        base >= 4 && < 5,-                       hspec2 >=0.4 && <0.5,-                       vector ==0.10.*,-                       unordered-containers ==0.2.*+                       Spock   ghc-options: -Wall -fno-warn-orphans  source-repository head
− src/Web/Spock.hs
@@ -1,106 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE DoAndIfThenElse #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-module Web.Spock-    ( -- * Spock's core-      spock, SpockM, SpockAction-    , spockT, SpockT, ActionT-     -- * Defining routes-    , get, post, C.head, put, delete, patch, defRoute-    , subcomponent, Http.StdMethod (..)-    , combineRoute-     -- * 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-    , 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-      -- * Safe actions-    , SafeAction (..)-    , safeActionPath-      -- * Digestive Functors-    , runForm-      -- * Internals for extending Spock-    , getSpockHeart, runSpockIO, WebStateM, WebState-    )-where--import Web.Spock.Core-import Web.Spock.Digestive-import Web.Spock.Monad-import Web.Spock.SafeActions-import Web.Spock.SessionManager-import Web.Spock.Types-import qualified Web.Spock.Core as C--import Control.Monad.Trans.Reader-import Control.Monad.Trans.Resource-import Data.Pool-import qualified Network.HTTP.Types as Http---- | Run a spock application using the warp server, a given db storageLayer and an initial state.--- Spock works with database libraries that already implement connection pooling and--- with those that don't come with it out of the box. For more see the 'PoolOrConn' type.-spock :: Int -> SessionCfg sess -> PoolOrConn conn -> st -> SpockM conn sess st () -> IO ()-spock port sessionCfg poolOrConn initialState defs =-    do sessionMgr <- createSessionManager sessionCfg-       connectionPool <--           case poolOrConn of-             PCPool p ->-                 return p-             PCConn cb ->-                 let pc = cb_poolConfiguration cb-                 in createPool (cb_createConn cb) (cb_destroyConn cb)-                        (pc_stripes pc) (pc_keepOpenTime pc)-                        (pc_resPerStripe pc)-       let internalState =-               WebState-               { web_dbConn = connectionPool-               , web_sessionMgr = sessionMgr-               , web_state = initialState-               }-           runM m = runResourceT $ runReaderT (runWebStateM m) internalState--       spockT port runM $-               do hookSafeActions-                  defs-                  middleware (sm_middleware sessionMgr)---- | 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/Core.hs
@@ -1,279 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE DoAndIfThenElse #-}-module Web.Spock.Core-    ( SpockT, ActionT, spockT-    , middleware, UploadedFile (..)-    , defRoute, get, post, head, put, delete, patch-    , request, header, cookie, body, jsonBody, jsonBody'-    , files, params, param, param', setStatus, setHeader, redirect-    , jumpNext-    , setCookie, setCookie'-    , bytes, lazyBytes, text, html, file, json, blaze-    , combineRoute, subcomponent-    , requireBasicAuth-    )-where--import Control.Arrow (first)-import Control.Monad-import Control.Monad.Error-import Control.Monad.Reader-import Control.Monad.State hiding (get, put)-import Data.Monoid-import Data.Time-import Network.HTTP.Types.Method-import Network.HTTP.Types.Status-import Prelude hiding (head)-import System.Locale-import Text.Blaze.Html (Html)-import Text.Blaze.Html.Renderer.Utf8 (renderHtml)-import Web.PathPieces-import Web.Spock.Routing-import Web.Spock.Wire-import qualified Data.Aeson as A-import qualified Data.ByteString as BS-import qualified Data.ByteString.Base64 as B64-import qualified Data.ByteString.Lazy as BSL-import qualified Data.CaseInsensitive as CI-import qualified Data.HashMap.Strict as HM-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Network.Wai as Wai-import qualified Network.Wai.Handler.Warp as Warp---- | Run a raw spock server on a defined port. If you don't need--- a custom base monad you can just supply 'id' as lift function.-spockT :: MonadIO m-       => Warp.Port-       -> (forall a. m a -> IO a)-       -> SpockT m ()-       -> IO ()-spockT port liftSpock routeDefs =-    do spockApp <- buildApp liftSpock routeDefs-       putStrLn $ "Spock is up and running on port " ++ show port-       Warp.run port spockApp---- | Specify an action that will be run when the HTTP verb 'GET' and the given route match-get :: MonadIO m => T.Text -> ActionT m () -> SpockT m ()-get = defRoute GET---- | Specify an action that will be run when the HTTP verb 'POST' and the given route match-post :: MonadIO m => T.Text -> ActionT m () -> SpockT m ()-post = defRoute POST---- | Specify an action that will be run when the HTTP verb 'HEAD' and the given route match-head :: MonadIO m => T.Text -> ActionT m () -> SpockT m ()-head = defRoute HEAD---- | Specify an action that will be run when the HTTP verb 'PUT' and the given route match-put :: MonadIO m => T.Text -> ActionT m () -> SpockT m ()-put = defRoute PUT---- | Specify an action that will be run when the HTTP verb 'DELETE' and the given route match-delete :: MonadIO m => T.Text -> ActionT m () -> SpockT m ()-delete = defRoute DELETE---- | Specify an action that will be run when the HTTP verb 'PATCH' and the given route match-patch :: MonadIO m => T.Text -> ActionT m () -> SpockT m ()-patch = defRoute PATCH---- | Get the original Wai Request object-request :: MonadIO m => ActionT m Wai.Request-request = asks ri_request---- | Read a header-header :: MonadIO m => T.Text -> ActionT m (Maybe T.Text)-header t =-    do req <- request-       return $ fmap T.decodeUtf8 (lookup (CI.mk (T.encodeUtf8 t)) $ Wai.requestHeaders req)---- | Read a cookie-cookie :: MonadIO m => T.Text -> ActionT m (Maybe T.Text)-cookie name =-    do req <- request-       return $ lookup "cookie" (Wai.requestHeaders req) >>= lookup name . parseCookies . T.decodeUtf8-    where-      parseCookies :: T.Text -> [(T.Text, T.Text)]-      parseCookies = map parseCookie . T.splitOn ";" . T.concat . T.words-      parseCookie = first T.init . T.breakOnEnd "="---- | Get the raw request body-body :: MonadIO m => ActionT m BS.ByteString-body =-    do req <- request-       let parseBody = liftIO $ Wai.requestBody req-           parseAll chunks =-               do bs <- parseBody-                  if BS.null bs-                  then return chunks-                  else parseAll (chunks `BS.append` bs)-       parseAll BS.empty---- | Parse the request body as json-jsonBody :: (MonadIO m, A.FromJSON a) => ActionT m (Maybe a)-jsonBody =-    do b <- body-       return $ A.decodeStrict b---- | Parse the request body as json and fails with 500 status code on error-jsonBody' :: (MonadIO m, A.FromJSON a) => ActionT m a-jsonBody' =-    do b <- body-       case A.eitherDecodeStrict' b of-         Left err ->-             do setStatus status500-                text (T.pack $ "Failed to parse json: " ++ err)-         Right val ->-             return val---- | Get uploaded files-files :: MonadIO m => ActionT m (HM.HashMap T.Text UploadedFile)-files =-    asks ri_files---- | Get all request params-params :: MonadIO m => ActionT m [(T.Text, T.Text)]-params =-    do p <- asks ri_params-       qp <- asks ri_queryParams-       return (qp ++ (map (\(k, v) -> (unCaptureVar k, v)) $ HM.toList p))---- | Read a request param. Spock looks in route captures first, then in POST variables and at last in GET variables-param :: (PathPiece p, MonadIO m) => T.Text -> ActionT m (Maybe p)-param k =-    do p <- asks ri_params-       qp <- asks ri_queryParams-       case HM.lookup (CaptureVar k) p of-         Just val ->-             case fromPathPiece val of-               Nothing ->-                   do liftIO $ putStrLn ("Cannot parse " ++ show k ++ " with value " ++ show val ++ " as path piece!")-                      jumpNext-               Just pathPieceVal ->-                   return $ Just pathPieceVal-         Nothing ->-             return $ join $ fmap fromPathPiece (lookup k qp)---- | Like 'param', but outputs an error when a param is missing-param' :: (PathPiece p, MonadIO m) => T.Text -> ActionT m p-param' k =-    do mParam <- param k-       case mParam of-         Nothing ->-             do setStatus status500-                text (T.concat [ "Missing parameter ", k ])-         Just val ->-             return val---- | Set a response status-setStatus :: MonadIO m => Status -> ActionT m ()-setStatus s =-    modify $ \rs -> rs { rs_status = s }---- | Set a response header. Overwrites already defined headers-setHeader :: MonadIO m => T.Text -> T.Text -> ActionT m ()-setHeader k v =-    modify $ \rs -> rs { rs_responseHeaders = ((k, v) : filter ((/= k) . fst) (rs_responseHeaders rs)) }---- | Abort the current action and jump the next one matching the route-jumpNext :: MonadIO m => ActionT m a-jumpNext = throwError ActionTryNext---- | Redirect to a given url-redirect :: MonadIO m => T.Text -> ActionT m a-redirect = throwError . ActionRedirect---- | Set a cookie living for a given number of seconds-setCookie :: MonadIO m => T.Text -> T.Text -> NominalDiffTime -> ActionT m ()-setCookie name value validSeconds =-    do now <- liftIO getCurrentTime-       setCookie' name value (validSeconds `addUTCTime` now)---- | Set a cookie living until a specific 'UTCTime'-setCookie' :: MonadIO m => T.Text -> T.Text -> UTCTime -> ActionT m ()-setCookie' name value validUntil =-    setHeader "Set-Cookie" rendered-    where-      rendered =-          let formattedTime =-                  T.pack $ formatTime defaultTimeLocale "%a, %d-%b-%Y %X %Z" validUntil-          in T.concat [ name-                      , "="-                      , value-                      , "; path=/; expires="-                      , formattedTime-                      , ";"-                      ]---- | Send a 'ByteString' as response body. Provide your own "Content-Type"-bytes :: MonadIO m => BS.ByteString -> ActionT m a-bytes val =-    lazyBytes $ BSL.fromStrict val---- | Send a lazy 'ByteString' as response body. Provide your own "Content-Type"-lazyBytes :: MonadIO m => BSL.ByteString -> ActionT m a-lazyBytes val =-    do modify $ \rs -> rs { rs_responseBody = ResponseLBS val }-       throwError ActionDone---- | Send text as a response body. Content-Type will be "text/plain"-text :: MonadIO m => T.Text -> ActionT m a-text val =-    do setHeader "Content-Type" "text/plain"-       bytes $ T.encodeUtf8 val---- | Send a text as response body. Content-Type will be "text/plain"-html :: MonadIO m => T.Text -> ActionT m a-html val =-    do setHeader "Content-Type" "text/html"-       bytes $ T.encodeUtf8 val---- | Send a file as response-file :: MonadIO m => T.Text -> FilePath -> ActionT m a-file contentType filePath =-     do setHeader "Content-Type" contentType-        modify $ \rs -> rs { rs_responseBody = ResponseFile filePath }-        throwError ActionDone---- | Send json as response. Content-Type will be "application/json"-json :: (A.ToJSON a, MonadIO m) => a -> ActionT m b-json val =-    do setHeader "Content-Type" "application/json"-       lazyBytes $ A.encode val---- | Send blaze html as response. Content-Type will be "text/html"-blaze :: MonadIO m => Html -> ActionT m a-blaze val =-    do setHeader "Content-Type" "text/html"-       lazyBytes $ renderHtml val---- | Basic authentification--- provide a title for the prompt and a function to validate--- user and password. Usage example:------ > get "/my-secret-page" $--- >   requireBasicAuth "Secret Page" (\user pass -> return (user == "admin" && pass == "1234")) $--- >   do html "This is top secret content. Login using that secret code I provided ;-)"----requireBasicAuth :: MonadIO m => T.Text -> (T.Text -> T.Text -> m Bool) -> ActionT m a -> ActionT m a-requireBasicAuth realmTitle authFun cont =-    do mAuthHeader <- header "Authorization"-       case mAuthHeader of-         Nothing ->-             authFailed-         Just authHeader ->-             let (_, rawValue) =-                     T.breakOn " " authHeader-                 (user, rawPass) =-                     (T.breakOn ":" . T.decodeUtf8 . B64.decodeLenient . T.encodeUtf8 . T.strip) rawValue-                 pass = T.drop 1 rawPass-             in do isOk <- lift $ authFun user pass-                   if isOk-                   then cont-                   else authFailed-    where-      authFailed =-          do setStatus status401-             setHeader "WWW-Authenticate" ("Basic realm=\"" <> realmTitle <> "\"")-             html "<h1>Authentication required.</h1>"
− src/Web/Spock/Digestive.hs
@@ -1,36 +0,0 @@-{-# LANGUAGE DoAndIfThenElse #-}-module Web.Spock.Digestive-    ( runForm )-where--import Web.Spock.Core--import Control.Applicative-import Control.Monad.Trans-import Network.HTTP.Types-import Network.Wai-import Text.Digestive.Form-import Text.Digestive.Types-import Text.Digestive.View-import qualified Data.HashMap.Strict as HM-import qualified Data.Text as T---- | Run a digestive functors form-runForm :: (Functor m, MonadIO m)-        => T.Text -- ^ form name-        -> Form v (ActionT m) a-        -> ActionT m (View v, Maybe a)-runForm formName form =-    do httpMethod <- requestMethod <$> request-       if httpMethod == methodGet-       then do f <- getForm formName form-               return (f, Nothing)-       else postForm formName form (const $ return localEnv)-    where-      localEnv path =-          do let name = fromPath $ path-                 applyParam f =-                     map (f . snd) . filter ((== name) . fst)-             vars <- (applyParam (TextInput)) <$> params-             sentFiles <- (applyParam (FileInput . T.unpack . uf_name) . HM.toList) <$> files-             return (vars ++ sentFiles)
+ src/Web/Spock/Internal/Core.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+module Web.Spock.Internal.Core+    ( SpockAllT+    , spockAllT+    , middleware+    , hookRoute+    , subcomponent+    )+where++import Web.Spock.Internal.Wire++import Control.Monad.Error+import Prelude hiding (head)+import Web.Routing.AbstractRouter+import qualified Network.Wai.Handler.Warp as Warp++-- | Run a raw spock server on a defined port. If you don't need+-- a custom base monad you can just supply 'id' as lift function.+spockAllT :: (MonadIO m, AbstractRouter r, RouteAppliedAction r ~ ActionT m ())+       => r+       -> Warp.Port+       -> (forall a. m a -> IO a)+       -> SpockAllT r m ()+       -> IO ()+spockAllT registryIf port liftSpock routeDefs =+    do spockApp <- buildApp registryIf liftSpock routeDefs+       putStrLn $ "Spock is up and running on port " ++ show port+       Warp.run port spockApp
+ src/Web/Spock/Internal/CoreAction.hs view
@@ -0,0 +1,240 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DoAndIfThenElse #-}+module Web.Spock.Internal.CoreAction+    ( ActionT+    , UploadedFile (..)+    , request, header, cookie, body, jsonBody, jsonBody'+    , files, params, param, param', setStatus, setHeader, redirect+    , jumpNext+    , setCookie, setCookie'+    , bytes, lazyBytes, text, html, file, json, blaze+    , requireBasicAuth+    )+where++import Web.Spock.Internal.Wire++import Control.Arrow (first)+import Control.Monad+import Control.Monad.Error+import Control.Monad.Reader+import Control.Monad.State hiding (get, put)+import Data.Monoid+import Data.Time+import Network.HTTP.Types.Status+import Prelude hiding (head)+import System.Locale+import Text.Blaze.Html (Html)+import Text.Blaze.Html.Renderer.Utf8 (renderHtml)+import Web.PathPieces+import Web.Routing.AbstractRouter+import qualified Data.Aeson as A+import qualified Data.ByteString as BS+import qualified Data.ByteString.Base64 as B64+import qualified Data.ByteString.Lazy as BSL+import qualified Data.CaseInsensitive as CI+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Network.Wai as Wai++-- | Get the original Wai Request object+request :: MonadIO m => ActionT m Wai.Request+request = asks ri_request++-- | Read a header+header :: MonadIO m => T.Text -> ActionT m (Maybe T.Text)+header t =+    do req <- request+       return $ fmap T.decodeUtf8 (lookup (CI.mk (T.encodeUtf8 t)) $ Wai.requestHeaders req)++-- | Read a cookie+cookie :: MonadIO m => T.Text -> ActionT m (Maybe T.Text)+cookie name =+    do req <- request+       return $ lookup "cookie" (Wai.requestHeaders req) >>= lookup name . parseCookies . T.decodeUtf8+    where+      parseCookies :: T.Text -> [(T.Text, T.Text)]+      parseCookies = map parseCookie . T.splitOn ";" . T.concat . T.words+      parseCookie = first T.init . T.breakOnEnd "="++-- | Get the raw request body+body :: MonadIO m => ActionT m BS.ByteString+body =+    do req <- request+       let parseBody = liftIO $ Wai.requestBody req+           parseAll chunks =+               do bs <- parseBody+                  if BS.null bs+                  then return chunks+                  else parseAll (chunks `BS.append` bs)+       parseAll BS.empty++-- | Parse the request body as json+jsonBody :: (MonadIO m, A.FromJSON a) => ActionT m (Maybe a)+jsonBody =+    do b <- body+       return $ A.decodeStrict b++-- | Parse the request body as json and fails with 500 status code on error+jsonBody' :: (MonadIO m, A.FromJSON a) => ActionT m a+jsonBody' =+    do b <- body+       case A.eitherDecodeStrict' b of+         Left err ->+             do setStatus status500+                text (T.pack $ "Failed to parse json: " ++ err)+         Right val ->+             return val++-- | Get uploaded files+files :: MonadIO m => ActionT m (HM.HashMap T.Text UploadedFile)+files =+    asks ri_files++-- | Get all request params+params :: MonadIO m => ActionT m [(T.Text, T.Text)]+params =+    do p <- asks ri_params+       qp <- asks ri_queryParams+       return (qp ++ (map (\(k, v) -> (unCaptureVar k, v)) $ HM.toList p))++-- | Read a request param. Spock looks in route captures first, then in POST variables and at last in GET variables+param :: (PathPiece p, MonadIO m) => T.Text -> ActionT m (Maybe p)+param k =+    do p <- asks ri_params+       qp <- asks ri_queryParams+       case HM.lookup (CaptureVar k) p of+         Just val ->+             case fromPathPiece val of+               Nothing ->+                   do liftIO $ putStrLn ("Cannot parse " ++ show k ++ " with value " ++ show val ++ " as path piece!")+                      jumpNext+               Just pathPieceVal ->+                   return $ Just pathPieceVal+         Nothing ->+             return $ join $ fmap fromPathPiece (lookup k qp)++-- | Like 'param', but outputs an error when a param is missing+param' :: (PathPiece p, MonadIO m) => T.Text -> ActionT m p+param' k =+    do mParam <- param k+       case mParam of+         Nothing ->+             do setStatus status500+                text (T.concat [ "Missing parameter ", k ])+         Just val ->+             return val++-- | Set a response status+setStatus :: MonadIO m => Status -> ActionT m ()+setStatus s =+    modify $ \rs -> rs { rs_status = s }++-- | Set a response header. Overwrites already defined headers+setHeader :: MonadIO m => T.Text -> T.Text -> ActionT m ()+setHeader k v =+    modify $ \rs -> rs { rs_responseHeaders = ((k, v) : filter ((/= k) . fst) (rs_responseHeaders rs)) }++-- | Abort the current action and jump the next one matching the route+jumpNext :: MonadIO m => ActionT m a+jumpNext = throwError ActionTryNext++-- | Redirect to a given url+redirect :: MonadIO m => T.Text -> ActionT m a+redirect = throwError . ActionRedirect++-- | Set a cookie living for a given number of seconds+setCookie :: MonadIO m => T.Text -> T.Text -> NominalDiffTime -> ActionT m ()+setCookie name value validSeconds =+    do now <- liftIO getCurrentTime+       setCookie' name value (validSeconds `addUTCTime` now)++-- | Set a cookie living until a specific 'UTCTime'+setCookie' :: MonadIO m => T.Text -> T.Text -> UTCTime -> ActionT m ()+setCookie' name value validUntil =+    setHeader "Set-Cookie" rendered+    where+      rendered =+          let formattedTime =+                  T.pack $ formatTime defaultTimeLocale "%a, %d-%b-%Y %X %Z" validUntil+          in T.concat [ name+                      , "="+                      , value+                      , "; path=/; expires="+                      , formattedTime+                      , ";"+                      ]++-- | Send a 'ByteString' as response body. Provide your own "Content-Type"+bytes :: MonadIO m => BS.ByteString -> ActionT m a+bytes val =+    lazyBytes $ BSL.fromStrict val++-- | Send a lazy 'ByteString' as response body. Provide your own "Content-Type"+lazyBytes :: MonadIO m => BSL.ByteString -> ActionT m a+lazyBytes val =+    do modify $ \rs -> rs { rs_responseBody = ResponseLBS val }+       throwError ActionDone++-- | Send text as a response body. Content-Type will be "text/plain"+text :: MonadIO m => T.Text -> ActionT m a+text val =+    do setHeader "Content-Type" "text/plain"+       bytes $ T.encodeUtf8 val++-- | Send a text as response body. Content-Type will be "text/plain"+html :: MonadIO m => T.Text -> ActionT m a+html val =+    do setHeader "Content-Type" "text/html"+       bytes $ T.encodeUtf8 val++-- | Send a file as response+file :: MonadIO m => T.Text -> FilePath -> ActionT m a+file contentType filePath =+     do setHeader "Content-Type" contentType+        modify $ \rs -> rs { rs_responseBody = ResponseFile filePath }+        throwError ActionDone++-- | Send json as response. Content-Type will be "application/json"+json :: (A.ToJSON a, MonadIO m) => a -> ActionT m b+json val =+    do setHeader "Content-Type" "application/json"+       lazyBytes $ A.encode val++-- | Send blaze html as response. Content-Type will be "text/html"+blaze :: MonadIO m => Html -> ActionT m a+blaze val =+    do setHeader "Content-Type" "text/html"+       lazyBytes $ renderHtml val++-- | Basic authentification+-- provide a title for the prompt and a function to validate+-- user and password. Usage example:+--+-- > get "/my-secret-page" $+-- >   requireBasicAuth "Secret Page" (\user pass -> return (user == "admin" && pass == "1234")) $+-- >   do html "This is top secret content. Login using that secret code I provided ;-)"+--+requireBasicAuth :: MonadIO m => T.Text -> (T.Text -> T.Text -> m Bool) -> ActionT m a -> ActionT m a+requireBasicAuth realmTitle authFun cont =+    do mAuthHeader <- header "Authorization"+       case mAuthHeader of+         Nothing ->+             authFailed+         Just authHeader ->+             let (_, rawValue) =+                     T.breakOn " " authHeader+                 (user, rawPass) =+                     (T.breakOn ":" . T.decodeUtf8 . B64.decodeLenient . T.encodeUtf8 . T.strip) rawValue+                 pass = T.drop 1 rawPass+             in do isOk <- lift $ authFun user pass+                   if isOk+                   then cont+                   else authFailed+    where+      authFailed =+          do setStatus status401+             setHeader "WWW-Authenticate" ("Basic realm=\"" <> realmTitle <> "\"")+             html "<h1>Authentication required.</h1>"
+ src/Web/Spock/Internal/Digestive.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DoAndIfThenElse #-}+module Web.Spock.Internal.Digestive+    ( runForm )+where++import Web.Spock.Internal.CoreAction++import Control.Applicative+import Control.Monad.Trans+import Network.HTTP.Types+import Network.Wai+import Text.Digestive.Form+import Text.Digestive.Types+import Text.Digestive.View+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T++-- | Run a digestive functors form+runForm :: (Functor m, MonadIO m)+        => T.Text -- ^ form name+        -> Form v (ActionT m) a+        -> ActionT m (View v, Maybe a)+runForm formName form =+    do httpMethod <- requestMethod <$> request+       if httpMethod == methodGet+       then do f <- getForm formName form+               return (f, Nothing)+       else postForm formName form (const $ return localEnv)+    where+      localEnv path =+          do let name = fromPath $ path+                 applyParam f =+                     map (f . snd) . filter ((== name) . fst)+             vars <- (applyParam (TextInput)) <$> params+             sentFiles <- (applyParam (FileInput . T.unpack . uf_name) . HM.toList) <$> files+             return (vars ++ sentFiles)
+ src/Web/Spock/Internal/Monad.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeFamilies #-}+module Web.Spock.Internal.Monad where++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++instance MonadTrans t => HasSpock (t (WebStateM conn sess st)) where+    type SpockConn (t (WebStateM conn sess st)) = conn+    type SpockState (t (WebStateM conn sess st)) = st+    type SpockSession (t (WebStateM conn sess st)) = sess+    runQuery a = webM $ runQueryImpl a+    getState = webM $ getStateImpl+    getSessMgr = webM $ getSessMgrImpl++instance HasSpock (WebStateM conn sess st) where+    type SpockConn (WebStateM conn sess st) = conn+    type SpockState (WebStateM conn sess st) = st+    type SpockSession (WebStateM conn sess st) = sess+    runQuery = runQueryImpl+    getState = getStateImpl+    getSessMgr = getSessMgrImpl++runQueryImpl :: (conn -> IO a) -> WebStateM conn sess st a+runQueryImpl query =+    do pool <- asks web_dbConn+       liftIO (withResource pool $ query)++getStateImpl :: WebStateM conn sess st st+getStateImpl = asks web_state++-- | Read the heart of Spock. This is useful if you want to construct your own+-- monads that work with runQuery and getState using "runSpockIO"+getSpockHeart :: MonadTrans t => t (WebStateM conn sess st) (WebState conn sess st)+getSpockHeart = webM ask++-- | Run an action inside of Spocks core monad. This allows you to use runQuery and getState+runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a+runSpockIO st (WebStateM action) =+    runResourceT $+    runReaderT action st++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
@@ -0,0 +1,253 @@+{-# LANGUAGE FlexibleContexts, DeriveGeneric, OverloadedStrings, DoAndIfThenElse, RankNTypes #-}+module Web.Spock.Internal.SessionManager+    ( createSessionManager+    , SessionId, Session(..), SessionManager(..)+    )+where++import Web.Spock.Internal.Types+import Web.Spock.Internal.CoreAction+import Web.Spock.Internal.Util++import Control.Arrow (first)+import Control.Concurrent+import Control.Concurrent.STM+import Control.Monad+import Control.Monad.Trans+import Data.Time+import System.Locale+import System.Random+import qualified Data.ByteString.Base64.URL as B64+import qualified Data.ByteString.Char8 as BSC+import qualified Data.ByteString.Lazy as BSL+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL+import qualified Data.Vault.Lazy as V+import qualified Network.Wai as Wai++createSessionManager :: SessionCfg sess -> IO (SessionManager conn sess st)+createSessionManager cfg =+    do cacheHM <- atomically $ newTVar HM.empty+       vaultKey <- V.newKey+       _ <- forkIO (forever (housekeepSessions cacheHM))+       return $ SessionManager+                  { sm_readSession = readSessionImpl vaultKey cacheHM+                  , sm_writeSession = writeSessionImpl vaultKey cacheHM+                  , sm_modifySession = modifySessionImpl vaultKey cacheHM+                  , sm_clearAllSessions = clearAllSessionsImpl cacheHM+                  , sm_middleware = sessionMiddleware cfg vaultKey cacheHM+                  , sm_addSafeAction = addSafeActionImpl vaultKey cacheHM+                  , sm_lookupSafeAction = lookupSafeActionImpl vaultKey cacheHM+                  , sm_removeSafeAction = removeSafeActionImpl vaultKey cacheHM+                  }++modifySessionBase :: V.Key SessionId+                  -> UserSessions conn sess st+                  -> (Session conn sess st -> Session conn sess st)+                  -> SpockAction conn sess st ()+modifySessionBase vK sessionRef modFun =+    do req <- request+       case V.lookup vK (Wai.vault req) of+         Nothing ->+             error "(3) Internal Spock Session Error. Please report this bug!"+         Just sid ->+             liftIO $ atomically $ modifyTVar' sessionRef (HM.adjust modFun sid)++readSessionBase :: V.Key SessionId+                -> UserSessions conn sess st+                -> SpockAction conn sess st (Session conn sess st)+readSessionBase vK sessionRef =+    do req <- request+       case V.lookup vK (Wai.vault req) of+         Nothing ->+             error "(1) Internal Spock Session Error. Please report this bug!"+         Just sid ->+             do sessions <- liftIO $ atomically $ readTVar sessionRef+                case HM.lookup sid sessions of+                  Nothing ->+                      error "(2) Internal Spock Session Error. Please report this bug!"+                  Just session ->+                      return session++addSafeActionImpl :: V.Key SessionId+                  -> UserSessions conn sess st+                  -> PackedSafeAction conn sess st+                  -> SpockAction conn sess st SafeActionHash+addSafeActionImpl vaultKey sessionMapVar safeAction =+    do base <- readSessionBase vaultKey sessionMapVar+       case HM.lookup safeAction (sas_reverse (sess_safeActions base)) of+         Just safeActionHash ->+             return safeActionHash+         Nothing ->+             do safeActionHash <- liftIO (randomHash 40)+                let f sas =+                        sas+                        { sas_forward = HM.insert safeActionHash safeAction (sas_forward sas)+                        , sas_reverse = HM.insert safeAction safeActionHash (sas_reverse sas)+                        }+                modifySessionBase vaultKey sessionMapVar (\s -> s { sess_safeActions = f (sess_safeActions s) })+                return safeActionHash++lookupSafeActionImpl :: V.Key SessionId+                     -> UserSessions conn sess st+                     -> SafeActionHash+                     -> SpockAction conn sess st (Maybe (PackedSafeAction conn sess st))+lookupSafeActionImpl vaultKey sessionMapVar hash =+    do base <- readSessionBase vaultKey sessionMapVar+       return $ HM.lookup hash (sas_forward (sess_safeActions base))++removeSafeActionImpl :: V.Key SessionId+                     -> UserSessions conn sess st+                     -> PackedSafeAction conn sess st+                     -> SpockAction conn sess st ()+removeSafeActionImpl vaultKey sessionMapVar action =+    modifySessionBase vaultKey sessionMapVar (\s -> s { sess_safeActions = f (sess_safeActions s ) })+    where+      f sas =+          sas+          { sas_forward =+              case HM.lookup action (sas_reverse sas) of+                Just h -> HM.delete h (sas_forward sas)+                Nothing -> sas_forward sas+          , sas_reverse = HM.delete action (sas_reverse sas)+          }++readSessionImpl :: V.Key SessionId+                -> UserSessions conn sess st+                -> SpockAction conn sess st sess+readSessionImpl vK sessionRef =+    do base <- readSessionBase vK sessionRef+       return (sess_data base)++writeSessionImpl :: V.Key SessionId+                 -> UserSessions conn sess st+                 -> sess+                 -> SpockAction conn sess st ()+writeSessionImpl vK sessionRef value =+    modifySessionImpl vK sessionRef (const value)++modifySessionImpl :: V.Key SessionId+                  -> UserSessions conn sess st+                  -> (sess -> sess)+                  -> SpockAction conn sess st ()+modifySessionImpl vK sessionRef f =+    do let modFun session =+                        session { sess_data = f (sess_data session) }+       modifySessionBase vK sessionRef modFun++sessionMiddleware :: SessionCfg sess+                  -> V.Key SessionId+                  -> UserSessions conn sess st+                  -> Wai.Middleware+sessionMiddleware cfg vK sessionRef app req respond =+    case getCookieFromReq (sc_cookieName cfg) of+      Just sid ->+          do mSess <- loadSessionImpl sessionRef sid+             case mSess of+               Nothing ->+                   mkNew+               Just sess ->+                   withSess False sess+      Nothing ->+          mkNew+    where+      getCookieFromReq name =+          lookup "cookie" (Wai.requestHeaders req) >>=+                 lookup name . parseCookies . T.decodeUtf8+      renderCookie name value validUntil =+          let formattedTime =+                  TL.pack $ formatTime defaultTimeLocale "%a, %d-%b-%Y %X %Z" validUntil+          in TL.concat [ TL.fromStrict name+                       , "="+                       , TL.fromStrict value+                       , "; path=/; expires="+                       , formattedTime+                       , ";"+                       ]+      parseCookies :: T.Text -> [(T.Text, T.Text)]+      parseCookies = map parseCookie . T.splitOn ";" . T.concat . T.words+      parseCookie = first T.init . T.breakOnEnd "="++      defVal = sc_emptySession cfg+      v = Wai.vault req+      addCookie sess responseHeaders =+          let cookieContent =+                  renderCookie (sc_cookieName cfg) (sess_id sess) (sess_validUntil sess)+              cookieC = ("Set-Cookie", BSL.toStrict $ TL.encodeUtf8 cookieContent)+          in (cookieC : responseHeaders)+      withSess shouldSetCookie sess =+          app (req { Wai.vault = V.insert vK (sess_id sess) v }) $ \unwrappedResp ->+              respond $+              if shouldSetCookie+              then mapReqHeaders (addCookie sess) unwrappedResp+              else unwrappedResp+      mkNew =+          do newSess <- newSessionImpl cfg sessionRef defVal+             withSess True newSess++newSessionImpl :: SessionCfg sess+               -> UserSessions conn sess st+               -> sess+               -> IO (Session conn sess st)+newSessionImpl sessCfg sessionRef content =+    do sess <- createSession sessCfg content+       atomically $ modifyTVar' sessionRef (\hm -> HM.insert (sess_id sess) sess hm)+       return $! sess++loadSessionImpl :: UserSessions conn sess st+                -> SessionId+                -> IO (Maybe (Session conn sess st))+loadSessionImpl sessionRef sid =+    do sessHM <- atomically $ readTVar sessionRef+       now <- getCurrentTime+       case HM.lookup sid sessHM of+         Just sess ->+             do if (sess_validUntil sess) > now+                then return $ Just sess+                else do deleteSessionImpl sessionRef sid+                        return Nothing+         Nothing ->+             return Nothing++deleteSessionImpl :: UserSessions conn sess st+                  -> SessionId+                  -> IO ()+deleteSessionImpl sessionRef sid =+    do atomically $ modifyTVar' sessionRef (\hm -> HM.delete sid hm)+       return ()++clearAllSessionsImpl :: UserSessions conn sess st+                     -> SpockAction conn sess st ()+clearAllSessionsImpl sessionRef =+    liftIO $ atomically $ modifyTVar' sessionRef (const HM.empty)++housekeepSessions :: UserSessions conn sess st -> IO ()+housekeepSessions sessionRef =+    do now <- getCurrentTime+       atomically $ modifyTVar' sessionRef (killOld now)+       threadDelay (1000 * 1000 * 60) -- 60 seconds+    where+      filterOld now (_, sess) =+          (sess_validUntil sess) > now+      killOld now hm =+          HM.fromList $ filter (filterOld now) $ HM.toList hm++createSession :: SessionCfg sess -> sess -> IO (Session conn sess st)+createSession sessCfg content =+    do sid <- randomHash (sc_sessionIdEntropy sessCfg)+       now <- getCurrentTime+       let validUntil = addUTCTime (sc_sessionTTL sessCfg) now+           emptySafeActions =+               SafeActionStore HM.empty HM.empty+       return (Session sid validUntil content emptySafeActions)++randomHash :: Int -> IO T.Text+randomHash len =+    do gen <- g+       return $ T.replace "=" "" $ T.decodeUtf8 $ B64.encode $ BSC.pack $+              take len $ randoms gen+    where+      g = newStdGen :: IO StdGen
+ src/Web/Spock/Internal/Types.hs view
@@ -0,0 +1,144 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ExistentialQuantification #-}+module Web.Spock.Internal.Types where++import Web.Spock.Internal.Core+import Web.Spock.Internal.CoreAction++import Control.Applicative+import Control.Concurrent.STM+import Control.Monad.Base+import Control.Monad.Reader+import Control.Monad.Trans.Control+import Control.Monad.Trans.Resource+import Data.Hashable+import Data.Pool+import Data.Time.Clock ( UTCTime(..), NominalDiffTime )+import Data.Typeable+import Network.Wai+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T++-- | Inside the SpockAllM monad, you may define routes and middleware.+type SpockAllM r conn sess st a =+    SpockAllT r (WebStateM conn sess st) a++-- | The SpockAction is the monad of all route-actions. You have access+-- to the database, session and state of your application.+type SpockAction conn sess st = ActionT (WebStateM conn sess st)++-- | If Spock should take care of connection pooling, you need to configure+-- it depending on what you need.+data PoolCfg+   = PoolCfg+   { pc_stripes :: Int+   , pc_resPerStripe :: Int+   , pc_keepOpenTime :: NominalDiffTime+   }++-- | The ConnBuilder instructs Spock how to create or close a database connection.+data ConnBuilder a+   = ConnBuilder+   { cb_createConn :: IO a+   , cb_destroyConn :: a -> IO ()+   , cb_poolConfiguration :: PoolCfg+   }++-- | You can feed Spock with either a connection pool, or instructions on how to build+-- a connection pool. See 'ConnBuilder'+data PoolOrConn a+   = PCPool (Pool a)+   | PCConn (ConnBuilder a)++-- | Configuration for the session manager+data SessionCfg a+   = SessionCfg+   { sc_cookieName :: T.Text+   , sc_sessionTTL :: NominalDiffTime+   , sc_sessionIdEntropy :: Int+   , sc_emptySession :: a+   }++data WebState conn sess st+   = WebState+   { web_dbConn :: Pool conn+   , web_sessionMgr :: SessionManager conn sess st+   , web_state :: st+   }++class HasSpock m where+    type SpockConn m :: *+    type SpockState m :: *+    type SpockSession m :: *+    -- | Give you access to a database connectin from the connection pool. The connection is+    -- released back to the pool once the function terminates.+    runQuery :: (SpockConn m -> IO a) -> m a+    -- | Read the application's state. If you wish to have mutable state, you could+    -- use a 'TVar' from the STM packge.+    getState :: m (SpockState m)+    -- | Get the session manager+    getSessMgr :: m (SessionManager (SpockConn m) (SpockSession m) (SpockState m))++-- | SafeActions are actions that need to be protected from csrf attacks+class (Hashable a, Eq a, Typeable a) => SafeAction conn sess st a where+    -- | The body of the safe action. Either GET or POST+    runSafeAction :: a -> SpockAction conn sess st ()++data PackedSafeAction conn sess st+    = forall a. (SafeAction conn sess st a) => PackedSafeAction { unpackSafeAction :: a }++instance Hashable (PackedSafeAction conn sess st) where+    hashWithSalt i (PackedSafeAction a) = hashWithSalt i a++instance Eq (PackedSafeAction conn sess st) where+   (PackedSafeAction a) == (PackedSafeAction b) =+       cast a == Just b++data SafeActionStore conn sess st+   = SafeActionStore+   { sas_forward :: !(HM.HashMap SafeActionHash (PackedSafeAction conn sess st))+   , sas_reverse :: !(HM.HashMap (PackedSafeAction conn sess st) SafeActionHash)+   }++type SafeActionHash = T.Text++newtype WebStateM conn sess st a = WebStateM { runWebStateM :: ReaderT (WebState conn sess st) (ResourceT IO) a }+    deriving (Monad, Functor, Applicative, MonadIO, MonadReader (WebState conn sess st))++instance MonadBase IO (WebStateM conn sess st) where+    liftBase = WebStateM . liftBase++instance MonadBaseControl IO (WebStateM conn sess st) where+    newtype StM (WebStateM conn sess st) a = WStM { unWStM :: StM (ReaderT (WebState conn sess st) (ResourceT IO)) a }+    liftBaseWith f = WebStateM . liftBaseWith $ \runInBase -> f $ liftM WStM . runInBase . runWebStateM+    restoreM = WebStateM . restoreM . unWStM++type SessionId = T.Text+data Session conn sess st+    = Session+    { sess_id :: !SessionId+    , sess_validUntil :: !UTCTime+    , sess_data :: !sess+    , sess_safeActions :: !(SafeActionStore conn sess st)+    }+instance Show (Session conn sess st) where+    show = show . sess_id++type UserSessions conn sess st =+    TVar (HM.HashMap SessionId (Session conn sess st))++data SessionManager conn sess st+   = SessionManager+   { 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 ()+   , sm_middleware :: Middleware+   , sm_addSafeAction :: PackedSafeAction conn sess st -> SpockAction conn sess st SafeActionHash+   , sm_lookupSafeAction :: SafeActionHash -> SpockAction conn sess st (Maybe (PackedSafeAction conn sess st))+   , sm_removeSafeAction :: PackedSafeAction conn sess st -> SpockAction conn sess st ()+   }
+ src/Web/Spock/Internal/Util.hs view
@@ -0,0 +1,12 @@+module Web.Spock.Internal.Util where++import Network.HTTP.Types+import Network.Wai.Internal++mapReqHeaders :: (ResponseHeaders -> ResponseHeaders) -> Response -> Response+mapReqHeaders f resp =+    case resp of+      (ResponseFile s h b1 b2) -> ResponseFile s (f h) b1 b2+      (ResponseBuilder s h b) -> ResponseBuilder s (f h) b+      (ResponseStream s h b) -> ResponseStream s (f h) b+      (ResponseRaw x r) -> ResponseRaw x (mapReqHeaders f r)
+ src/Web/Spock/Internal/Wire.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+module Web.Spock.Internal.Wire where++import Control.Applicative+import Control.Exception+import Control.Monad.RWS.Strict+import Control.Monad.Error+import Control.Monad.Reader.Class ()+import Control.Monad.Trans.Resource+import Data.Hashable+import Data.Maybe+import Network.HTTP.Types.Method+import Network.HTTP.Types.Status+#if MIN_VERSION_base(4,6,0)+import Prelude+#else+import Prelude hiding (catch)+#endif+import System.Directory+import Web.Routing.AbstractRouter+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import qualified Data.CaseInsensitive as CI+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Network.Wai as Wai+import qualified Network.Wai.Parse as P++instance Hashable StdMethod where+    hashWithSalt = hashUsing fromEnum++data UploadedFile+   = UploadedFile+   { uf_name :: T.Text+   , uf_contentType :: T.Text+   , uf_tempLocation :: FilePath+   }++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+   }++data ResponseBody+   = ResponseFile FilePath+   | ResponseLBS BSL.ByteString+   | ResponseRedirect T.Text+   deriving (Show, Eq)++data ResponseState+   = ResponseState+   { rs_responseHeaders :: [(T.Text, T.Text)]+   , rs_status :: Status+   , rs_responseBody :: ResponseBody+   } deriving (Show, Eq)++data ActionInterupt+    = ActionRedirect T.Text+    | ActionTryNext+    | ActionError String+    | ActionDone+    deriving (Show)++instance Error ActionInterupt where+    noMsg = ActionError "Unkown Internal Action Error"+    strMsg = ActionError++newtype ActionT m a+    = ActionT { runActionT :: ErrorT ActionInterupt (RWST RequestInfo () ResponseState m) a }+      deriving (Monad, Functor, Applicative, MonadIO, MonadReader RequestInfo, MonadState ResponseState, MonadError ActionInterupt)++instance MonadTrans ActionT where+    lift = ActionT . lift . lift++respStateToResponse :: ResponseState -> Wai.Response+respStateToResponse (ResponseState headers status body) =+    case body of+      ResponseFile fp ->+          Wai.responseFile status waiHeaders fp Nothing+      ResponseLBS bsl ->+          Wai.responseLBS status waiHeaders bsl+      ResponseRedirect target ->+          Wai.responseLBS status302 (("Location", T.encodeUtf8 target) : waiHeaders) BSL.empty+    where+      waiHeaders = map (\(k, v) -> (CI.mk $ T.encodeUtf8 k, T.encodeUtf8 v)) headers++errorResponse :: Status -> BSL.ByteString -> ResponseState+errorResponse s e =+    ResponseState+    { rs_responseHeaders = [("Content-Type", "text/html")]+    , rs_status = s+    , rs_responseBody =+        ResponseLBS $+        BSL.concat [ "<html><head><title>"+                   , e+                   , "</title></head><body><h1>"+                   , e+                   , "</h1></body></html>"+                   ]+    }++notFound :: Wai.Response+notFound =+    respStateToResponse $ errorResponse status404 "404 - File not found"++invalidReq :: Wai.Response+invalidReq =+    respStateToResponse $ errorResponse status400 "400 - Bad request"++serverError :: ResponseState+serverError =+    errorResponse status500 "500 - Internal Server Error!"++type SpockAllT r m a =+    RegistryT r Wai.Middleware StdMethod m a++buildApp :: forall m r. (MonadIO m, AbstractRouter r, RouteAppliedAction r ~ ActionT m ())+         => r+         -> (forall a. m a -> IO a)+         -> SpockAllT r m ()+         -> IO Wai.Application+buildApp registryIf registryLift spockActions =+    do (_, getMatchingRoutes, middlewares) <-+           registryLift $ runRegistry registryIf spockActions+       let spockMiddleware = foldl (.) id middlewares+           app :: Wai.Application+           app req respond =+            case parseMethod $ Wai.requestMethod req of+              Left _ ->+                  respond invalidReq+              Right stdMethod ->+                  runResourceT $+                  withInternalState $ \st ->+                      do (bodyParams, bodyFiles) <- P.parseRequestBody (P.tempFileBackEnd st) req+                         let uploadedFiles =+                                 HM.fromList $+                                   map (\(k, fileInfo) ->+                                            ( T.decodeUtf8 k+                                            , UploadedFile (T.decodeUtf8 $ P.fileName fileInfo) (T.decodeUtf8 $ P.fileContentType fileInfo) (P.fileContent fileInfo)+                                            )+                                       ) bodyFiles+                             postParams =+                                 map (\(k, v) -> (T.decodeUtf8 k, T.decodeUtf8 v)) bodyParams+                             getParams =+                                 map (\(k, mV) -> (T.decodeUtf8 k, T.decodeUtf8 $ fromMaybe BS.empty mV)) $ Wai.queryString req+                             queryParams = postParams ++ getParams+                             resp = errorResponse status200 ""+                             allActions = getMatchingRoutes stdMethod (Wai.pathInfo req)+                             applyAction :: [(ParamMap, ActionT m ())] -> m ResponseState+                             applyAction [] =+                                 return $ errorResponse status404 "404 - File not found"+                             applyAction ((captures, selectedAction) : xs) =+                                       do let env = RequestInfo req captures queryParams uploadedFiles+                                          (r, respState, _) <-+                                              runRWST (runErrorT $ runActionT $ selectedAction) env resp+                                          case r of+                                            Left (ActionRedirect loc) ->+                                                return $ 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+                                            Left ActionDone ->+                                                return respState+                                            Right () ->+                                                return respState+                         respState <-+                             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
+ src/Web/Spock/Internal/Wrapper.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE TypeFamilies #-}+module Web.Spock.Internal.Wrapper where++import Web.Spock.Internal.Core+import Web.Spock.Internal.Wire+import Web.Spock.Internal.SessionManager+import Web.Spock.Internal.Types++import Control.Monad.Trans.Reader+import Control.Monad.Trans.Resource+import Data.Pool+import Web.Routing.AbstractRouter++-- | Run a spock application using the warp server, a given db storageLayer and an initial state.+-- Spock works with database libraries that already implement connection pooling and+-- with those that don't come with it out of the box. For more see the 'PoolOrConn' type.+spockAll :: forall r conn sess st.+            ( AbstractRouter r+            , RouteAppliedAction r ~ ActionT (WebStateM conn sess st) ()+            )+         => r+         -> Int+         -> SessionCfg sess+         -> PoolOrConn conn+         -> st+         -> SpockAllM r conn sess st ()+         -> IO ()+spockAll regIf port sessionCfg poolOrConn initialState defs =+    do sessionMgr <- createSessionManager sessionCfg+       connectionPool <-+           case poolOrConn of+             PCPool p ->+                 return p+             PCConn cb ->+                 let pc = cb_poolConfiguration cb+                 in createPool (cb_createConn cb) (cb_destroyConn cb)+                        (pc_stripes pc) (pc_keepOpenTime pc)+                        (pc_resPerStripe pc)+       let internalState =+               WebState+               { web_dbConn = connectionPool+               , web_sessionMgr = sessionMgr+               , web_state = initialState+               }+       spockAllT regIf port (\m -> runResourceT $ runReaderT (runWebStateM m) internalState) $+               do defs+                  middleware (sm_middleware sessionMgr)
− src/Web/Spock/Monad.hs
@@ -1,69 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE TypeFamilies #-}-module Web.Spock.Monad where--import Web.Spock.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--instance MonadTrans t => HasSpock (t (WebStateM conn sess st)) where-    type SpockConn (t (WebStateM conn sess st)) = conn-    type SpockState (t (WebStateM conn sess st)) = st-    type SpockSession (t (WebStateM conn sess st)) = sess-    runQuery a = webM $ runQueryImpl a-    getState = webM $ getStateImpl-    getSessMgr = webM $ getSessMgrImpl--instance HasSpock (WebStateM conn sess st) where-    type SpockConn (WebStateM conn sess st) = conn-    type SpockState (WebStateM conn sess st) = st-    type SpockSession (WebStateM conn sess st) = sess-    runQuery = runQueryImpl-    getState = getStateImpl-    getSessMgr = getSessMgrImpl--runQueryImpl :: (conn -> IO a) -> WebStateM conn sess st a-runQueryImpl query =-    do pool <- asks web_dbConn-       liftIO (withResource pool $ query)--getStateImpl :: WebStateM conn sess st st-getStateImpl = asks web_state---- | Read the heart of Spock. This is useful if you want to construct your own--- monads that work with runQuery and getState using "runSpockIO"-getSpockHeart :: MonadTrans t => t (WebStateM conn sess st) (WebState conn sess st)-getSpockHeart = webM ask---- | Run an action inside of Spocks core monad. This allows you to use runQuery and getState-runSpockIO :: WebState conn sess st -> WebStateM conn sess st a -> IO a-runSpockIO st (WebStateM action) =-    runResourceT $-    runReaderT action st--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/Routing.hs
@@ -1,169 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Web.Spock.Routing where--import Data.Hashable-import Data.Maybe-import qualified Data.Text as T-import qualified Data.Vector as V-import qualified Data.Vector.Mutable as VM-import qualified Text.Regex as Regex-import qualified Data.HashMap.Strict as HM--type ParamMap = HM.HashMap CaptureVar T.Text--newtype CaptureVar-      = CaptureVar { unCaptureVar :: T.Text }-      deriving (Show, Eq, Hashable)--data RegexWrapper-   = RegexWrapper-   { rw_regex :: !Regex.Regex-   , rw_original :: !T.Text-   }--instance Eq RegexWrapper where-    r1 == r2 =-        rw_original r1 == rw_original r2--instance Show RegexWrapper where-    show (RegexWrapper _ x) = show x--data RouteNode-   = RouteNodeRegex !CaptureVar !RegexWrapper-   | RouteNodeCapture !CaptureVar-   | RouteNodeText !T.Text-   | RouteNodeRoot-   deriving (Show, Eq)--data RouteData a-   = RouteData-   { rd_node :: !RouteNode-   , rd_data :: Maybe a-   }-   deriving (Show, Eq)--data RoutingTree a-   = RoutingTree-   { rt_node :: !(RouteData a)-   , rt_children :: !(V.Vector (RoutingTree a))-   }-   deriving (Show, Eq)--buildRegex :: T.Text -> RegexWrapper-buildRegex t =-    RegexWrapper (Regex.mkRegex $ T.unpack t) t--emptyRoutingTree :: RoutingTree a-emptyRoutingTree =-    RoutingTree (RouteData RouteNodeRoot Nothing) V.empty--mergeData :: Maybe a -> Maybe a -> Maybe a-mergeData (Just _) (Just _) =-    error "Spock error: Don't define the same route twice!"-mergeData (Just a) _ = Just a-mergeData _ (Just b) = Just b-mergeData _ _ = Nothing--addToRoutingTree :: T.Text -> a -> RoutingTree a -> RoutingTree a-addToRoutingTree route dat currTree =-    let applyTree [] tree = tree-        applyTree (current:xs) tree =-            let children = V.map (\(RoutingTree d _) -> rd_node d) (rt_children tree)-                currentDat =-                    case xs of-                      [] -> Just dat-                      _ -> Nothing-                children' =-                    case V.findIndex (==current) children of-                      Nothing ->-                          let h = applyTree xs $ RoutingTree (RouteData current currentDat) V.empty-                          in V.cons h (rt_children tree)-                      Just idx ->-                          let origNode = (V.!) (rt_children tree) idx-                              matchingNode = rt_node $ origNode-                              appliedNode = matchingNode { rd_data = mergeData (rd_data matchingNode) currentDat }-                          in V.modify (\v -> VM.write v idx (applyTree xs $ RoutingTree appliedNode (rt_children origNode))) (rt_children tree)-            in tree { rt_children = children' }-    in case filter (not . T.null) $ T.splitOn "/" route of-         [] ->-             let currNode = rt_node currTree-                 currNode' = currNode { rd_data = mergeData (rd_data currNode) (Just dat) }-             in currTree { rt_node = currNode' }-         xs -> applyTree (map parseRouteNode xs) currTree--parseRouteNode :: T.Text -> RouteNode-parseRouteNode node =-    case T.uncons node of-      Just (':', var) ->-          RouteNodeCapture $ CaptureVar var-      Just ('{', rest) ->-          case T.uncons (T.reverse rest) of-            Just ('}', def) ->-                let (var, xs) = T.breakOn ":" (T.reverse def)-                in case T.uncons xs of-                     Just (':', regex) ->-                         RouteNodeRegex (CaptureVar var) (buildRegex regex)-                     _ ->-                         nodeError-            _ -> nodeError-      Just _ ->-          RouteNodeText node-      Nothing ->-          nodeError-    where-      nodeError = error ("Spock route error: " ++ (show node) ++ " is not a valid route node.")--emptyParamMap :: ParamMap-emptyParamMap = HM.empty--matchRoute :: T.Text -> RoutingTree a -> [(ParamMap, a)]-matchRoute route globalTree =-    matchRoute' (T.splitOn "/" route) globalTree--matchRoute' :: [T.Text] -> RoutingTree a -> [(ParamMap, a)]-matchRoute' routeParts globalTree =-    case filter (not . T.null) routeParts of-      [] ->-          case rd_data $ rt_node globalTree of-            Nothing -> []-            Just action -> [(emptyParamMap, action)]-      xs ->-          findRoute xs (rt_children globalTree)-    where-      findRoute :: [T.Text] -> V.Vector (RoutingTree a) -> [(ParamMap, a)]-      findRoute fullPath trees =-          let foundPaths = V.foldl' (matchTree fullPath emptyParamMap) V.empty trees-          in V.toList foundPaths-          where-            matchTree :: [T.Text] -> ParamMap -> V.Vector (ParamMap, a) -> RoutingTree a -> V.Vector (ParamMap, a)-            matchTree [] _ vec _ = vec-            matchTree (textNode : xs) paramMap vec rt =-                case matchNode textNode (rd_node $ rt_node rt) of-                  (False, _) ->-                      vec-                  (True, mCapture) ->-                      let paramMap' =-                              case mCapture of-                                Nothing -> paramMap-                                Just (var, value) ->-                                    HM.insert var value paramMap-                          nodeData = rd_data $ rt_node rt-                          nodeChildren = rt_children rt-                      in case xs of-                           [] | isJust nodeData ->-                                  V.snoc vec (paramMap', fromJust nodeData)-                              | otherwise ->-                                  vec-                           _ ->-                               let foundPaths = V.foldl' (matchTree xs paramMap') V.empty nodeChildren-                               in V.concat [foundPaths, vec]--matchNode :: T.Text -> RouteNode -> (Bool, Maybe (CaptureVar, T.Text))-matchNode _ RouteNodeRoot = (False, Nothing)-matchNode t (RouteNodeText m) = (m == t, Nothing)-matchNode t (RouteNodeCapture var) = (True, Just (var, t))-matchNode t (RouteNodeRegex var regex) =-    case Regex.matchRegex (rw_regex regex) (T.unpack t) of-      Nothing -> (False, Nothing)-      Just _ -> (True, Just (var, t))
+ src/Web/Spock/Safe.hs view
@@ -0,0 +1,226 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE DataKinds #-}+module Web.Spock.Safe+    ( -- * Spock's core+      spock, SpockM, SpockAction+    , spockT, SpockT, ActionT+    , spockApp+     -- * Defining routes+    , Path, root, var, static, (</>)+     -- * Rendering routes+    , renderRoute+     -- * Hooking routes+    , subcomponent+    , get, post, head, put, delete, patch, hookRoute+    , 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+    , 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+      -- * Safe actions+    , SafeAction (..)+    , safeActionPath+      -- * Digestive Functors+    , runForm+      -- * Internals for extending Spock+    , getSpockHeart, runSpockIO, WebStateM, WebState+    )+where+++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+import qualified Web.Spock.Internal.Core as C++import Control.Applicative+import Control.Monad.Trans+import Data.Monoid+import Network.HTTP.Types.Method+import Prelude hiding (head)+import Web.Routing.SafeRouting+import qualified Data.Text as T+import qualified Network.HTTP.Types as Http+import qualified Network.Wai as Wai+import qualified Network.Wai.Handler.Warp as Warp++type SpockM conn sess st a = SpockT (WebStateM conn sess st) a++newtype SpockT m a+    = SpockT { runSpockT :: C.SpockAllT (SafeRouter (ActionT m) ()) m a+             } deriving (Monad, Functor, Applicative, MonadIO)++instance MonadTrans SpockT where+    lift = SpockT . lift++-- | Run a spock application using the warp server, a given db storageLayer and an initial state.+-- Spock works with database libraries that already implement connection pooling and+-- with those that don't come with it out of the box. For more see the 'PoolOrConn' type.+spock :: Int -> SessionCfg sess -> PoolOrConn conn -> st -> SpockM conn sess st () -> IO ()+spock port sessCfg poolOrConn initSt spockAppl =+    spockAll SafeRouter port sessCfg poolOrConn initSt (runSpockT spockAppl')+    where+      spockAppl' =+          do hookSafeActions+             spockAppl++-- | Run a raw spock application with custom underlying monad+spockT :: (MonadIO m)+       => Warp.Port+       -> (forall a. m a -> IO a)+       -> SpockT m ()+       -> IO ()+spockT port liftFun (SpockT app) =+    C.spockAllT SafeRouter port liftFun app++-- | Convert a Spock-App to a wai-application+spockApp :: (MonadIO m) => (forall a. m a -> IO a) -> SpockT m () -> IO Wai.Application+spockApp liftFun (SpockT app) =+    W.buildApp 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 -> HListElim xs (ActionT m ()) -> SpockT m ()+get = hookRoute GET++-- | Specify an action that will be run when the HTTP verb 'POST' and the given route match+post :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()+post = hookRoute POST++-- | Specify an action that will be run when the HTTP verb 'HEAD' and the given route match+head :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()+head = hookRoute HEAD++-- | Specify an action that will be run when the HTTP verb 'PUT' and the given route match+put :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()+put = hookRoute PUT++-- | Specify an action that will be run when the HTTP verb 'DELETE' and the given route match+delete :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()+delete = hookRoute DELETE++-- | Specify an action that will be run when the HTTP verb 'PATCH' and the given route match+patch :: MonadIO m => Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()+patch = hookRoute PATCH++-- | Specify an action that will be run when a HTTP verb and the given route match+hookRoute :: Monad m => StdMethod -> Path xs -> HListElim xs (ActionT m ()) -> SpockT m ()+hookRoute m path action = SpockT $ C.hookRoute m (SafeRouterPath path) (HListElim' action)++-- | Define a subcomponent. Usage example:+--+-- > subcomponent "site" $+-- >   do get "home" homeHandler+-- >      get ("misc" <> var) $ -- ...+-- > subcomponent "admin" $+-- >   do get "home" adminHomeHandler+--+-- The request "/site/home" will be routed to homeHandler and the+-- request "/admin/home" will be routed to adminHomeHandler+subcomponent :: Monad m => Path '[] -> SpockT m () -> SpockT m ()+subcomponent p (SpockT subapp) = SpockT $ C.subcomponent (SafeRouterPath p) subapp++-- | 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:+--+-- > newtype DeleteUser = DeleteUser Int deriving (Hashable, Typeable, Eq)+-- >+-- > instance SafeAction Connection () () DeleteUser where+-- >    runSafeAction (DeleteUser i) =+-- >       do runQuery $ deleteUserFromDb i+-- >          redirect "/user-list"+-- >+-- > get "/user-details/:userId" $+-- >   do userId <- param' "userId"+-- >      deleteUrl <- safeActionPath (DeleteUser userId)+-- >      html $ "Click <a href='" <> deleteUrl <> "'>here</a> to delete user!"+--+-- Note that safeActions currently only support GET and POST requests.+--+safeActionPath :: forall conn sess st a.+                  ( SafeAction conn sess st a+                  , HasSpock(SpockAction conn sess st)+                  , SpockConn (SpockAction conn sess st) ~ conn+                  , SpockSession (SpockAction conn sess st) ~ sess+                  , SpockState (SpockAction conn sess st) ~ st)+               => a+               -> SpockAction conn sess st T.Text+safeActionPath safeAction =+    do mgr <- getSessMgr+       hash <- (sm_addSafeAction mgr) (PackedSafeAction safeAction)+       return $ "/h/" <> hash++hookSafeActions :: forall conn sess st.+                   ( HasSpock (SpockAction conn sess st)+                   , SpockConn (SpockAction conn sess st) ~ conn+                   , SpockSession (SpockAction conn sess st) ~ sess+                   , SpockState (SpockAction conn sess st) ~ st)+                => SpockM conn sess st ()+hookSafeActions =+    do get (static "h" </> var) run+       post (static "h" </> var) run+    where+      run h =+          do mgr <- getSessMgr+             mAction <- (sm_lookupSafeAction mgr) h+             case mAction of+               Nothing ->+                   do setStatus Http.status404+                      text "File not found"+               Just p@(PackedSafeAction action) ->+                   do runSafeAction action+                      (sm_removeSafeAction mgr) p
− src/Web/Spock/SafeActions.hs
@@ -1,65 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE NoMonomorphismRestriction #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}-module Web.Spock.SafeActions where--import Web.Spock.Core-import Web.Spock.Types--import Data.Monoid-import Network.HTTP.Types.Status-import qualified Data.Text as T---- | Wire up a safe action: Safe actions are actions that are protected from--- csrf attacks. Here's a usage example:------ > newtype DeleteUser = DeleteUser Int deriving (Hashable, Typeable, Eq)--- >--- > instance SafeAction Connection () () DeleteUser where--- >    runSafeAction (DeleteUser i) =--- >       do runQuery $ deleteUserFromDb i--- >          redirect "/user-list"--- >--- > get "/user-details/:userId" $--- >   do userId <- param' "userId"--- >      deleteUrl <- safeActionPath (DeleteUser userId)--- >      html $ "Click <a href='" <> deleteUrl <> "'>here</a> to delete user!"------ Note that safeActions currently only support GET and POST requests.----safeActionPath :: forall conn sess st a.-                  ( SafeAction conn sess st a-                  , HasSpock(SpockAction conn sess st)-                  , SpockConn (SpockAction conn sess st) ~ conn-                  , SpockSession (SpockAction conn sess st) ~ sess-                  , SpockState (SpockAction conn sess st) ~ st)-               => a-               -> SpockAction conn sess st T.Text-safeActionPath safeAction =-    do mgr <- getSessMgr-       hash <- (sm_addSafeAction mgr) (PackedSafeAction safeAction)-       return $ "/h/" <> hash--hookSafeActions :: forall conn sess st.-                   ( HasSpock (SpockAction conn sess st)-                   , SpockConn (SpockAction conn sess st) ~ conn-                   , SpockSession (SpockAction conn sess st) ~ sess-                   , SpockState (SpockAction conn sess st) ~ st)-                => SpockM conn sess st ()-hookSafeActions =-    do get "/h/:spock-csurf-protection" run-       post "/h/:spock-csurf-protection" run-    where-      run =-          do Just h <- param "spock-csurf-protection"-             mgr <- getSessMgr-             mAction <- (sm_lookupSafeAction mgr) h-             case mAction of-               Nothing ->-                   do setStatus status404-                      text "File not found"-               Just p@(PackedSafeAction action) ->-                   do runSafeAction action-                      (sm_removeSafeAction mgr) p
− src/Web/Spock/SessionManager.hs
@@ -1,253 +0,0 @@-{-# LANGUAGE FlexibleContexts, DeriveGeneric, OverloadedStrings, DoAndIfThenElse, RankNTypes #-}-module Web.Spock.SessionManager-    ( createSessionManager-    , SessionId, Session(..), SessionManager(..)-    )-where--import Web.Spock.Types-import Web.Spock.Core-import Web.Spock.Util--import Control.Arrow (first)-import Control.Concurrent-import Control.Concurrent.STM-import Control.Monad-import Control.Monad.Trans-import Data.Time-import System.Locale-import System.Random-import qualified Data.ByteString.Base64.URL as B64-import qualified Data.ByteString.Char8 as BSC-import qualified Data.ByteString.Lazy as BSL-import qualified Data.HashMap.Strict as HM-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Encoding as TL-import qualified Data.Vault.Lazy as V-import qualified Network.Wai as Wai--createSessionManager :: SessionCfg sess -> IO (SessionManager conn sess st)-createSessionManager cfg =-    do cacheHM <- atomically $ newTVar HM.empty-       vaultKey <- V.newKey-       _ <- forkIO (forever (housekeepSessions cacheHM))-       return $ SessionManager-                  { sm_readSession = readSessionImpl vaultKey cacheHM-                  , sm_writeSession = writeSessionImpl vaultKey cacheHM-                  , sm_modifySession = modifySessionImpl vaultKey cacheHM-                  , sm_clearAllSessions = clearAllSessionsImpl cacheHM-                  , sm_middleware = sessionMiddleware cfg vaultKey cacheHM-                  , sm_addSafeAction = addSafeActionImpl vaultKey cacheHM-                  , sm_lookupSafeAction = lookupSafeActionImpl vaultKey cacheHM-                  , sm_removeSafeAction = removeSafeActionImpl vaultKey cacheHM-                  }--modifySessionBase :: V.Key SessionId-                  -> UserSessions conn sess st-                  -> (Session conn sess st -> Session conn sess st)-                  -> SpockAction conn sess st ()-modifySessionBase vK sessionRef modFun =-    do req <- request-       case V.lookup vK (Wai.vault req) of-         Nothing ->-             error "(3) Internal Spock Session Error. Please report this bug!"-         Just sid ->-             liftIO $ atomically $ modifyTVar' sessionRef (HM.adjust modFun sid)--readSessionBase :: V.Key SessionId-                -> UserSessions conn sess st-                -> SpockAction conn sess st (Session conn sess st)-readSessionBase vK sessionRef =-    do req <- request-       case V.lookup vK (Wai.vault req) of-         Nothing ->-             error "(1) Internal Spock Session Error. Please report this bug!"-         Just sid ->-             do sessions <- liftIO $ atomically $ readTVar sessionRef-                case HM.lookup sid sessions of-                  Nothing ->-                      error "(2) Internal Spock Session Error. Please report this bug!"-                  Just session ->-                      return session--addSafeActionImpl :: V.Key SessionId-                  -> UserSessions conn sess st-                  -> PackedSafeAction conn sess st-                  -> SpockAction conn sess st SafeActionHash-addSafeActionImpl vaultKey sessionMapVar safeAction =-    do base <- readSessionBase vaultKey sessionMapVar-       case HM.lookup safeAction (sas_reverse (sess_safeActions base)) of-         Just safeActionHash ->-             return safeActionHash-         Nothing ->-             do safeActionHash <- liftIO (randomHash 40)-                let f sas =-                        sas-                        { sas_forward = HM.insert safeActionHash safeAction (sas_forward sas)-                        , sas_reverse = HM.insert safeAction safeActionHash (sas_reverse sas)-                        }-                modifySessionBase vaultKey sessionMapVar (\s -> s { sess_safeActions = f (sess_safeActions s) })-                return safeActionHash--lookupSafeActionImpl :: V.Key SessionId-                     -> UserSessions conn sess st-                     -> SafeActionHash-                     -> SpockAction conn sess st (Maybe (PackedSafeAction conn sess st))-lookupSafeActionImpl vaultKey sessionMapVar hash =-    do base <- readSessionBase vaultKey sessionMapVar-       return $ HM.lookup hash (sas_forward (sess_safeActions base))--removeSafeActionImpl :: V.Key SessionId-                     -> UserSessions conn sess st-                     -> PackedSafeAction conn sess st-                     -> SpockAction conn sess st ()-removeSafeActionImpl vaultKey sessionMapVar action =-    modifySessionBase vaultKey sessionMapVar (\s -> s { sess_safeActions = f (sess_safeActions s ) })-    where-      f sas =-          sas-          { sas_forward =-              case HM.lookup action (sas_reverse sas) of-                Just h -> HM.delete h (sas_forward sas)-                Nothing -> sas_forward sas-          , sas_reverse = HM.delete action (sas_reverse sas)-          }--readSessionImpl :: V.Key SessionId-                -> UserSessions conn sess st-                -> SpockAction conn sess st sess-readSessionImpl vK sessionRef =-    do base <- readSessionBase vK sessionRef-       return (sess_data base)--writeSessionImpl :: V.Key SessionId-                 -> UserSessions conn sess st-                 -> sess-                 -> SpockAction conn sess st ()-writeSessionImpl vK sessionRef value =-    modifySessionImpl vK sessionRef (const value)--modifySessionImpl :: V.Key SessionId-                  -> UserSessions conn sess st-                  -> (sess -> sess)-                  -> SpockAction conn sess st ()-modifySessionImpl vK sessionRef f =-    do let modFun session =-                        session { sess_data = f (sess_data session) }-       modifySessionBase vK sessionRef modFun--sessionMiddleware :: SessionCfg sess-                  -> V.Key SessionId-                  -> UserSessions conn sess st-                  -> Wai.Middleware-sessionMiddleware cfg vK sessionRef app req respond =-    case getCookieFromReq (sc_cookieName cfg) of-      Just sid ->-          do mSess <- loadSessionImpl sessionRef sid-             case mSess of-               Nothing ->-                   mkNew-               Just sess ->-                   withSess False sess-      Nothing ->-          mkNew-    where-      getCookieFromReq name =-          lookup "cookie" (Wai.requestHeaders req) >>=-                 lookup name . parseCookies . T.decodeUtf8-      renderCookie name value validUntil =-          let formattedTime =-                  TL.pack $ formatTime defaultTimeLocale "%a, %d-%b-%Y %X %Z" validUntil-          in TL.concat [ TL.fromStrict name-                       , "="-                       , TL.fromStrict value-                       , "; path=/; expires="-                       , formattedTime-                       , ";"-                       ]-      parseCookies :: T.Text -> [(T.Text, T.Text)]-      parseCookies = map parseCookie . T.splitOn ";" . T.concat . T.words-      parseCookie = first T.init . T.breakOnEnd "="--      defVal = sc_emptySession cfg-      v = Wai.vault req-      addCookie sess responseHeaders =-          let cookieContent =-                  renderCookie (sc_cookieName cfg) (sess_id sess) (sess_validUntil sess)-              cookieC = ("Set-Cookie", BSL.toStrict $ TL.encodeUtf8 cookieContent)-          in (cookieC : responseHeaders)-      withSess shouldSetCookie sess =-          app (req { Wai.vault = V.insert vK (sess_id sess) v }) $ \unwrappedResp ->-              respond $-              if shouldSetCookie-              then mapReqHeaders (addCookie sess) unwrappedResp-              else unwrappedResp-      mkNew =-          do newSess <- newSessionImpl cfg sessionRef defVal-             withSess True newSess--newSessionImpl :: SessionCfg sess-               -> UserSessions conn sess st-               -> sess-               -> IO (Session conn sess st)-newSessionImpl sessCfg sessionRef content =-    do sess <- createSession sessCfg content-       atomically $ modifyTVar' sessionRef (\hm -> HM.insert (sess_id sess) sess hm)-       return $! sess--loadSessionImpl :: UserSessions conn sess st-                -> SessionId-                -> IO (Maybe (Session conn sess st))-loadSessionImpl sessionRef sid =-    do sessHM <- atomically $ readTVar sessionRef-       now <- getCurrentTime-       case HM.lookup sid sessHM of-         Just sess ->-             do if (sess_validUntil sess) > now-                then return $ Just sess-                else do deleteSessionImpl sessionRef sid-                        return Nothing-         Nothing ->-             return Nothing--deleteSessionImpl :: UserSessions conn sess st-                  -> SessionId-                  -> IO ()-deleteSessionImpl sessionRef sid =-    do atomically $ modifyTVar' sessionRef (\hm -> HM.delete sid hm)-       return ()--clearAllSessionsImpl :: UserSessions conn sess st-                     -> SpockAction conn sess st ()-clearAllSessionsImpl sessionRef =-    liftIO $ atomically $ modifyTVar' sessionRef (const HM.empty)--housekeepSessions :: UserSessions conn sess st -> IO ()-housekeepSessions sessionRef =-    do now <- getCurrentTime-       atomically $ modifyTVar' sessionRef (killOld now)-       threadDelay (1000 * 1000 * 60) -- 60 seconds-    where-      filterOld now (_, sess) =-          (sess_validUntil sess) > now-      killOld now hm =-          HM.fromList $ filter (filterOld now) $ HM.toList hm--createSession :: SessionCfg sess -> sess -> IO (Session conn sess st)-createSession sessCfg content =-    do sid <- randomHash (sc_sessionIdEntropy sessCfg)-       now <- getCurrentTime-       let validUntil = addUTCTime (sc_sessionTTL sessCfg) now-           emptySafeActions =-               SafeActionStore HM.empty HM.empty-       return (Session sid validUntil content emptySafeActions)--randomHash :: Int -> IO T.Text-randomHash len =-    do gen <- g-       return $ T.replace "=" "" $ T.decodeUtf8 $ B64.encode $ BSC.pack $-              take len $ randoms gen-    where-      g = newStdGen :: IO StdGen
+ src/Web/Spock/Simple.hs view
@@ -0,0 +1,235 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+module Web.Spock.Simple+    ( -- * Spock's core+      spock, SpockM, SpockAction+    , spockT, SpockT, ActionT+    , spockApp+     -- * Defining routes+    , SpockRoute, (<#>)+     -- * Hooking routes+    , subcomponent+    , get, post, head, put, delete, patch, hookRoute+    , 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+    , 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+      -- * Safe actions+    , SafeAction (..)+    , safeActionPath+      -- * Digestive Functors+    , runForm+      -- * Internals for extending Spock+    , getSpockHeart, runSpockIO, WebStateM, WebState+    )+where+++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+import qualified Web.Spock.Internal.Core as C++import Control.Applicative+import Control.Monad.Trans+import Data.Monoid+import Data.String+import Network.HTTP.Types.Method+import Prelude hiding (head)+import Web.Routing.TextRouting+import qualified Data.Text as T+import qualified Network.HTTP.Types as Http+import qualified Network.Wai as Wai+import qualified Network.Wai.Handler.Warp as Warp++type SpockM conn sess st a = SpockT (WebStateM conn sess st) a++newtype SpockT m a+    = SpockT { runSpockT :: C.SpockAllT (TextRouter (ActionT m) ()) m a+             } deriving (Monad, Functor, Applicative, MonadIO)++instance MonadTrans SpockT where+    lift = SpockT . lift++newtype SpockRoute+    = SpockRoute { _unSpockRoute :: T.Text }+    deriving (Eq, Ord, Show, Read, IsString)++-- | Run a spock application using the warp server, a given db storageLayer and an initial state.+-- Spock works with database libraries that already implement connection pooling and+-- with those that don't come with it out of the box. For more see the 'PoolOrConn' type.+spock :: Int -> SessionCfg sess -> PoolOrConn conn -> st -> SpockM conn sess st () -> IO ()+spock port sessCfg poolOrConn initSt spockAppl =+    spockAll TextRouter port sessCfg poolOrConn initSt (runSpockT spockAppl')+    where+      spockAppl' =+          do hookSafeActions+             spockAppl++-- | Run a raw spock application with custom underlying monad+spockT :: (MonadIO m)+       => Warp.Port+       -> (forall a. m a -> IO a)+       -> SpockT m ()+       -> IO ()+spockT port liftFun (SpockT app) =+    C.spockAllT TextRouter port liftFun app++-- | Convert a Spock-App to a wai-application+spockApp :: (MonadIO m) => (forall a. m a -> IO a) -> SpockT m () -> IO Wai.Application+spockApp liftFun (SpockT app) =+    W.buildApp TextRouter liftFun app++-- | Combine two route components safely+-- "/foo" <#> "/bar" ===> "/foo/bar"+-- "foo" <#> "bar" ===> "/foo/bar"+-- "foo <#> "/bar" ===> "/foo/bar"+(<#>) :: SpockRoute -> SpockRoute -> SpockRoute+(SpockRoute t) <#> (SpockRoute t') = SpockRoute $ combineRoute t t'++-- | Specify an action that will be run when the HTTP verb 'GET' and the given route match+get :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()+get = hookRoute GET++-- | Specify an action that will be run when the HTTP verb 'POST' and the given route match+post :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()+post = hookRoute POST++-- | Specify an action that will be run when the HTTP verb 'HEAD' and the given route match+head :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()+head = hookRoute HEAD++-- | Specify an action that will be run when the HTTP verb 'PUT' and the given route match+put :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()+put = hookRoute PUT++-- | Specify an action that will be run when the HTTP verb 'DELETE' and the given route match+delete :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()+delete = hookRoute DELETE++-- | Specify an action that will be run when the HTTP verb 'PATCH' and the given route match+patch :: MonadIO m => SpockRoute -> ActionT m () -> SpockT m ()+patch = hookRoute PATCH++-- | Specify an action that will be run when a HTTP verb and the given route match+hookRoute :: Monad m => StdMethod -> SpockRoute -> ActionT m () -> SpockT m ()+hookRoute m (SpockRoute path) action = SpockT $ C.hookRoute m (TextRouterPath path) (TAction action)++-- | Define a subcomponent. Usage example:+--+-- > subcomponent "/site" $+-- >   do get "/home" homeHandler+-- >      get "/misc/:param" $ -- ...+-- > subcomponent "/admin" $+-- >   do get "/home" adminHomeHandler+--+-- The request /site/home will be routed to homeHandler and the+-- request /admin/home will be routed to adminHomeHandler+subcomponent :: Monad m => SpockRoute -> SpockT m () -> SpockT m ()+subcomponent (SpockRoute p) (SpockT subapp) = SpockT $ C.subcomponent (TextRouterPath p) subapp++-- | 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:+--+-- > newtype DeleteUser = DeleteUser Int deriving (Hashable, Typeable, Eq)+-- >+-- > instance SafeAction Connection () () DeleteUser where+-- >    runSafeAction (DeleteUser i) =+-- >       do runQuery $ deleteUserFromDb i+-- >          redirect "/user-list"+-- >+-- > get "/user-details/:userId" $+-- >   do userId <- param' "userId"+-- >      deleteUrl <- safeActionPath (DeleteUser userId)+-- >      html $ "Click <a href='" <> deleteUrl <> "'>here</a> to delete user!"+--+-- Note that safeActions currently only support GET and POST requests.+--+safeActionPath :: forall conn sess st a.+                  ( SafeAction conn sess st a+                  , HasSpock(SpockAction conn sess st)+                  , SpockConn (SpockAction conn sess st) ~ conn+                  , SpockSession (SpockAction conn sess st) ~ sess+                  , SpockState (SpockAction conn sess st) ~ st)+               => a+               -> SpockAction conn sess st T.Text+safeActionPath safeAction =+    do mgr <- getSessMgr+       hash <- (sm_addSafeAction mgr) (PackedSafeAction safeAction)+       return $ "/h/" <> hash++hookSafeActions :: forall conn sess st.+                   ( HasSpock (SpockAction conn sess st)+                   , SpockConn (SpockAction conn sess st) ~ conn+                   , SpockSession (SpockAction conn sess st) ~ sess+                   , SpockState (SpockAction conn sess st) ~ st)+                => SpockM conn sess st ()+hookSafeActions =+    do get "/h/:spock-csurf-protection" run+       post "/h/:spock-csurf-protection" run+    where+      run =+          do Just h <- param "spock-csurf-protection"+             mgr <- getSessMgr+             mAction <- (sm_lookupSafeAction mgr) h+             case mAction of+               Nothing ->+                   do setStatus Http.status404+                      text "File not found"+               Just p@(PackedSafeAction action) ->+                   do runSafeAction action+                      (sm_removeSafeAction mgr) p
+ src/Web/Spock/Specs/All.hs view
@@ -0,0 +1,11 @@+module Web.Spock.Specs.All where++import qualified Web.Spock.Specs.SimpleSpec+import qualified Web.Spock.Specs.SafeSpec++import Test.Hspec++allSpecs :: Spec+allSpecs =+    do Web.Spock.Specs.SimpleSpec.spec+       Web.Spock.Specs.SafeSpec.spec
+ src/Web/Spock/Specs/FrameworkSpecHelper.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.Spock.Specs.FrameworkSpecHelper where++import Test.Hspec+import Test.Hspec.Wai++import qualified Network.Wai as Wai++frameworkSpec :: IO Wai.Application -> Spec+frameworkSpec app =+    with app $+    do routingSpec+       actionSpec++routingSpec :: SpecWith Wai.Application+routingSpec =+    describe "Routing Framework" $+      do it "allows root actions" $+            get "/" `shouldRespondWith` "root" { matchStatus = 200 }+         it "routes different HTTP-verbs to different actions" $+            do verbTest get "GET"+               verbTest (\p -> post p "") "POST"+               verbTest (\p -> put p "") "PUT"+               verbTest delete "DELETE"+               verbTest (\p -> patch p "") "PATCH"+         it "can extract params from routes" $+            get "/param-test/42" `shouldRespondWith` "int42" { matchStatus = 200 }+         it "can handle multiple matching routes" $+            get "/param-test/static" `shouldRespondWith` "static" { matchStatus = 200 }+    where+      verbTest verb verbVerbose =+          (verb "/verb-test")+          `shouldRespondWith` (verbVerbose { matchStatus = 200 })++actionSpec :: SpecWith Wai.Application+actionSpec =+    describe "Action Framework" $ return ()
+ src/Web/Spock/Specs/SafeSpec.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Web.Spock.Specs.SafeSpec where++import Web.Spock.Safe+import Web.Spock.Specs.FrameworkSpecHelper++import Data.Monoid+import Test.Hspec+import qualified Data.Text as T++app :: SpockT IO ()+app =+    do get root $ text "root"+       get "verb-test" $ text "GET"+       post "verb-test" $ text "POST"+       put "verb-test" $ text "PUT"+       delete "verb-test" $ text "DELETE"+       patch "verb-test" $ text "PATCH"+       get ("param-test" </> var) $ \(i :: Int) ->+           text $ "int" <> (T.pack $ show i)+       get ("param-test" </> "static") $+           text "static"++spec :: Spec+spec = describe "SafeRouting" $ frameworkSpec (spockApp id app)
+ src/Web/Spock/Specs/SimpleSpec.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Web.Spock.Specs.SimpleSpec where++import Web.Spock.Simple+import Web.Spock.Specs.FrameworkSpecHelper++import Data.Monoid+import Test.Hspec+import qualified Data.Text as T++app :: SpockT IO ()+app =+    do get "/" $ text "root"+       get "/verb-test" $ text "GET"+       post "/verb-test" $ text "POST"+       put "/verb-test" $ text "PUT"+       delete "/verb-test" $ text "DELETE"+       patch "/verb-test" $ text "PATCH"+       get "/param-test/:int" $+           do Just (i :: Int) <- param "int"+              text $ "int" <> (T.pack $ show i)+       get "/param-test/static" $+           text "static"+spec :: Spec+spec = describe "SimpleRouting" $ frameworkSpec (spockApp id app)
− src/Web/Spock/Types.hs
@@ -1,142 +0,0 @@-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ExistentialQuantification #-}-module Web.Spock.Types where--import Web.Spock.Core--import Control.Applicative-import Control.Concurrent.STM-import Control.Monad.Base-import Control.Monad.Reader-import Control.Monad.Trans.Control-import Control.Monad.Trans.Resource-import Data.Hashable-import Data.Pool-import Data.Time.Clock ( UTCTime(..), NominalDiffTime )-import Data.Typeable-import Network.Wai-import qualified Data.HashMap.Strict as HM-import qualified Data.Text as T---- | Inside the SpockM monad, you may define routes and middleware.-type SpockM conn sess st = SpockT (WebStateM conn sess st)---- | The SpockAction is the monad of all route-actions. You have access--- to the database, session and state of your application.-type SpockAction conn sess st = ActionT (WebStateM conn sess st)---- | If Spock should take care of connection pooling, you need to configure--- it depending on what you need.-data PoolCfg-   = PoolCfg-   { pc_stripes :: Int-   , pc_resPerStripe :: Int-   , pc_keepOpenTime :: NominalDiffTime-   }---- | The ConnBuilder instructs Spock how to create or close a database connection.-data ConnBuilder a-   = ConnBuilder-   { cb_createConn :: IO a-   , cb_destroyConn :: a -> IO ()-   , cb_poolConfiguration :: PoolCfg-   }---- | You can feed Spock with either a connection pool, or instructions on how to build--- a connection pool. See 'ConnBuilder'-data PoolOrConn a-   = PCPool (Pool a)-   | PCConn (ConnBuilder a)---- | Configuration for the session manager-data SessionCfg a-   = SessionCfg-   { sc_cookieName :: T.Text-   , sc_sessionTTL :: NominalDiffTime-   , sc_sessionIdEntropy :: Int-   , sc_emptySession :: a-   }--data WebState conn sess st-   = WebState-   { web_dbConn :: Pool conn-   , web_sessionMgr :: SessionManager conn sess st-   , web_state :: st-   }--class HasSpock m where-    type SpockConn m :: *-    type SpockState m :: *-    type SpockSession m :: *-    -- | Give you access to a database connectin from the connection pool. The connection is-    -- released back to the pool once the function terminates.-    runQuery :: (SpockConn m -> IO a) -> m a-    -- | Read the application's state. If you wish to have mutable state, you could-    -- use a 'TVar' from the STM packge.-    getState :: m (SpockState m)-    -- | Get the session manager-    getSessMgr :: m (SessionManager (SpockConn m) (SpockSession m) (SpockState m))---- | SafeActions are actions that need to be protected from csrf attacks-class (Hashable a, Eq a, Typeable a) => SafeAction conn sess st a where-    -- | The body of the safe action. Either GET or POST-    runSafeAction :: a -> SpockAction conn sess st ()--data PackedSafeAction conn sess st-    = forall a. (SafeAction conn sess st a) => PackedSafeAction { unpackSafeAction :: a }--instance Hashable (PackedSafeAction conn sess st) where-    hashWithSalt i (PackedSafeAction a) = hashWithSalt i a--instance Eq (PackedSafeAction conn sess st) where-   (PackedSafeAction a) == (PackedSafeAction b) =-       cast a == Just b--data SafeActionStore conn sess st-   = SafeActionStore-   { sas_forward :: !(HM.HashMap SafeActionHash (PackedSafeAction conn sess st))-   , sas_reverse :: !(HM.HashMap (PackedSafeAction conn sess st) SafeActionHash)-   }--type SafeActionHash = T.Text--newtype WebStateM conn sess st a = WebStateM { runWebStateM :: ReaderT (WebState conn sess st) (ResourceT IO) a }-    deriving (Monad, Functor, Applicative, MonadIO, MonadReader (WebState conn sess st))--instance MonadBase IO (WebStateM conn sess st) where-    liftBase = WebStateM . liftBase--instance MonadBaseControl IO (WebStateM conn sess st) where-    newtype StM (WebStateM conn sess st) a = WStM { unWStM :: StM (ReaderT (WebState conn sess st) (ResourceT IO)) a }-    liftBaseWith f = WebStateM . liftBaseWith $ \runInBase -> f $ liftM WStM . runInBase . runWebStateM-    restoreM = WebStateM . restoreM . unWStM--type SessionId = T.Text-data Session conn sess st-    = Session-    { sess_id :: !SessionId-    , sess_validUntil :: !UTCTime-    , sess_data :: !sess-    , sess_safeActions :: !(SafeActionStore conn sess st)-    }-instance Show (Session conn sess st) where-    show = show . sess_id--type UserSessions conn sess st =-    TVar (HM.HashMap SessionId (Session conn sess st))--data SessionManager conn sess st-   = SessionManager-   { 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 ()-   , sm_middleware :: Middleware-   , sm_addSafeAction :: PackedSafeAction conn sess st -> SpockAction conn sess st SafeActionHash-   , sm_lookupSafeAction :: SafeActionHash -> SpockAction conn sess st (Maybe (PackedSafeAction conn sess st))-   , sm_removeSafeAction :: PackedSafeAction conn sess st -> SpockAction conn sess st ()-   }
− src/Web/Spock/Util.hs
@@ -1,12 +0,0 @@-module Web.Spock.Util where--import Network.HTTP.Types-import Network.Wai.Internal--mapReqHeaders :: (ResponseHeaders -> ResponseHeaders) -> Response -> Response-mapReqHeaders f resp =-    case resp of-      (ResponseFile s h b1 b2) -> ResponseFile s (f h) b1 b2-      (ResponseBuilder s h b) -> ResponseBuilder s (f h) b-      (ResponseStream s h b) -> ResponseStream s (f h) b-      (ResponseRaw x r) -> ResponseRaw x (mapReqHeaders f r)
− src/Web/Spock/Wire.hs
@@ -1,285 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE NoMonomorphismRestriction #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-module Web.Spock.Wire where--import Web.Spock.Routing--import Control.Applicative-import Control.Exception-import Control.Monad.RWS.Strict-import Control.Monad.Error-import Control.Monad.Reader.Class ()-import Control.Monad.Trans.Resource-import Data.Hashable-import Data.Maybe-import Network.HTTP.Types.Method-import Network.HTTP.Types.Status-#if MIN_VERSION_base(4,6,0)-import Prelude-#else-import Prelude hiding (catch)-#endif-import System.Directory-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BSL-import qualified Data.CaseInsensitive as CI-import qualified Data.HashMap.Strict as HM-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Network.Wai as Wai-import qualified Network.Wai.Parse as P--instance Hashable StdMethod where-    hashWithSalt = hashUsing fromEnum--type SpockRoutingTree m = RoutingTree (ActionT m ())-type SpockTreeMap m = HM.HashMap StdMethod (SpockRoutingTree m)--type SpockRouteMap m = HM.HashMap StdMethod (HM.HashMap T.Text (ActionT m ()))--data SpockState m-   = SpockState-   { ss_treeMap :: !(SpockRouteMap m)-   , ss_middleware :: [Wai.Middleware]-   , ss_spockLift :: forall a. m a -> IO a-   }--data UploadedFile-   = UploadedFile-   { uf_name :: T.Text-   , uf_contentType :: T.Text-   , uf_tempLocation :: FilePath-   }--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-   }--data ResponseBody-   = ResponseFile FilePath-   | ResponseLBS BSL.ByteString-   | ResponseRedirect T.Text-   deriving (Show, Eq)--data ResponseState-   = ResponseState-   { rs_responseHeaders :: [(T.Text, T.Text)]-   , rs_status :: Status-   , rs_responseBody :: ResponseBody-   } deriving (Show, Eq)--type BaseRoute = T.Text--data ActionInterupt-    = ActionRedirect T.Text-    | ActionTryNext-    | ActionError String-    | ActionDone-    deriving (Show)--instance Error ActionInterupt where-    noMsg = ActionError "Unkown Internal Action Error"-    strMsg = ActionError--newtype ActionT m a-    = ActionT { runActionT :: ErrorT ActionInterupt (RWST RequestInfo () ResponseState m) a }-      deriving (Monad, Functor, Applicative, MonadIO, MonadReader RequestInfo, MonadState ResponseState, MonadError ActionInterupt)--instance MonadTrans ActionT where-    lift = ActionT . lift . lift--newtype SpockT (m :: * -> *) a-    = SpockT { runSpockT :: RWST BaseRoute () (SpockState m) m a }-      deriving (Monad, Functor, Applicative, MonadIO, MonadReader BaseRoute, MonadState (SpockState m))--instance MonadTrans SpockT where-    lift = SpockT . lift--respStateToResponse :: ResponseState -> Wai.Response-respStateToResponse (ResponseState headers status body) =-    case body of-      ResponseFile fp ->-          Wai.responseFile status waiHeaders fp Nothing-      ResponseLBS bsl ->-          Wai.responseLBS status waiHeaders bsl-      ResponseRedirect target ->-          Wai.responseLBS status302 (("Location", T.encodeUtf8 target) : waiHeaders) BSL.empty-    where-      waiHeaders = map (\(k, v) -> (CI.mk $ T.encodeUtf8 k, T.encodeUtf8 v)) headers--errorResponse :: Status -> BSL.ByteString -> ResponseState-errorResponse s e =-    ResponseState-    { rs_responseHeaders = [("Content-Type", "text/html")]-    , rs_status = s-    , rs_responseBody =-        ResponseLBS $-        BSL.concat [ "<html><head><title>"-                   , e-                   , "</title></head><body><h1>"-                   , e-                   , "</h1></body></html>"-                   ]-    }--notFound :: Wai.Response-notFound =-    respStateToResponse $ errorResponse status404 "404 - File not found"--invalidReq :: Wai.Response-invalidReq =-    respStateToResponse $ errorResponse status400 "400 - Bad request"--serverError :: ResponseState-serverError =-    errorResponse status500 "500 - Internal Server Error!"--buildApp :: forall m. (MonadIO m)-         => (forall a. m a -> IO a)-         -> SpockT m ()-         -> IO Wai.Application-buildApp spockLift spockActions =-    do let initState =-               SpockState-               { ss_treeMap = HM.empty-               , ss_middleware = []-               , ss_spockLift = spockLift-               }-       (spockState, ()) <- spockLift $ execRWST (runSpockT spockActions) "/" initState-       let spockMiddleware = foldl (.) id (ss_middleware spockState)-           routingTreeMap = buildRoutingTree (ss_treeMap spockState)-           app :: Wai.Application-           app req respond =-            case parseMethod $ Wai.requestMethod req of-              Left _ ->-                  respond invalidReq-              Right stdMethod ->-                  case HM.lookup stdMethod routingTreeMap of-                    Just routeTree ->-                        runResourceT $-                        withInternalState $ \st ->-                            do (bodyParams, bodyFiles) <- P.parseRequestBody (P.tempFileBackEnd st) req-                               let uploadedFiles =-                                       HM.fromList $-                                         map (\(k, fileInfo) ->-                                                  ( T.decodeUtf8 k-                                                  , UploadedFile (T.decodeUtf8 $ P.fileName fileInfo) (T.decodeUtf8 $ P.fileContentType fileInfo) (P.fileContent fileInfo)-                                                  )-                                             ) bodyFiles-                                   postParams =-                                       map (\(k, v) -> (T.decodeUtf8 k, T.decodeUtf8 v)) bodyParams-                                   getParams =-                                       map (\(k, mV) -> (T.decodeUtf8 k, T.decodeUtf8 $ fromMaybe BS.empty mV)) $ Wai.queryString req-                                   queryParams = postParams ++ getParams-                                   resp = errorResponse status200 ""-                                   allActions = matchRoute' (Wai.pathInfo req) routeTree--                                   applyAction :: [(ParamMap, ActionT m ())] -> m ResponseState-                                   applyAction [] =-                                       return $ errorResponse status404 "404 - File not found"-                                   applyAction ((captures, selectedAction) : xs) =-                                       do let env = RequestInfo req captures queryParams uploadedFiles-                                          (r, respState, _) <--                                              runRWST (runErrorT $ runActionT $ selectedAction) env resp-                                          case r of-                                            Left (ActionRedirect loc) ->-                                                return $ 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-                                            Left ActionDone ->-                                                return respState-                                            Right () ->-                                                return respState-                               respState <--                                   liftIO $-                                   (spockLift $ 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-                    Nothing ->-                        respond notFound-       return $ spockMiddleware $ app---- | Hook up a 'Wai.Middleware'-middleware :: MonadIO m => Wai.Middleware -> SpockT m ()-middleware mw =-    modify $ \st -> st { ss_middleware = (ss_middleware st ++ [mw]) }---- | Define a route matching a provided 'StdMethod' and route-defRoute :: (MonadIO m) => StdMethod -> T.Text -> ActionT m () -> SpockT m ()-defRoute method route action =-    do baseRoute <- ask-       let fullRoute = baseRoute `combineRoute` route-       modify $ \st -> st { ss_treeMap = HM.insertWith HM.union method (HM.singleton fullRoute action) (ss_treeMap st) }---- | Combine two routes, ensuring that the slashes don't get messed up-combineRoute :: T.Text -> T.Text -> T.Text-combineRoute r1 r2 =-    case T.uncons r1 of-      Nothing -> T.concat ["/", r2']-      Just ('/', _) -> T.concat [r1', r2']-      Just _ -> T.concat ["/", r1', r2']-    where-      r1' =-          if T.last r1 == '/'-          then r1-          else if T.null r2-               then r1-               else T.concat [r1, "/"]-      r2' =-          if T.null r2-          then ""-          else if T.head r2 == '/' then T.drop 1 r2 else r2---- | Define a subcomponent------ > subcomponent "/api" $--- >    do get "/user" $ text "USER"--- >       post "/new-user" $ text "OK!"------ >>> curl http://localhost:8080/api/user--- USER----subcomponent :: (MonadIO m) => T.Text -> SpockT m a -> SpockT m a-subcomponent baseRoute defs =-    do parentState <- get-       parentRoute <- ask-       let initState =-               parentState-               { ss_treeMap = HM.empty-               , ss_middleware = []-               }-       (a, finalState, ()) <--           liftIO $ (ss_spockLift parentState) $-           runRWST (runSpockT defs) (parentRoute `combineRoute` baseRoute) initState-       modify $ \st ->-           st-           { ss_treeMap = HM.unionWith HM.union (ss_treeMap st) (ss_treeMap finalState)-           , ss_middleware = (ss_middleware st) ++ (ss_middleware finalState)-           }-       return a--buildRoutingTree :: SpockRouteMap m -> SpockTreeMap m-buildRoutingTree routeMap =-    HM.map (\v -> foldl treeBuilder emptyRoutingTree $ HM.toList v) routeMap-    where-      treeBuilder tree (route, action) =-          addToRoutingTree route action tree
test/Spec.hs view
@@ -1,1 +1,5 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}+import Test.Hspec+import Web.Spock.Specs.All++main :: IO ()+main = hspec allSpecs
− test/Web/Spock/RoutingSpec.hs
@@ -1,109 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Web.Spock.RoutingSpec (spec) where--import Test.Hspec--import Web.Spock.Routing-import qualified Data.Vector as V-import qualified Data.HashMap.Strict as HM--spec :: Spec-spec =-    do matchNodeDesc-       matchRouteDesc-       parseRouteNodeDesc-       addToRoutingTreeDesc--matchNodeDesc :: Spec-matchNodeDesc =-    describe "matchNode" $-    do it "shouldn't match to root node" $-          matchNode "foo" RouteNodeRoot `shouldBe` (False, Nothing)-       it "should capture basic variables" $-          matchNode "123" (RouteNodeCapture (CaptureVar "x")) `shouldBe` (True, Just (CaptureVar "x", "123"))-       it "should work with regex" $-          matchNode "123" (RouteNodeRegex (CaptureVar "x") (buildRegex "^[0-9]+$")) `shouldBe` (True, Just (CaptureVar "x", "123"))--matchRouteDesc :: Spec-matchRouteDesc =-    describe "matchRoute" $-    do it "shouldn't match unknown routes" $-          do matchRoute "random" routingTree `shouldBe` noMatches-             matchRoute "/baz" routingTree `shouldBe` noMatches-       it "should match known routes" $-          do matchRoute "/" routingTree `shouldBe` oneMatch emptyParamMap [1]-             matchRoute "/bar" routingTree `shouldBe` oneMatch emptyParamMap [2]-       it "should capture variables in routes" $-          do matchRoute "/bar/5" routingTree `shouldBe` oneMatch (vMap [("baz", "5")]) [3]-             matchRoute "/bar/23/baz" routingTree `shouldBe` oneMatch (vMap [("baz", "23")]) [4]-             matchRoute "/bar/23/baz/100" routingTree `shouldBe` oneMatch (vMap [("baz", "23"), ("bim", "100")]) [4]-             matchRoute "/ba/23/100" routingTree `shouldBe` oneMatch (vMap [("baz", "23"), ("bim", "100")]) [4]-             matchRoute "/entry/344/2014-20-14T12:23" routingTree `shouldBe` oneMatch (vMap [("cid", "344"), ("since", "2014-20-14T12:23")]) [6]-             matchRoute "/entry/bytags/344/2014-20-14T12:23" routingTree `shouldBe` oneMatch (vMap [("cid", "344"), ("since", "2014-20-14T12:23")]) [7]-             matchRoute "/entry/2/rel/3" routingTree `shouldBe` oneMatch (vMap [("eid", "2"), ("cid", "3")]) [9]-       it "should handle multiple possibile matches correctly" $-          do matchRoute "/bar/bingo" routingTree `shouldBe` multiMatch-             matchRoute "/entry/1/audit" routingTree `shouldBe` multiMatch'-    where-      vMap kv =-          HM.fromList $ map (\(k, v) -> (CaptureVar k, v)) kv-      multiMatch =-          ((oneMatch emptyParamMap [5])-            ++ oneMatch (vMap [("baz", "bingo")]) [3])-      multiMatch' =-          ((oneMatch (vMap [("since", "audit"), ("cid", "1")]) [6])-           ++ (oneMatch (vMap [("eid", "1")]) [8]))-      noMatches = []-      oneMatch pm m = [(pm, m)]-      routingTree =-          foldl (\tree (route, action) -> addToRoutingTree route action tree) emptyRoutingTree routes-      routes =-          [ ("/", [1])-          , ("/bar", [2 :: Int])-          , ("/bar/:baz", [3])-          , ("/bar/bingo", [5])-          , ("/bar/:baz/baz", [4])-          , ("/bar/:baz/baz/:bim", [4])-          , ("/ba/:baz/:bim", [4])-          , ("/entry/:cid/:since", [6])-          , ("/entry/bytags/:cid/:since", [7])-          , ("/entry/:eid/audit", [8])-          , ("/entry/:eid/rel/:cid", [9])-          ]--parseRouteNodeDesc :: Spec-parseRouteNodeDesc =-    describe "parseRouteNode" $-    do it "parses text nodes correctly" $-          parseRouteNode "foo" `shouldBe` RouteNodeText "foo"-       it "parses capture variables" $-          parseRouteNode ":bar" `shouldBe` RouteNodeCapture (CaptureVar "bar")-       it "parses regex capture variables" $-          parseRouteNode "{bar:^[0-9]$}" `shouldBe` RouteNodeRegex (CaptureVar "bar") (buildRegex "^[0-9]$")--addToRoutingTreeDesc :: Spec-addToRoutingTreeDesc =-    describe "addToRoutingTree" $-    do it "adds the root node correctly" $-          addToRoutingTree "/" [True] emptyT `shouldBe` baseRoute-       it "adds a new branch correctly" $-          addToRoutingTree "/foo/:bar" [True] emptyT `shouldBe` fooBar []-       it "add a new subbranch correctly" $-          addToRoutingTree "/foo/:bar/baz" [True] (fooBar []) `shouldBe` fooBar baz-    where-      emptyT = emptyRoutingTree-      baseRoute = RoutingTree { rt_node = RouteData{rd_node = RouteNodeRoot, rd_data = Just [True]}, rt_children = V.empty}-      baz = [ RoutingTree { rt_node = RouteData { rd_node = RouteNodeText "baz", rd_data = Just [True] },rt_children = V.empty }]-      fooBar xs =-          RoutingTree-          { rt_node =-                RouteData {rd_node = RouteNodeRoot, rd_data = Nothing }-          , rt_children =-              V.fromList-                   [ RoutingTree { rt_node = RouteData{rd_node = RouteNodeText "foo", rd_data = Nothing}-                                 , rt_children =-                                     V.fromList-                                          [ RoutingTree { rt_node = RouteData { rd_node = RouteNodeCapture (CaptureVar "bar")-                                                                              , rd_data = Just [True]-                                                                              }-                                                        , rt_children = V.fromList xs}]}]}
− test/Web/Spock/WireSpec.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Web.Spock.WireSpec (spec) where--import Web.Spock.Wire-import Test.Hspec--spec :: Spec-spec =-    do describe "combineRoute" $-         do it "handles slashes correctly" $-               do ("/" `combineRoute`  "foo") `shouldBe` "/foo"-                  ("" `combineRoute`  "foo") `shouldBe` "/foo"-                  ("/" `combineRoute`  "/foo") `shouldBe` "/foo"-                  ("" `combineRoute`  "/foo") `shouldBe` "/foo"-                  ("/test" `combineRoute`  "foo") `shouldBe` "/test/foo"-                  ("/test" `combineRoute`  "") `shouldBe` "/test"