packages feed

Wheb 0.2.0.0 → 0.3.0.0

raw patch · 13 files changed

+256/−161 lines, 13 filesdep ~WhebPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: Wheb

API changes (from Hackage documentation)

+ Web.Wheb: throwRedirect :: Monad m => Text -> WhebHandlerT g s m
+ Web.Wheb.Plugins.Cache: CacheContainer :: r -> CacheContainer
+ Web.Wheb.Plugins.Cache: backendCacheDelete :: (CacheBackend c, CacheApp a, MonadIO m) => Text -> c -> WhebT a b m ()
+ Web.Wheb.Plugins.Cache: backendCacheGet :: (CacheBackend c, CacheApp a, MonadIO m) => Text -> c -> WhebT a b m (Maybe ByteString)
+ Web.Wheb.Plugins.Cache: backendCachePut :: (CacheBackend c, CacheApp a, MonadIO m) => Text -> ByteString -> Integer -> c -> WhebT a b m ()
+ Web.Wheb.Plugins.Cache: class CacheApp a
+ Web.Wheb.Plugins.Cache: class CacheBackend c
+ Web.Wheb.Plugins.Cache: data CacheContainer
+ Web.Wheb.Plugins.Cache: deleteCacheValue :: (CacheApp a, MonadIO m) => Text -> WhebT a b m ()
+ Web.Wheb.Plugins.Cache: getCacheContainer :: CacheApp a => a -> CacheContainer
+ Web.Wheb.Plugins.Cache: getCacheValue :: (CacheApp a, MonadIO m) => Text -> WhebT a b m (Maybe ByteString)
+ Web.Wheb.Plugins.Cache: getCacheValue' :: (CacheApp a, MonadIO m) => ByteString -> Text -> WhebT a b m ByteString
+ Web.Wheb.Plugins.Cache: setCacheValue :: (CacheApp a, MonadIO m) => Text -> ByteString -> WhebT a b m ()
+ Web.Wheb.Plugins.Cache: setCacheValue' :: (CacheApp a, MonadIO m) => Text -> ByteString -> Integer -> WhebT a b m ()
+ Web.Wheb.Plugins.Debug.MemoryBackend: CacheData :: TVar (Map Text ByteString) -> CacheData
+ Web.Wheb.Plugins.Debug.MemoryBackend: cacheStorage :: CacheData -> TVar (Map Text ByteString)
+ Web.Wheb.Plugins.Debug.MemoryBackend: data CacheData
+ Web.Wheb.Plugins.Debug.MemoryBackend: initCacheMemory :: InitM g s m CacheContainer
+ Web.Wheb.Plugins.Debug.MemoryBackend: instance CacheBackend CacheData
+ Web.Wheb.Types: ErrorStatus :: Status -> Text -> WhebError
+ Web.Wheb.Types: curCookies :: InternalState s -> CookiesText
+ Web.Wheb.Utils: builderToStrictText :: Builder -> Text
+ Web.Wheb.Utils: spacks :: Show a => a -> Text
+ Web.Wheb.WhebT: throwRedirect :: Monad m => Text -> WhebHandlerT g s m
- Web.Wheb.Types: Error500 :: String -> WhebError
+ Web.Wheb.Types: Error500 :: Text -> WhebError
- Web.Wheb.Types: InternalState :: s -> Map HeaderName ByteString -> InternalState s
+ Web.Wheb.Types: InternalState :: s -> Map HeaderName ByteString -> CookiesText -> InternalState s

Files

