wai-routes 0.7.1 → 0.7.2
raw patch · 5 files changed
+144/−73 lines, 5 filesdep +monad-loopsPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: monad-loops
API changes (from Hackage documentation)
- Network.Wai.Middleware.Routes: data RouteM a
- Network.Wai.Middleware.Routes.Monad: data RouteM a
- Network.Wai.Middleware.Routes.Monad: instance Applicative RouteM
- Network.Wai.Middleware.Routes.Monad: instance Functor RouteM
- Network.Wai.Middleware.Routes.Monad: instance Monad RouteM
- Network.Wai.Middleware.Routes.Monad: instance MonadIO RouteM
- Network.Wai.Middleware.Routes.Monad: instance MonadState RouteState RouteM
+ Network.Wai.Middleware.Routes: catchall :: Application -> RouteM ()
+ Network.Wai.Middleware.Routes: file :: FilePath -> HandlerM sub master ()
+ Network.Wai.Middleware.Routes: jsonBody :: FromJSON a => HandlerM master master (Either String a)
+ Network.Wai.Middleware.Routes: rawBody :: HandlerM master master ByteString
+ Network.Wai.Middleware.Routes: type RouteM = F RouterF
+ Network.Wai.Middleware.Routes: waiApp :: RouteM () -> Application
+ Network.Wai.Middleware.Routes.Handler: file :: FilePath -> HandlerM sub master ()
+ Network.Wai.Middleware.Routes.Handler: jsonBody :: FromJSON a => HandlerM master master (Either String a)
+ Network.Wai.Middleware.Routes.Handler: rawBody :: HandlerM master master ByteString
+ Network.Wai.Middleware.Routes.Monad: catchall :: Application -> RouteM ()
+ Network.Wai.Middleware.Routes.Monad: instance Functor RouterF
+ Network.Wai.Middleware.Routes.Monad: instance Functor f => Applicative (F f)
+ Network.Wai.Middleware.Routes.Monad: instance Functor f => Functor (F f)
+ Network.Wai.Middleware.Routes.Monad: instance Functor f => Monad (F f)
+ Network.Wai.Middleware.Routes.Monad: type RouteM = F RouterF
+ Network.Wai.Middleware.Routes.Monad: waiApp :: RouteM () -> Application
- Network.Wai.Middleware.Routes: toWaiApp :: RouteM () -> IO Application
+ Network.Wai.Middleware.Routes: toWaiApp :: Monad m => RouteM () -> m Application
- Network.Wai.Middleware.Routes.Monad: toWaiApp :: RouteM () -> IO Application
+ Network.Wai.Middleware.Routes.Monad: toWaiApp :: Monad m => RouteM () -> m Application
Files
- README.md +32/−22
- src/Network/Wai/Middleware/Routes.hs +7/−2
- src/Network/Wai/Middleware/Routes/Handler.hs +49/−9
- src/Network/Wai/Middleware/Routes/Monad.hs +51/−36
- wai-routes.cabal +5/−4
README.md view
@@ -1,8 +1,6 @@-Wai Routes (wai-routes-0.7.1)+[Wai-Routes](https://ajnsit.github.io/wai-routes) [](https://hackage.haskell.org/package/wai-routes) [](http://packdeps.haskellers.com/feed?needle=wai-routes) [](https://travis-ci.org/ajnsit/wai-routes) [](https://coveralls.io/github/ajnsit/wai-routes?branch=master) ==================================== -[](https://travis-ci.org/ajnsit/wai-routes)- This package provides typesafe URLs for Wai applications. Features:@@ -34,6 +32,16 @@ - Tests and code coverage +Performance+===========++When it comes to performance, Wai-routes compares quite favorably with other Haskell web development micro frameworks.++See more details here - [philopon/apiary-benchmark](https://github.com/philopon/apiary-benchmark)++++ Example Usage ============= @@ -77,29 +85,31 @@ -- Run the application main :: IO ()- main = toWaiApp myApp >>= run 8080+ main = run 8080 (waiApp myApp) Changelog ========= -* 0.1 : Intial release-* 0.2 : Updated functionality based on yesod-routes package-* 0.2.1 : Changed license to MIT-* 0.2.2 : Fixed license information in hs and cabal files-* 0.2.3 : Implemented a better showRoute function. Added blaze-builder as a dependency-* 0.2.4 : Put an upper bound on yesod-routes version as 1.2 breaks API compatibility-* 0.3.0 : yesod-routes 1.2 compatibility. Abstracted request data. Created `runNext` which skips to the next app in the wai stack-* 0.3.1 : Removed internal 'App' synonym which only muddied the types. Added common content types for convenience.-* 0.3.2 : Added HandlerM Monad which makes it easier to build Handlers-* 0.3.3 : Better exports from the Network.Wai.Middleware.Routes module-* 0.3.4 : Added 'liftResourceT' to lift a ResourceT into HandlerM-* 0.4.0 : Wai 2 compatibility. Replaced 'liftResourceT' with 'lift'+* 0.7.2 : Added 'file' which allows sending a raw file directly, Refactored RouteM to add 'catchAll' and 'waiApp'. No api breakage.+* 0.7.1 : Added 'showRouteQuery', renamed 'text' to 'plain', 'html' now accepts Text instead of ByteString+* 0.7.0 : Subsites support added+* 0.6.2 : Added 'maybeRoute' and 'routeAttrSet', to get information about the currently executing route+* 0.6.1 : Fixed cabal and travis files+* 0.6.0 : Removed dependency on yesod-routes. Updated code to compile with wai-3 and ghc-7.8, ghc-7.10+* 0.5.1 : Bumped dependency upper bounds to allow text 1.* * 0.4.1 : showRoute now returns "/" instead of ""+* 0.4.0 : Wai 2 compatibility. Replaced 'liftResourceT' with 'lift'+* 0.3.4 : Added 'liftResourceT' to lift a ResourceT into HandlerM+* 0.3.3 : Better exports from the Network.Wai.Middleware.Routes module+* 0.3.2 : Added HandlerM Monad which makes it easier to build Handlers+* 0.3.1 : Removed internal 'App' synonym which only muddied the types. Added common content types for convenience.+* 0.3.0 : yesod-routes 1.2 compatibility. Abstracted request data. Created `runNext` which skips to the next app in the wai stack * 0.5.0 : Added raw,text,html,json helpers. Update to wai-2.1.-* 0.5.1 : Bumped dependency upper bounds to allow text 1.*-* 0.6.0 : Removed dependency on yesod-routes. Updated code to compile with wai-3 and ghc-7.8, ghc-7.10-* 0.6.1 : Fixed cabal and travis files-* 0.6.2 : Added 'maybeRoute' and 'routeAttrSet', to get information about the currently executing route-* 0.7.0 : Subsites support added-* 0.7.1 : Added 'showRouteQuery', renamed 'text' to 'plain', 'html' now accepts Text instead of ByteString+* 0.2.4 : Put an upper bound on yesod-routes version as 1.2 breaks API compatibility+* 0.2.3 : Implemented a better showRoute function. Added blaze-builder as a dependency+* 0.2.2 : Fixed license information in hs and cabal files+* 0.2.1 : Changed license to MIT+* 0.2 : Updated functionality based on yesod-routes package+* 0.1 : Intial release+
src/Network/Wai/Middleware/Routes.hs view
@@ -48,10 +48,12 @@ -- * Route Monad makes it easy to compose routes together , RouteM- , defaultAction -- | Set the default action for a app+ , catchall -- | Catch all routes with the supplied application+ , defaultAction -- | A synonym for `catchall`, kept for backwards compatibility , middleware -- | Add another middleware to the app , route -- | Add another routed middleware to the app- , toWaiApp -- | Convert a RouteM to a wai Application+ , waiApp -- | Convert a RouteM to a wai Application+ , toWaiApp -- | Similar to waiApp, but result is wrapped in a monad. Kept for backwards compatibility -- * HandlerM Monad makes it easy to build a handler , HandlerM()@@ -64,11 +66,14 @@ , master -- | Access the master datatype , header -- | Add a header to the response , status -- | Set the response status+ , file -- | Send a file as response , raw -- | Set the raw response body , json -- | Set the json response body , plain -- | Set the plain text response body , html -- | Set the html response body , next -- | Run the next application in the stack+ , rawBody -- | Consume and return the request body as a lazy bytestring+ , jsonBody -- | Consume and return the request body as JSON ) where
src/Network/Wai/Middleware/Routes/Handler.hs view
@@ -21,31 +21,36 @@ , master -- | Access the master datatype , header -- | Add a header to the response , status -- | Set the response status+ , file -- | Send a file as response , raw -- | Set the raw response body , json -- | Set the json response body , plain -- | Set the plain text response body , html -- | Set the html response body , next -- | Run the next application in the stack+ , rawBody -- | Consume and return the request body as a lazy bytestring+ , jsonBody -- | Consume and return the request body as JSON ) where -import Network.Wai (Request, Response, responseBuilder, pathInfo, queryString)-import Control.Monad (liftM)-import Control.Monad.State (StateT, get, put, modify, runStateT, MonadState, MonadIO, lift, MonadTrans)--import Control.Applicative (Applicative)-+import Network.Wai (Request, Response, responseFile, responseBuilder, pathInfo, queryString, requestBody) import Network.Wai.Middleware.Routes.Routes (Env(..), RequestData, HandlerS, waiReq, currentRoute, runNext, ResponseHandler) import Network.Wai.Middleware.Routes.Class (Route, RouteAttrs(..)) import Network.Wai.Middleware.Routes.ContentTypes (contentType, typeHtml, typeJson, typePlain) +import Control.Monad (liftM)+import Control.Monad.Loops (unfoldWhileM)+import Control.Monad.State (StateT, get, put, modify, runStateT, MonadState, MonadIO, lift, liftIO, MonadTrans)++import Control.Applicative (Applicative, (<$>))+ import Data.Maybe (maybe) import Data.ByteString (ByteString)+import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import Network.HTTP.Types.Header (HeaderName()) import Network.HTTP.Types.Status (Status(), status200) -import Data.Aeson (ToJSON)+import Data.Aeson (ToJSON, FromJSON, eitherDecode) import qualified Data.Aeson as A import Data.Set (Set)@@ -70,10 +75,14 @@ data HandlerState sub master = HandlerState { getMaster :: master , getRequestData :: RequestData sub+ -- TODO: Experimental+ -- Streaming request body, consumed, and stored as a ByteString+ , reqBody :: Maybe BL.ByteString , respHeaders :: [(HeaderName, ByteString)] , respStatus :: Status , respBody :: BL.ByteString , respResp :: Maybe ResponseHandler+ , respFile :: Maybe FilePath , getSub :: sub , toMasterRoute :: Route sub -> Route master }@@ -81,14 +90,38 @@ -- | "Run" HandlerM, resulting in a Handler runHandlerM :: HandlerM sub master () -> HandlerS sub master runHandlerM h env req hh = do- (_, state) <- runStateT (extractH h) (HandlerState (envMaster env) req [] status200 "" Nothing (envSub env) (envToMaster env))+ (_, state) <- runStateT (extractH h) (HandlerState (envMaster env) req Nothing [] status200 "" Nothing Nothing (envSub env) (envToMaster env)) case respResp state of Nothing -> hh $ toResp state Just resp -> resp hh toResp :: HandlerState sub master -> Response-toResp hs = responseBuilder (respStatus hs) (respHeaders hs) (fromLazyByteString $ respBody hs)+toResp hs = case respFile hs of+ Nothing -> responseBuilder (respStatus hs) (respHeaders hs) (fromLazyByteString $ respBody hs)+ Just f -> responseFile (respStatus hs) (respHeaders hs) f Nothing +-- | Get the request body as a lazy bytestring+-- Get the body as a Lazy bytestring+-- EXPERIMENTAL. Consumes the entire body+-- TODO: Implement streaming. Prevent clash with direct use of `Network.Wai.requestBody`+rawBody :: HandlerM master master BL.ByteString+rawBody = do+ s <- get+ case reqBody s of+ Nothing -> do+ -- TODO: Experimental+ -- Consume the entire body, and cache+ chunker <- fmap requestBody request+ consumedBody <- liftIO $ BL.fromChunks <$> unfoldWhileM (not . B.null) chunker+ put s {reqBody = Just consumedBody}+ return consumedBody+ Just consumedBody -> return consumedBody++-- Parse the body as a JSON object+-- TODO: Add this to wai-routes+jsonBody :: FromJSON a => HandlerM master master (Either String a)+jsonBody = liftM eitherDecode rawBody+ -- | Get the master master :: HandlerM sub master master master = liftM getMaster get@@ -135,6 +168,13 @@ where setStatus :: Status -> HandlerState sub master -> HandlerState sub master setStatus s st = st{respStatus=s}++-- | Set the response body to a file+file :: FilePath -> HandlerM sub master ()+file s = modify $ setBody s+ where+ setBody :: FilePath -> HandlerState sub master -> HandlerState sub master+ setBody s st = st{respFile=Just s} -- | Set the response body -- TODO: Add functions to append to body, and also to flush body contents
src/Network/Wai/Middleware/Routes/Monad.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, TypeFamilies #-}+{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, TypeFamilies, RankNTypes, DeriveFunctor #-} {- | Module : Network.Wai.Middleware.Routes.Monad@@ -15,10 +15,12 @@ ( -- * Route Monad RouteM -- * Compose Routes- , defaultAction , middleware , route+ , catchall+ , defaultAction -- * Convert to Wai Application+ , waiApp , toWaiApp ) where@@ -27,54 +29,67 @@ import Network.Wai.Middleware.Routes.Routes import Network.HTTP.Types -import Control.Monad.State+import Control.Applicative (Applicative, (<*>), pure) -import Control.Applicative (Applicative)+-- Free Monad+newtype F f a = F { runF :: forall r. (a -> r) -> (f r -> r) -> r }+instance Functor f => Functor (F f) where+ fmap f (F g) = F (\kp -> g (kp . f))+instance Functor f => Applicative (F f) where+ pure a = F (\kp _ -> kp a)+ F f <*> F g = F (\kp kf -> f (\a -> g (kp . a) kf) kf)+instance Functor f => Monad (F f) where+ return a = F (\kp _ -> kp a)+ F m >>= f = F (\kp kf -> m (\a -> runF (f a) kp kf) kf) -data RouteState = RouteState- { middlewares :: [Middleware]- , defaultApp :: Application- }+-- | Add a layer+wrap :: Functor f => f (F f a) -> F f a+wrap f = F (\kp kf -> kf (fmap (\ (F m) -> m kp kf) f)) --- The final "catchall" application, simply returns a 404 response--- Ideally you should put your own default application-defaultApplication :: Application-defaultApplication _req h = h $ responseLBS status404 [("Content-Type", "text/plain")] "Error : 404 - Document not found"+-- | A version of lift that can be used with just a Functor for f.+liftF :: Functor f => f a -> F f a+liftF = wrap . fmap return+-- End free monad things +-- A Router functor can either add a middleware, or resolve to an app itself.+data RouterF x = M Middleware x | D Application deriving Functor -addMiddleware :: Middleware -> RouteState -> RouteState-addMiddleware m s@(RouteState {middlewares=ms}) = s {middlewares=m:ms}+-- Router type+type RouteM = F RouterF -setDefaultApp :: Application -> RouteState -> RouteState-setDefaultApp a s = s {defaultApp=a}+-- | Catch all routes and process them with the supplied application.+-- Note: As expected from the name, no request proceeds past a catchall.+catchall :: Application -> RouteM ()+catchall a = liftF $ D a --- | The Route Monad-newtype RouteM a = S { runS :: StateT RouteState IO a }- deriving (Applicative, Monad, MonadIO, Functor, MonadState RouteState)+-- | Synonym of `catchall`. Kept for backwards compatibility+defaultAction :: Application -> RouteM ()+defaultAction = catchall --- | Add a middleware to the application.--- Middleware is nested so the one declared earlier is outer.+-- | Add a middleware to the application+-- Middleware are ordered so the one declared earlier is wraps the remaining application. middleware :: Middleware -> RouteM ()-middleware = modify . addMiddleware+middleware m = liftF $ M m () -- | Add a route to the application. -- Routes are ordered so the one declared earlier is matched first. route :: (Routable master master) => master -> RouteM () route = middleware . routeDispatch --- | Set the default action of the Application.--- You should only call this once in an application.--- Subsequent invocations override the previous settings.-defaultAction :: Application -> RouteM ()-defaultAction = modify . setDefaultApp---- Empty state-initRouteState :: RouteState-initRouteState = RouteState [] defaultApplication+-- The final "catchall" application, simply returns a 404 response+-- Ideally you should put your own default application+defaultApplication :: Application+defaultApplication _req h = h $ responseLBS status404 [("Content-Type", "text/plain")] "Error : 404 - Document not found" --- | Convert a RouteM Monadic value into a wai application.-toWaiApp :: RouteM () -> IO Application-toWaiApp m = do- (_,s) <- runStateT (runS m) initRouteState- return $ foldl (\a b -> b a) (defaultApp s) (middlewares s)+-- | Convert a RouteM monad into a wai application.+-- Note: We ignore the return type of the monad+waiApp :: RouteM () -> Application+waiApp (F r) = r (const defaultApplication) f+ where+ f (M m r) = m r+ f (D a) = a +-- | Similar to waiApp but returns the app in an arbitrary monad+-- Kept for backwards compatibility+toWaiApp :: Monad m => RouteM () -> m Application+toWaiApp = return . waiApp
wai-routes.cabal view
@@ -1,12 +1,12 @@ name : wai-routes-version : 0.7.1+version : 0.7.2 cabal-version : >=1.18 build-type : Simple license : MIT license-file : LICENSE maintainer : ajnsit@gmail.com stability : Experimental-homepage : https://github.com/ajnsit/wai-routes+homepage : https://ajnsit.github.io/wai-routes/ synopsis : Typesafe URLs for Wai applications. description : Provides easy to use typesafe URLs for Wai Applications. See README for more information. Also see examples/ directory for usage examples. category : Network@@ -20,8 +20,8 @@ source-repository this type : git- location : http://github.com/ajnsit/wai-routes/tree/v0.7.1- tag : v0.7.1+ location : http://github.com/ajnsit/wai-routes/tree/v0.7.2+ tag : v0.7.2 library build-depends: base >= 4.7 && < 4.9@@ -36,6 +36,7 @@ , bytestring >= 0.10 && < 0.11 , http-types >= 0.8 && < 0.9 , blaze-builder >= 0.4 && < 0.5+ , monad-loops >= 0.4 && < 0.5 exposed-modules: Network.Wai.Middleware.Routes Network.Wai.Middleware.Routes.Parse Network.Wai.Middleware.Routes.Overlap