Wheb.cabal view
@@ -1,5 +1,5 @@ name:                Wheb-version:             0.2.0.0+version:             0.3.0.0 synopsis:            The frictionless WAI Framework license:             BSD3 license-file:        LICENSE@@ -16,6 +16,8 @@   .   * Minimal boilerplate to start your application.   .+  * Session, Auth and Cache interfaces are built in. Just drop in a backend.+  .   * Typesafe web-routes or named routes and URL generation.   .   * Easy to use for REST APIs@@ -41,20 +43,25 @@   .   * Auth   .+  * Cache+  .   * <http://hackage.haskell.org/package/wheb-mongo Wheb-Mongo>   .+  * <http://hackage.haskell.org/package/wheb-redis Wheb-Redis>+  .   * <http://hackage.haskell.org/package/wheb-strapped Wheb-Strapped>   .   /Wheb in action:/   .+  Use with language extensions /OverloadedStrings/+  .   > import           Web.Wheb-  > import           Data.Text.Lazy (pack)   >   > main :: IO ()   > main = do   >   opts <- genMinOpts $ do-  >      addGET "home" rootPat $ (text (pack "Hi!"))-  >      addGET "about" ("about" </> "something") $ html (pack "<html><body><h1>About!</h1></body></html>")+  >      addGET "home" rootPat $ text "Hi!"+  >      addGET "about" ("about" </> "something") $ html "<html><body><h1>About!</h1></body></html>"   >   runWhebServer opts   .   /Bigger example (Stateful.hs):/@@ -72,7 +79,7 @@   >  import           Control.Concurrent.STM   >  import           Control.Monad.IO.Class   >  import           Data.Monoid-  >  import           Data.Text.Lazy (Text, pack)+  >  import           Data.Text.Lazy (Text)   >  import           Web.Wheb   >   >  data MyApp = MyApp Text (TVar Int)@@ -100,7 +107,7 @@   >    opts <- generateOptions $ do   >              startingCounter <- liftIO $ newTVarIO 0   >              addWhebMiddleware counterMw-  >              addGET (pack ".") rootPat $ homePage+  >              addGET "." rootPat $ homePage   >              return $ (MyApp "AwesomeApp" startingCounter, MyHandlerData 0)   >    runWhebServer opts @@ -111,7 +118,7 @@  library   default-language: Haskell2010-  exposed-modules:     Web.Wheb, Web.Wheb.Cookie, Web.Wheb.InitM, Web.Wheb.Internal, Web.Wheb.Routes, Web.Wheb.Types, Web.Wheb.Utils, Web.Wheb.WhebT, Web.Wheb.Plugins.Auth, Web.Wheb.Plugins.Session, Web.Wheb.Plugins.Debug.MemoryBackend+  exposed-modules:     Web.Wheb, Web.Wheb.Cookie, Web.Wheb.InitM, Web.Wheb.Internal, Web.Wheb.Routes, Web.Wheb.Types, Web.Wheb.Utils, Web.Wheb.WhebT, Web.Wheb.Plugins.Auth, Web.Wheb.Plugins.Session, Web.Wheb.Plugins.Cache, Web.Wheb.Plugins.Debug.MemoryBackend      build-depends:       base >= 4.7 && < 4.8,                         text >= 1.0 && < 1.2, @@ -140,7 +147,7 @@  test-suite Main   type:            exitcode-stdio-1.0-  build-depends:   Wheb ==0.2.*,+  build-depends:   Wheb ==0.3.*,                    base ==4.7.*,                     HUnit >= 1.2 && < 2,                    QuickCheck >= 2.4,
src/Web/Wheb.hs view
@@ -34,6 +34,8 @@   , file   , builder   , redirect+  , throwRedirect+     -- *** Setting a header   , setHeader   , setRawHeader@@ -117,4 +119,4 @@ import Web.Wheb.Routes ((</>), compilePat, grabInt, grabText, pS, pT, rootPat) import Web.Wheb.Types (ChunkType(..), CSettings, WhebSocket, EResponse, HandlerData(..), HandlerResponse(..), InitM(..), InitOptions(..), InternalState(..), MethodMatch, MinHandler, MinOpts, MinWheb, PackedSite(..), ParsedChunk(..), Route(..), RouteParamList, SettingsValue(..), UrlBuildError(..), UrlParser(..), UrlPat(..), WhebContent(..), WhebError(..), WhebFile(..), WhebHandler, WhebHandlerT, WhebMiddleware, WhebOptions(..), WhebT(..)) import Web.Wheb.Utils (spack)-import Web.Wheb.WhebT (builder, runRawHandler, runRawHandlerT, file, redirect, getApp, getHandlerState, getPOSTParam, getPOSTParams, getQueryParams, getRawPOST, getRequest, getRequestHeader, getRoute, getRoute', getRouteParam, getRouteParams, getSetting, getSetting', getSetting'', getSettings, getWithApp, getWithRequest, html, modifyHandlerState, modifyHandlerState', putHandlerState, runWhebServer, runWhebServerT, setHeader, setRawHeader, text)+import Web.Wheb.WhebT (builder, runRawHandler, runRawHandlerT, file, redirect, throwRedirect, getApp, getHandlerState, getPOSTParam, getPOSTParams, getQueryParams, getRawPOST, getRequest, getRequestHeader, getRoute, getRoute', getRouteParam, getRouteParams, getSetting, getSetting', getSetting'', getSettings, getWithApp, getWithRequest, html, modifyHandlerState, modifyHandlerState', putHandlerState, runWhebServer, runWhebServerT, setHeader, setRawHeader, text)
src/Web/Wheb/Cookie.hs view
@@ -7,18 +7,18 @@   , getCookies   , removeCookie   ) where-    ++import Control.Monad (liftM) import qualified Blaze.ByteString.Builder as B (toByteString)-import Data.Maybe (fromMaybe)-import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as T (empty, fromStrict, toStrict)+import Control.Monad.State (modify, MonadState(get))+import Data.Text (Text)+import qualified Data.Text as TS (empty)+import qualified Data.Text.Encoding as TS (encodeUtf8) import Data.Time.Calendar (Day(ModifiedJulianDay)) import Data.Time.Clock (secondsToDiffTime, UTCTime(UTCTime))-import Web.Cookie (CookiesText, def, parseCookiesText, renderSetCookie, -                   SetCookie(setCookieExpires, setCookieName, setCookieValue))-import Web.Wheb.Types (WhebT)-import Web.Wheb.Utils (lazyTextToSBS)-import Web.Wheb.WhebT (getRequestHeader, setRawHeader)+import Web.Cookie (CookiesText, def, renderSetCookie, SetCookie(..))+import Web.Wheb.Types+import Web.Wheb.WhebT (setRawHeader)  getDefaultCookie :: Monad m => WhebT g s m SetCookie getDefaultCookie = return def -- Populate with settings...@@ -27,24 +27,23 @@ setCookie k v = getDefaultCookie >>= (setCookie' k v)  setCookie' :: Monad m => Text -> Text -> SetCookie -> WhebT g s m ()-setCookie' k v sc = setRawHeader ("Set-Cookie", cookieText)-  where cookie = sc { setCookieName = lazyTextToSBS k-                    , setCookieValue = lazyTextToSBS v+setCookie' k v sc = do+  setRawHeader ("Set-Cookie", cookieText)+  WhebT $ modify (\a -> a {curCookies = [(k,v)] ++ (curCookies a)})+  where cookie = sc { setCookieName = TS.encodeUtf8 k+                    , setCookieValue = TS.encodeUtf8 v                     }         cookieText = B.toByteString $ renderSetCookie cookie          getCookies :: Monad m => WhebT g s m CookiesText-getCookies = (getRequestHeader "Cookie") >>= -             (return . parseFunc . (fromMaybe T.empty))-  where parseFunc = parseCookiesText . lazyTextToSBS+getCookies = WhebT $ liftM (curCookies) get  getCookie :: Monad m => Text -> WhebT g s m (Maybe Text)-getCookie k = getCookies >>= -    (return . (fmap T.fromStrict) . (lookup (T.toStrict k)))+getCookie k = liftM (lookup k) getCookies  removeCookie :: Monad m => Text -> WhebT g s m () removeCookie k = do   defCookie <- getDefaultCookie   let utcLongAgo = UTCTime (ModifiedJulianDay 0) (secondsToDiffTime 0)       expiredCookie = defCookie {setCookieExpires = Just utcLongAgo}-  setCookie' k T.empty expiredCookie+  setCookie' k TS.empty expiredCookie
src/Web/Wheb/InitM.hs view
@@ -35,8 +35,8 @@ import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Writer (liftM, MonadWriter(tell), Monoid(mempty), WriterT(runWriterT)) import qualified Data.Map as M (empty, fromList)-import qualified Data.Text.Lazy as T (lines, pack, splitOn, strip, Text, unpack)-import qualified Data.Text.Lazy.IO as T (readFile)+import qualified Data.Text as T (lines, pack, splitOn, strip, Text, unpack)+import qualified Data.Text.IO as T (readFile) import Data.Typeable (Typeable) import Network.HTTP.Types.Method (StdMethod(DELETE, GET, POST, PUT)) import Network.Wai (Middleware)@@ -134,7 +134,7 @@                        , runTimeSettings = initSettings                        , warpSettings = warpsettings                        , startingCtx = g-                       , startingState = InternalState s M.empty+                       , startingState = InternalState s M.empty []                        , waiStack = initWaiMw                        , whebMiddlewares = initWhebMw                        , defaultErrorHandler = defaultErr
src/Web/Wheb/Internal.hs view
@@ -2,24 +2,23 @@  module Web.Wheb.Internal where +import qualified Data.CaseInsensitive as CI import qualified Data.ByteString.Char8 as B+import Data.Maybe (fromMaybe) import Control.Monad (void)-import Control.Monad.Except (ExceptT, runExceptT)+import Control.Monad.Except (runExceptT) import Control.Monad.Reader (ReaderT(runReaderT)) import Control.Monad.State (evalStateT, StateT(runStateT)) import qualified Data.Map as M (toList)-import qualified Data.Text.Lazy as T (fromStrict, Text, toStrict, pack) import Network.HTTP.Types.Method (parseMethod, StdMethod(GET))-import Network.Wai (Application, Request(pathInfo, requestMethod), Response)+import Network.Wai (Application, Request(..), Response) import Network.Wai.Parse (lbsBackEnd, parseRequestBody) import Network.Wai.Handler.WebSockets (websocketsOr) import qualified Network.WebSockets as W import Web.Wheb.Routes (findUrlMatch, findSiteMatch, findSocketMatch)-import Web.Wheb.Types (EResponse, HandlerData(HandlerData, postData, routeParams),-                       HandlerResponse(HandlerResponse), InternalState(..), -                       WhebContent(toResponse), WhebError(Error404), WhebHandlerT, -                       WhebMiddleware, WhebOptions(..), WhebT(runWhebT))+import Web.Wheb.Types import Web.Wheb.Utils (uhOh)+import Web.Cookie (parseCookiesText)  -- * Converting to WAI application                       @@ -37,21 +36,21 @@                   Just (h, params) -> do                       c <- W.acceptRequest pc                       void $ runIO $ do-                            (mRes, st) <- runMiddlewares opts whebMiddlewares baseData-                            runDebugHandler (opts {startingState = st}) (h c) (baseData { routeParams = params })+                            (mRes, st) <- runMiddlewares initOpts whebMiddlewares baseData+                            runDebugHandler (initOpts {startingState = st}) (h c) (baseData { routeParams = params })                   Nothing -> W.rejectRequest pc (B.pack "No socket for path.") -        handleMain r respond = do-            pData <- parseRequestBody lbsBackEnd r+        handleMain r' respond' = do+            pData <- parseRequestBody lbsBackEnd r'             res <- runIO $ do                     let mwData = baseData { postData = pData }-                    (mRes, st) <- runMiddlewares opts whebMiddlewares mwData+                    (mRes, st) <- runMiddlewares initOpts whebMiddlewares mwData                     case mRes of                         Just resp -> return $ Right resp                         Nothing -> do                             case (findSiteMatch appSites pathChunks) of                               Just h -> do-                                runWhebHandler opts h st mwData+                                runWhebHandler initOpts h st mwData                               Nothing -> do                                   case (findUrlMatch stdMthd pathChunks appRoutes) of                                         Just (h, params) -> do@@ -59,11 +58,13 @@                                             runWhebHandler opts h st hData                                          Nothing          -> return $ Left Error404             finished <- either handleError return res-            respond finished+            respond' finished         baseData   = HandlerData startingCtx r ([], []) [] opts-        pathChunks = fmap T.fromStrict $ pathInfo r+        parsedCookies = parseCookiesText $ (fromMaybe B.empty) $ (lookup $ CI.mk $ B.pack "Cookies") $ requestHeaders r+        initOpts = opts {startingState = startingState {curCookies = parsedCookies}}+        pathChunks = pathInfo r         stdMthd    = either (\_-> GET) id $ parseMethod $ requestMethod r-        runErrorHandler eh = runWhebHandler opts eh startingState baseData+        runErrorHandler eh = runWhebHandler initOpts eh startingState baseData         handleError err = do           errRes <- runIO $ runErrorHandler (defaultErrorHandler err)           either (return . (const uhOh)) return errRes
src/Web/Wheb/Plugins/Auth.hs view
@@ -36,7 +36,7 @@ import Control.Monad.Except (liftM, MonadError(throwError), MonadIO(..)) import Crypto.PasswordStore (makePassword, verifyPassword) import Data.Text.Encoding as ES (decodeUtf8, encodeUtf8)-import Data.Text.Lazy as T (fromStrict, pack, Text, toStrict)+import Data.Text as T (pack, Text) import Web.Wheb (getHandlerState, getWithApp, modifyHandlerState',                   WhebError(Error403), WhebHandlerT, WhebMiddleware, WhebT) import Web.Wheb.Plugins.Session (deleteSessionValue, getSessionValue', SessionApp, setSessionValue)@@ -140,9 +140,9 @@ getUserSessionKey = return $ T.pack "user-id" -- later read from settings.  makePwHash :: MonadIO m => Password -> WhebT a b m PwHash-makePwHash pw = liftM (T.fromStrict . ES.decodeUtf8) $ -                        liftIO $ makePassword (ES.encodeUtf8 $ T.toStrict pw) 14+makePwHash pw = liftM (ES.decodeUtf8) $ +                        liftIO $ makePassword (ES.encodeUtf8 $ pw) 14  verifyPw :: Text -> Text -> Bool-verifyPw pw hash = verifyPassword (ES.encodeUtf8 $ T.toStrict pw) -                          (ES.encodeUtf8 $ T.toStrict hash)+verifyPw pw hash = verifyPassword (ES.encodeUtf8 $ pw) +                          (ES.encodeUtf8 $ hash)
+ src/Web/Wheb/Plugins/Cache.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RankNTypes #-}++module Web.Wheb.Plugins.Cache+  ( CacheContainer (..)+  , CacheApp (..)+  , CacheBackend (..)+  +  , setCacheValue+  , setCacheValue'+  , getCacheValue+  , getCacheValue'+  , deleteCacheValue+  ) where+    +import Control.Monad (liftM)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Data.ByteString as BS (ByteString)+import Web.Wheb++data CacheContainer = forall r. CacheBackend r => CacheContainer r++class CacheApp a where+  getCacheContainer :: a -> CacheContainer++class CacheBackend c where+  backendCachePut    :: (CacheApp a, MonadIO m) => Text -> ByteString -> Integer -> c -> WhebT a b m ()+  backendCacheGet    :: (CacheApp a, MonadIO m) => Text -> c -> WhebT a b m (Maybe ByteString)+  backendCacheDelete :: (CacheApp a, MonadIO m) => Text -> c -> WhebT a b m ()++runWithContainer :: (CacheApp a, MonadIO m) => (forall r. CacheBackend r => r -> WhebT a s m b) -> WhebT a s m b+runWithContainer f = do+  CacheContainer cacheStore <- getWithApp getCacheContainer+  f cacheStore++deleteCacheValue :: (CacheApp a, MonadIO m) => Text -> WhebT a b m ()+deleteCacheValue key = runWithContainer $ backendCacheDelete key++-- | Set a cache value with an expiration of an hour+setCacheValue :: (CacheApp a, MonadIO m) => Text -> ByteString -> WhebT a b m ()+setCacheValue key content = setCacheValue' key content (60 * 60)++-- | Set a cache value with expiration in seconds+setCacheValue' :: (CacheApp a, MonadIO m) => Text -> ByteString -> Integer -> WhebT a b m ()+setCacheValue' key content expr = runWithContainer $ backendCachePut key content expr++getCacheValue :: (CacheApp a, MonadIO m) => Text -> WhebT a b m (Maybe ByteString)+getCacheValue key = runWithContainer $ backendCacheGet key++getCacheValue' :: (CacheApp a, MonadIO m) => ByteString -> Text -> WhebT a b m ByteString+getCacheValue' def key = liftM (fromMaybe def) (getCacheValue key)
src/Web/Wheb/Plugins/Debug/MemoryBackend.hs view
@@ -5,17 +5,32 @@ import Control.Monad.IO.Class (MonadIO(liftIO)) import Data.Map as M (alter, delete, empty, insert, lookup, Map, member, update) import Data.Maybe (fromMaybe)-import Data.Text.Lazy (Text)+import Data.Text (Text)+import Data.ByteString (ByteString) import Web.Wheb (InitM)-import Web.Wheb.Plugins.Auth (AuthBackend(..), AuthContainer(..), AuthError(..), AuthUser(AuthUser), -                              getUserSessionKey, makePwHash, PwHash, UserKey, verifyPw)-import Web.Wheb.Plugins.Session (deleteSessionValue, SessionBackend(..), SessionContainer(..))+import Web.Wheb.Plugins.Auth+import Web.Wheb.Plugins.Cache+import Web.Wheb.Plugins.Session  data SessionData = SessionData    { sessionMemory ::  TVar (M.Map Text (M.Map Text Text)) } data UserData = UserData   { userStorage :: TVar (M.Map UserKey PwHash) }+data CacheData = CacheData+  { cacheStorage :: TVar (M.Map Text ByteString) } ++-- | In memory cache backend. Cache value+-- will not persist after server restart and will never clear old values.+instance CacheBackend CacheData where+  backendCachePut key content _ (CacheData tv) = do+    liftIO $ atomically $ modifyTVar tv (M.insert key content)+  backendCacheGet key (CacheData tv) = do+      curCache <- liftIO $ readTVarIO tv+      return $ (M.lookup key curCache)+  backendCacheDelete key (CacheData tv) =+      liftIO $ atomically $ modifyTVar tv (M.delete key)+ -- | In memory session backend. Session values  -- will not persist after server restart. instance SessionBackend SessionData where@@ -68,3 +83,8 @@ initAuthMemory = do   tv <- liftIO $ newTVarIO $ M.empty   return $! AuthContainer $ UserData tv+  +initCacheMemory :: InitM g s m CacheContainer+initCacheMemory = do+  tv <- liftIO $ newTVarIO $ M.empty+  return $! CacheContainer $ CacheData tv
src/Web/Wheb/Plugins/Session.hs view
@@ -19,9 +19,9 @@ import Control.Monad (liftM) import Control.Monad.IO.Class (MonadIO(..)) import Data.Maybe (fromMaybe)-import Data.Text.Lazy (pack, Text)-import Data.Text.Lazy.Encoding as T (decodeUtf8)-import Data.UUID (toLazyASCIIBytes)+import Data.Text (pack, Text)+import Data.Text.Encoding as T (decodeUtf8)+import Data.UUID (toASCIIBytes) import Data.UUID.V4 (nextRandom) import Web.Wheb (getWithApp, WhebT) import Web.Wheb.Cookie (getCookie, setCookie)@@ -71,7 +71,7 @@      generateSessionKey :: (SessionApp a, MonadIO m) => WhebT a b m Text generateSessionKey = do-  newKey <- liftM (T.decodeUtf8 . toLazyASCIIBytes) (liftIO nextRandom)+  newKey <- liftM (T.decodeUtf8 . toASCIIBytes) (liftIO nextRandom)   setCookie session_cookie_key newKey   return newKey 
src/Web/Wheb/Routes.hs view
@@ -24,20 +24,18 @@   ) where    import Data.Monoid ((<>))-import qualified Data.Text.Lazy as T (fromStrict, null, pack, Text, toStrict)-import Data.Text.Lazy.Read (decimal, Reader)+import qualified Data.Text as TS (null, pack, Text)+import qualified Data.Text.Encoding as TS (encodeUtf8)+import Data.Text.Read (decimal, Reader) import Data.Typeable (cast, Typeable) import Network.HTTP.Types.Method (StdMethod) import Network.HTTP.Types.URI (decodePathSegments, encodePathSegments) import Web.Routes (runSite)-import Web.Wheb.Types (ChunkType(..), ParsedChunk(..), Route(Route), RouteParamList, -                       UrlBuildError(NoParam, ParamTypeMismatch), UrlParser(UrlParser),-                       UrlPat(Chunk, Composed, FuncChunk), WhebHandlerT, SocketRoute(SocketRoute), -                       PackedSite(..), WhebSocket)-import Web.Wheb.Utils (builderToText, lazyTextToSBS, spack)+import Web.Wheb.Types+import Web.Wheb.Utils (builderToText, lazyTextToSBS, spacks, builderToStrictText)  -- | Build a 'Route' from a 'UrlPat'-patRoute :: (Maybe T.Text) -> +patRoute :: (Maybe TS.Text) ->              StdMethod ->              UrlPat ->              WhebHandlerT g s m -> @@ -51,7 +49,7 @@  -- | Represents root path @/@ rootPat :: UrlPat-rootPat = Chunk $ T.pack ""+rootPat = Chunk $ TS.pack ""  -- | Allows for easier building of URL patterns --   This should be the primary URL constructor.@@ -65,39 +63,39 @@ a </> b = Composed [a, b]  -- | Parses URL parameter and matches on 'Int'-grabInt :: T.Text -> UrlPat+grabInt :: TS.Text -> UrlPat grabInt key = FuncChunk key f IntChunk   where rInt = decimal :: Reader Int         f = ((either (const Nothing) (Just . MkChunk . fst)) . rInt)  -- | Parses URL parameter and matches on 'Text'-grabText :: T.Text -> UrlPat+grabText :: TS.Text -> UrlPat grabText key = FuncChunk key (Just . MkChunk) TextChunk  -- | Constructors to use w/o OverloadedStrings-pT :: T.Text -> UrlPat+pT :: TS.Text -> UrlPat pT = Chunk  pS :: String -> UrlPat-pS = pT . T.pack+pS = pT . TS.pack  -- | Lookup and cast a URL parameter to its expected type if it exists.-getParam :: Typeable a => T.Text -> RouteParamList -> Maybe a+getParam :: Typeable a => TS.Text -> RouteParamList -> Maybe a getParam k l = (lookup k l) >>= unwrap   where unwrap :: Typeable a => ParsedChunk -> Maybe a         unwrap (MkChunk a) = cast a  -- | Convert URL chunks (split on /)                                -matchUrl :: [T.Text] -> UrlParser -> Maybe RouteParamList+matchUrl :: [TS.Text] -> UrlParser -> Maybe RouteParamList matchUrl url (UrlParser f _) = f url  -- | Runs a 'UrlParser' with 'RouteParamList' to a URL path-generateUrl :: UrlParser -> RouteParamList -> Either UrlBuildError T.Text+generateUrl :: UrlParser -> RouteParamList -> Either UrlBuildError TS.Text generateUrl (UrlParser _ f) = f  -- | Sort through a list of routes to find a Handler and 'RouteParamList' findUrlMatch :: StdMethod ->-                [T.Text] ->+                [TS.Text] ->                 [Route g s m] ->                 Maybe (WhebHandlerT g s m, RouteParamList) findUrlMatch _ _ [] = Nothing@@ -108,7 +106,7 @@                         Nothing -> findUrlMatch rmtd path rs  -- | Sort through socket routes to find a match-findSocketMatch :: [T.Text] -> [SocketRoute g s m] -> Maybe (WhebSocket g s m, RouteParamList)+findSocketMatch :: [TS.Text] -> [SocketRoute g s m] -> Maybe (WhebSocket g s m, RouteParamList) findSocketMatch _ [] = Nothing findSocketMatch path ((SocketRoute (UrlParser f _) h):rs) =      case f path of@@ -116,22 +114,22 @@         Nothing -> findSocketMatch path rs  findSiteMatch :: [PackedSite g s m] -> -                 [T.Text] -> +                 [TS.Text] ->                   Maybe (WhebHandlerT g s m) findSiteMatch [] _ = Nothing findSiteMatch ((PackedSite t site):sites) cs =    either (const (findSiteMatch sites cs)) Just $-        runSite (T.toStrict t) site (map T.toStrict cs)+        runSite t site cs  -- | Test a parser to make sure it can match what it produces and vice versa testUrlParser :: UrlParser -> RouteParamList -> Bool testUrlParser up rpl =    case generateUrl up rpl of       Left _ -> False-      Right t -> case (matchUrl (fmap T.fromStrict $ decodeUrl t) up) of+      Right t -> case (matchUrl (decodeUrl t) up) of           Just params -> either (const False) (==t) (generateUrl up params)           Nothing -> False-  where decodeUrl = decodePathSegments . lazyTextToSBS+  where decodeUrl = decodePathSegments . TS.encodeUtf8  -- | Implementation for a 'UrlParser' using pseudo-typed URL composition. --   Pattern will match path when the pattern is as long as the path, matching@@ -142,15 +140,15 @@ --       This will match on "/blog/1/edit" and /blog/9999/edit/" --       But not "/blog/1/", "/blog/1", "blog/foo/edit/", "/blog/9/edit/d", --       nor "/blog/9/edit//"-matchPat :: [UrlPat] ->  [T.Text] -> Maybe RouteParamList-matchPat chunks [] = matchPat chunks [T.pack ""]+matchPat :: [UrlPat] ->  [TS.Text] -> Maybe RouteParamList+matchPat chunks [] = matchPat chunks [TS.pack ""] matchPat chunks t  = parse t chunks []   where parse [] [] params = Just params-        parse [] c  params = Nothing-        parse (u:[]) [] params | T.null u  = Just params+        parse [] _  params = Nothing+        parse (u:[]) [] params | TS.null u  = Just params                                | otherwise = Nothing-        parse (u:us) [] _ = Nothing-        parse (u:us) ((Chunk c):cs) params | T.null c  = parse (u:us) cs params+        parse _      [] _ = Nothing+        parse (u:us) ((Chunk c):cs) params | TS.null c  = parse (u:us) cs params                                            | u == c    = parse us cs params                                            | otherwise = Nothing         parse (u:us) ((FuncChunk k f _):cs) params = do@@ -158,28 +156,28 @@                                             parse us cs ((k, val):params)         parse us ((Composed xs):cs) params = parse us (xs ++ cs) params -buildPat :: [UrlPat] -> RouteParamList -> Either UrlBuildError T.Text+buildPat :: [UrlPat] -> RouteParamList -> Either UrlBuildError TS.Text buildPat pats params = fmap addSlashes $ build [] pats     where build acc [] = Right acc           build acc ((Chunk c):[]) = build (acc <> [c]) []-          build acc ((Chunk c):cs) | T.null c = build acc cs+          build acc ((Chunk c):cs) | TS.null c = build acc cs                                    | otherwise = build (acc <> [c]) cs           build acc ((Composed xs):cs)     = build acc (xs <> cs)           build acc ((FuncChunk k _ t):cs) =                case (showParam t k params) of                       (Right  v)  -> build (acc <> [v]) cs                       (Left err)  -> Left err-          addSlashes []   = T.pack "/"-          addSlashes list = builderToText $-                              encodePathSegments (fmap T.toStrict list)+          addSlashes []   = TS.pack "/"+          addSlashes list = builderToStrictText $+                              encodePathSegments list -showParam :: ChunkType -> T.Text -> RouteParamList -> Either UrlBuildError T.Text+showParam :: ChunkType -> TS.Text -> RouteParamList -> Either UrlBuildError TS.Text showParam chunkType k l =      case (lookup k l) of         Just (MkChunk v) -> case chunkType of-            IntChunk -> toEither $ fmap spack (cast v :: Maybe Int)-            TextChunk -> toEither (cast v :: Maybe T.Text)+            IntChunk -> toEither $ fmap spacks (cast v :: Maybe Int)+            TextChunk -> toEither (cast v :: Maybe TS.Text)         Nothing -> Left NoParam     where toEither v = case v of                           Just b  -> Right b-                          Nothing -> Left $ ParamTypeMismatch k+                          Nothing -> Left $ ParamTypeMismatch $ k
src/Web/Wheb/Types.hs view
@@ -17,6 +17,7 @@ import Data.Map as M (Map) import Data.String (IsString(..)) import qualified Data.Text.Lazy as T (pack, Text, unpack)+import qualified Data.Text as TS (pack, Text, unpack) import Data.Typeable (Typeable) import Network.HTTP.Types.Header (HeaderName, ResponseHeaders) import Network.HTTP.Types.Method (StdMethod)@@ -26,7 +27,7 @@ import Network.Wai.Parse (File, Param) import Network.WebSockets (Connection) import Web.Routes (Site(..))-+import Web.Cookie (CookiesText)  -- | WhebT g s m --@@ -56,7 +57,7 @@   toResponse :: Status -> ResponseHeaders -> a -> Response  -- | A Wheb response that represents a file.-data WhebFile = WhebFile T.Text+data WhebFile = WhebFile TS.Text  data HandlerResponse = forall a . WhebContent a => HandlerResponse Status a @@ -71,15 +72,17 @@ -- | Our 'StateT' portion of 'WhebT' uses this. data InternalState s =   InternalState { reqState     :: s-                , respHeaders  :: M.Map HeaderName ByteString } +                , respHeaders  :: M.Map HeaderName ByteString+                , curCookies   :: CookiesText }                   data SettingsValue = forall a. (Typeable a) => MkVal a -data WhebError = Error500 String +data WhebError = Error500 T.Text                | Error404                | Error403+               | ErrorStatus Status T.Text                | RouteParamDoesNotExist-               | URLError T.Text UrlBuildError+               | URLError TS.Text UrlBuildError   deriving (Show)  -- | Monoid to use in InitM's WriterT@@ -121,7 +124,7 @@  type EResponse = Either WhebError Response -type CSettings = M.Map T.Text SettingsValue+type CSettings = M.Map TS.Text SettingsValue      type WhebHandler g s      = WhebT g s IO HandlerResponse type WhebHandlerT g s m   = WhebT g s m HandlerResponse@@ -135,9 +138,9 @@ type MinOpts = WhebOptions () () IO  -- * Routes-data PackedSite g s m = forall a . PackedSite T.Text (Site a (WhebHandlerT g s m))+data PackedSite g s m = forall a . PackedSite TS.Text (Site a (WhebHandlerT g s m)) -type  RouteParamList = [(T.Text, ParsedChunk)]+type  RouteParamList = [(TS.Text, ParsedChunk)] type  MethodMatch = StdMethod -> Bool  data ParsedChunk = forall a. (Typeable a, Show a) => MkChunk a@@ -145,16 +148,16 @@ instance Show ParsedChunk where   show (MkChunk a) = show a -data UrlBuildError = NoParam | ParamTypeMismatch T.Text | UrlNameNotFound+data UrlBuildError = NoParam | ParamTypeMismatch TS.Text | UrlNameNotFound      deriving (Show)   -- | A Parser should be able to extract params and regenerate URL from params. data UrlParser = UrlParser -    { parseFunc :: ([T.Text] -> Maybe RouteParamList)-    , genFunc   :: (RouteParamList -> Either UrlBuildError T.Text) }+    { parseFunc :: ([TS.Text] -> Maybe RouteParamList)+    , genFunc   :: (RouteParamList -> Either UrlBuildError TS.Text) }  data Route g s m = Route -  { routeName    :: (Maybe T.Text)+  { routeName    :: (Maybe TS.Text)   , routeMethod  :: MethodMatch   , routeParser  :: UrlParser   , routeHandler :: (WhebHandlerT g s m) }@@ -167,17 +170,17 @@ data ChunkType = IntChunk | TextChunk   deriving (Show) -data UrlPat = Chunk T.Text+data UrlPat = Chunk TS.Text             | Composed [UrlPat]             | FuncChunk -                { chunkParamName :: T.Text-                , chunkFunc :: (T.Text -> Maybe ParsedChunk)+                { chunkParamName :: TS.Text+                , chunkFunc :: (TS.Text -> Maybe ParsedChunk)                 , chunkType :: ChunkType }  instance Show UrlPat where-  show (Chunk a) = "(Chunk " ++ (T.unpack a) ++ ")"+  show (Chunk a) = "(Chunk " ++ (TS.unpack a) ++ ")"   show (Composed a) = intercalate "/" $ fmap show a-  show (FuncChunk pn _ ct) = "(FuncChunk " ++ (T.unpack pn) ++ " | " ++ (show ct) ++ ")"+  show (FuncChunk pn _ ct) = "(FuncChunk " ++ (TS.unpack pn) ++ " | " ++ (show ct) ++ ")"  instance IsString UrlPat where-  fromString = Chunk . T.pack+  fromString = Chunk . TS.pack
src/Web/Wheb/Utils.hs view
@@ -2,24 +2,29 @@  module Web.Wheb.Utils where -import Blaze.ByteString.Builder (Builder, fromLazyByteString, toLazyByteString)+import Blaze.ByteString.Builder (Builder, fromLazyByteString, toLazyByteString, toByteString) import Data.IORef (atomicModifyIORef, newIORef, readIORef) import Data.Monoid ((<>), Monoid(mappend, mempty)) import qualified Data.Text.Encoding as TS (decodeUtf8, encodeUtf8)+import qualified Data.Text as TS (pack, unpack, Text) import qualified Data.Text.Lazy as T (fromStrict, pack, Text, toStrict) import qualified Data.Text.Lazy.Encoding as T (decodeUtf8, encodeUtf8) import Network.HTTP.Types.Status (status500) import Network.Wai (Response, responseBuilder, responseFile, responseLBS, responseToStream)-import Web.Wheb.Types (HandlerResponse(..), WhebContent(..), WhebError, WhebFile(..), WhebHandlerT)+import Web.Wheb.Types (HandlerResponse(..), WhebContent(..), WhebError(..), WhebFile(..), WhebHandlerT)  lazyTextToSBS = TS.encodeUtf8 . T.toStrict sbsToLazyText = T.fromStrict . TS.decodeUtf8 builderToText = T.decodeUtf8 . toLazyByteString---- | Show and pack into 'Text'+builderToStrictText = TS.decodeUtf8 . toByteString+-- | Show and pack into Lazy 'Text' spack :: Show a => a -> T.Text spack = T.pack . show +-- | Show and pack into Strict 'Text'+spacks :: Show a => a -> TS.Text+spacks = TS.pack . show+ -- | See a 'HandlerResponse's as 'Text' showResponseBody :: HandlerResponse -> IO T.Text showResponseBody (HandlerResponse s r) = do@@ -42,10 +47,11 @@   toResponse s hds = responseBuilder s hds . fromLazyByteString . T.encodeUtf8  instance WhebContent WhebFile where-  toResponse s hds (WhebFile fp) = responseFile s hds (show fp) Nothing+  toResponse s hds (WhebFile fp) = responseFile s hds (TS.unpack fp) Nothing  ----------------------- Some defaults ----------------------- defaultErr :: Monad m => WhebError -> WhebHandlerT g s m+defaultErr (ErrorStatus s t) = return $ HandlerResponse s t defaultErr err = return $ HandlerResponse status500 $              ("<h1>Error: " <> (T.pack $ show err) <> ".</h1>") 
src/Web/Wheb/WhebT.hs view
@@ -20,6 +20,7 @@   , file   , builder   , redirect+  , throwRedirect      -- * Settings   , getSetting@@ -53,7 +54,7 @@ import Blaze.ByteString.Builder (Builder) import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.STM (atomically, readTVar, newTVarIO, writeTVar)-import Control.Monad.Error (liftM, MonadError(throwError), MonadIO, void)+import Control.Monad.Except (liftM, MonadError(throwError), MonadIO) import Control.Monad.Reader (MonadReader(ask)) import Control.Monad.State (modify, MonadState(get)) import qualified Data.ByteString.Lazy as LBS (ByteString, empty)@@ -61,7 +62,9 @@ import Data.List (find) import qualified Data.Map as M (insert, lookup) import Data.Maybe (fromMaybe)-import qualified Data.Text.Lazy as T (pack, empty, Text)+import qualified Data.Text as TS+import qualified Data.Text.Encoding as TS (decodeUtf8, encodeUtf8)+import qualified Data.Text.Lazy as T import Data.Typeable (cast, Typeable) import Network.HTTP.Types.Header (Header) import Network.HTTP.Types.Status (serviceUnavailable503, status200, status302)@@ -69,15 +72,10 @@ import Network.Wai (defaultRequest, Request(queryString, requestHeaders), responseLBS) import Network.Wai.Handler.Warp as W (runSettings, setPort) import Network.Wai.Parse (File, Param)-import System.Posix.Signals (Handler(Catch), installHandler, sigINT, sigTSTP, sigTERM)+import System.Posix.Signals (Handler(Catch), installHandler, sigINT, sigTERM) import Web.Wheb.Internal (optsToApplication, runDebugHandler) import Web.Wheb.Routes (generateUrl, getParam)-import Web.Wheb.Types (CSettings, EResponse, HandlerData(HandlerData, globalCtx, globalSettings, postData, request, routeParams), -                       HandlerResponse(HandlerResponse), InternalState(InternalState, reqState, respHeaders), -                       Route(..), RouteParamList, SettingsValue(..), -                       UrlBuildError(UrlNameNotFound), WhebError(RouteParamDoesNotExist, URLError), -                       WhebFile(WhebFile), WhebHandlerT, WhebOptions(..), WhebT(WhebT))-import Web.Wheb.Utils (lazyTextToSBS, sbsToLazyText)+import Web.Wheb.Types  -- * ReaderT and StateT Functionality @@ -114,17 +112,17 @@ -- * Settings  -- | Help prevent monomorphism errors for simple settings.-getSetting :: Monad m => T.Text -> WhebT g s m (Maybe T.Text)+getSetting :: Monad m => TS.Text -> WhebT g s m (Maybe T.Text) getSetting = getSetting'  -- | Open up underlying support for polymorphic global settings-getSetting' :: (Monad m, Typeable a) => T.Text -> WhebT g s m (Maybe a)+getSetting' :: (Monad m, Typeable a) => TS.Text -> WhebT g s m (Maybe a) getSetting' k = liftM (\cs -> (M.lookup k cs) >>= unwrap) getSettings     where unwrap :: Typeable a => SettingsValue -> Maybe a           unwrap (MkVal a) = cast a  -- | Get a setting or a default-getSetting'' :: (Monad m, Typeable a) => T.Text -> a -> WhebT g s m a+getSetting'' :: (Monad m, Typeable a) => TS.Text -> a -> WhebT g s m a getSetting'' k d = liftM (fromMaybe d) (getSetting' k)  -- | Get all settings.@@ -138,17 +136,17 @@ getRouteParams = WhebT $ liftM routeParams ask  -- | Cast a route param into its type.-getRouteParam :: (Typeable a, Monad m) => T.Text -> WhebT g s m a+getRouteParam :: (Typeable a, Monad m) => TS.Text -> WhebT g s m a getRouteParam t = do   p <- getRouteParam' t   maybe (throwError RouteParamDoesNotExist) return p  -- | Cast a route param into its type.-getRouteParam' :: (Typeable a, Monad m) => T.Text -> WhebT g s m (Maybe a)+getRouteParam' :: (Typeable a, Monad m) => TS.Text -> WhebT g s m (Maybe a) getRouteParam' t = liftM (getParam t) getRouteParams  -- | Convert 'Either' from 'getRoute'' into an error in the Monad-getRoute :: Monad m => T.Text -> RouteParamList ->  WhebT g s m T.Text+getRoute :: Monad m => TS.Text -> RouteParamList ->  WhebT g s m TS.Text getRoute name l = do         res <- getRoute' name l         case res of@@ -156,15 +154,15 @@             Left err -> throwError $ URLError name err  -- | Generate a route from a name and param list.-getRoute' :: Monad m => T.Text -> +getRoute' :: Monad m => TS.Text ->               RouteParamList -> -             WhebT g s m (Either UrlBuildError T.Text)+             WhebT g s m (Either UrlBuildError TS.Text) getRoute' n l = liftM buildRoute (getRawRoute n l)     where buildRoute (Just (Route {..})) = generateUrl routeParser l           buildRoute (Nothing)           = Left UrlNameNotFound  -- | Generate the raw route-getRawRoute :: Monad m => T.Text -> +getRawRoute :: Monad m => TS.Text ->               RouteParamList ->               WhebT g s m (Maybe (Route g s m)) getRawRoute n _ = WhebT $ liftM f ask  @@ -185,12 +183,12 @@ getRawPOST = WhebT $ liftM postData ask  -- | Get POST params as 'Text'-getPOSTParams :: MonadIO m => WhebT g s m [(T.Text, T.Text)]+getPOSTParams :: MonadIO m => WhebT g s m [(TS.Text, TS.Text)] getPOSTParams = liftM (fmap f . fst) getRawPOST-  where f (a, b) = (sbsToLazyText a, sbsToLazyText b)+  where f (a, b) = (TS.decodeUtf8 a, TS.decodeUtf8 b)  -- | Maybe get one param if it exists.-getPOSTParam :: MonadIO m => T.Text -> WhebT g s m (Maybe T.Text)+getPOSTParam :: MonadIO m => TS.Text -> WhebT g s m (Maybe TS.Text) getPOSTParam k = liftM (lookup k) getPOSTParams   -- | Get params from URL (e.g. from '/foo/?q=4')@@ -198,10 +196,10 @@ getQueryParams = getWithRequest queryString  -- | Get a request header-getRequestHeader :: Monad m => T.Text -> WhebT g s m (Maybe T.Text)+getRequestHeader :: Monad m => TS.Text -> WhebT g s m (Maybe TS.Text) getRequestHeader k = getRequest >>= f-  where hk = mk $ lazyTextToSBS k-        f = (return . (fmap sbsToLazyText) . (lookup hk) . requestHeaders)+  where hk = mk $ TS.encodeUtf8 k+        f = (return . (fmap TS.decodeUtf8) . (lookup hk) . requestHeaders)  -- * Responses @@ -212,39 +210,45 @@             is { respHeaders = M.insert hn hc respHeaders }   -- | Set a header for the response-setHeader :: Monad m => T.Text -> T.Text -> WhebT g s m ()-setHeader hn hc = setRawHeader (mk $ lazyTextToSBS hn, lazyTextToSBS hc)+setHeader :: Monad m => TS.Text -> TS.Text -> WhebT g s m ()+setHeader hn hc = setRawHeader (mk $ TS.encodeUtf8 hn, TS.encodeUtf8 hc) --- | Give filepath and content type to serve a file from disk.-file :: Monad m => T.Text -> T.Text -> WhebHandlerT g s m+-- | Give filepath and content type to serve a file via lazy text.+file :: Monad m => TS.Text -> TS.Text -> WhebHandlerT g s m file fp ct = do-    setHeader (T.pack "Content-Type") (ct) +    setHeader (TS.pack "Content-Type") (ct)      return $ HandlerResponse status200 (WhebFile fp) --- | Return simple HTML from Text+-- | Return simple HTML from lazy Text html :: Monad m => T.Text -> WhebHandlerT g s m html c = do-    setHeader (T.pack "Content-Type") (T.pack "text/html") +    setHeader (TS.pack "Content-Type") (TS.pack "text/html")      return $ HandlerResponse status200 c --- | Return simple Text +-- | Return simple lazy Text  text :: Monad m => T.Text -> WhebHandlerT g s m text c = do-    setHeader (T.pack "Content-Type") (T.pack "text/plain") +    setHeader (TS.pack "Content-Type") (TS.pack "text/plain")      return $ HandlerResponse status200 c  -- | Give content type and Blaze Builder-builder :: Monad m => T.Text -> Builder -> WhebHandlerT g s m+builder :: Monad m => TS.Text -> Builder -> WhebHandlerT g s m builder c b = do-    setHeader (T.pack "Content-Type") c +    setHeader (TS.pack "Content-Type") c      return $ HandlerResponse status200 b  -- | Redirect to a given URL-redirect :: Monad m => T.Text -> WhebHandlerT g s m+redirect :: Monad m => TS.Text -> WhebHandlerT g s m redirect c = do-    setHeader (T.pack "Location") c+    setHeader (TS.pack "Location") c     return $ HandlerResponse status302 T.empty     +-- | Thow a redirect as an error+throwRedirect :: Monad m => TS.Text -> WhebHandlerT g s m+throwRedirect c = do+    setHeader (TS.pack "Location") c+    throwError $ ErrorStatus status302 T.empty+     -- * Running a Wheb Application  -- | Running a Handler with a custom Transformer@@ -275,23 +279,25 @@      installHandler sigINT catchSig Nothing     installHandler sigTERM catchSig Nothing-    installHandler sigTSTP (Catch (atomically $ writeTVar forceTVar True >> writeTVar shutdownTVar True)) Nothing      forkIO $ runSettings rtSettings $         gracefulExit $         waiStack $         optsToApplication opts runIO -    loop+    let termSig = (Catch (atomically $ writeTVar forceTVar True >> writeTVar shutdownTVar True))+        installForceKill = installHandler sigTERM termSig Nothing >> installHandler sigINT termSig Nothing++    loop installForceKill     putStrLn $ "Waiting for connections to close..."     waitForConnections forceTVar     putStrLn $ "Shutting down server..."     sequence_ cleanupActions    where catchSig = (Catch (atomically $ writeTVar shutdownTVar True))-        loop = do+        loop terminate = do           shutDown <- atomically $ readTVar shutdownTVar-          if shutDown then return () else (threadDelay 100000) >> loop+          if shutDown then terminate else (threadDelay 100000) >> loop terminate         gracefulExit app r respond = do           isExit <- atomically $ readTVar shutdownTVar           case isExit of@@ -304,7 +310,7 @@             then return ()             else waitForConnections forceTVar         port = fromMaybe 3000 $ -          (M.lookup (T.pack "port") runTimeSettings) >>= (\(MkVal m) -> cast m)+          (M.lookup (TS.pack "port") runTimeSettings) >>= (\(MkVal m) -> cast m)         rtSettings = W.setPort port warpSettings  -- | Convenience wrapper for 'runWhebServerT' function in IO