diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,3 +26,7 @@
 v 0.2.1
 
 	- `stream` function can now optionally flush. `stream'` will always flush. This may improve list streaming functionality.
+
+v 0.4.0
+
+	- Breaking change; refactor internals & simplify `Web.App.RouteT`
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,13 +1,16 @@
-# webapp - WAI web framework
-
-Webapp is a web framework that is designed to provide everything needed to define & deploy a web app. For how to use, see Haddock documentation. For an example, see `example.hs`.
-
-Webapp provides a function called `webappMain` (as well as a series of other similarly named functions) that start the built-in webserver. Your web application's `main` function should include a call to one of them at the end.
+# Web.App - WAI web framework
 
-# Using a webapp web app
+[![Build Status](https://travis-ci.org/natesymer/webapp.svg?branch=master)](https://travis-ci.org/natesymer/webapp)
 
-Once you've written your web app, deploying is up to you. Webapp will probably work with services like Heroku or complicated load balancers, but it designed to be a standalone server (i.e. no need to run behind something like nginx).
+Web.App is a general, minimalist Haskell web framework. See Haddock documentation and `example.hs`.
 
-Webapp works by first binding to an IPv4 TCP port, immediately after which resigning privileges†. Then it builds a `WAI` app from your `WebAppT` app, applies middleware, and runs `Warp`.
+## Usage notes
 
-† The effective GID & UID are set to match the real GID & UID.
+1. Web.App can either be ran standalone (i.e. look ma, no nginx!) or behind other server programs.
+  - To bind to privileged ports, your program must be executable as root. Privileges are resigned after the port is bound.
+  - Web.App uses Warp under the hood to serve a WAI app based on provided routes.
+  - SSL & HTTP2 are supported.
+2. Web.App provides a function called `webappMain` (as well as a series of other similarly named functions) that start your app.
+  - Your program's `main` function should finish with a call to one of them.
+3. Web.App also provides command line options for controlling the HTTP server.
+  - It also provides 
diff --git a/Web/App.hs b/Web/App.hs
--- a/Web/App.hs
+++ b/Web/App.hs
@@ -13,20 +13,16 @@
 (
   module Web.App.HTTP,
   module Web.App.Main,
-  module Web.App.Middleware,
   module Web.App.Path,
   module Web.App.RouteT,
   module Web.App.State,
-  module Web.App.Stream,
-  module Web.App.WebApp
+  module Web.App.Stream
 )
 where
 
 import Web.App.HTTP
 import Web.App.Main
-import Web.App.Middleware
 import Web.App.Path
 import Web.App.RouteT
 import Web.App.State
 import Web.App.Stream
-import Web.App.WebApp
diff --git a/Web/App/HTTP.hs b/Web/App/HTTP.hs
--- a/Web/App/HTTP.hs
+++ b/Web/App/HTTP.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE OverloadedStrings, RankNTypes, ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings, RankNTypes #-}
 
 {-|
 Module      : Web.App.HTTP
@@ -7,65 +7,67 @@
 Maintainer  : nate@symer.io
 Stability   : experimental
 Portability : POSIX
-
-Start an HTTP server.
 -}
 
 module Web.App.HTTP
 (
-  -- * Running Warp
-  runSecure,
-  runInsecure,
-  -- * Configuring Warp
-  mkWarpSettings,
-  -- * Bind to a TCP port for HTTP
-  bindTCP
+  runServer
 )
 where
 
-import Control.Monad
-
 import Network.Wai (Application)
-import Network.Wai.Handler.Warp (defaultSettings,runSettingsSocket,Port)
-import Network.Wai.Handler.WarpTLS (tlsSettings,runTLSSocket,TLSSettings(..),OnInsecure(..))
-import Network.Wai.Handler.Warp.Internal
-import Network.Socket (sClose,withSocketsDo,Socket)
-import Data.Streaming.Network (bindPortTCP)
-import Control.Exception (bracket)
+import Network.Wai.Handler.Warp (defaultSettings, runSettingsSocket, setPort)
+import Network.Wai.Handler.WarpTLS (tlsSettings, runTLSSocket, TLSSettings(..), OnInsecure(..))
+import Network.Socket (close, listen, bind, socket, Socket, SocketOption(..), AddrInfoFlag(..), SocketType(..), Family(..), isListening, setSocketOption, maxListenQueue, withSocketsDo, getAddrInfo, defaultHints, addrFamily, addrFlags, addrAddress, addrProtocol, addrSocketType)
+import Control.Exception (bracket, bracketOnError)
 
 import System.Exit
 import System.Posix
 
-bindTCP :: Port -> (Socket -> IO ()) -> IO ()
-bindTCP port f = withSocketsDo $ bracket (bindPortTCP port "*4") sClose f
+import Data.List (sortOn)
+import Data.Maybe (fromJust)
+import Data.Bool (bool)
 
--- |Build a Warp Settings struct
-mkWarpSettings :: IO () -- ^ function to be called on a SIGTERM or SIGINT
-               -> Int -- ^ port
-               -> Settings
-mkWarpSettings teardown port = defaultSettings {
-    settingsPort = port,
-    settingsInstallShutdownHandler = \killSockets -> void $ do
-      installHandler sigTERM (handler $ killSockets >> teardown) Nothing
-      installHandler sigINT (handler $ killSockets >> teardown) Nothing
-  }
-  where handler = Catch . (>> exitImmediately ExitSuccess)
-    
--- |Serve a WAI app using Warp over TLS.
-runSecure :: FilePath -- ^ 'FilePath' to an SSL certificate
-          -> FilePath -- ^ 'FilePath' to an SSL private key
-          -> Socket -- ^ Socket to use
-          -> Settings -- ^ Warp settings structure
-          -> Application -- ^ WAI application
+-- | Serve your application.
+runServer :: Maybe (FilePath, FilePath) -- ^ Certificate pair to use (cert, key)
+          -> Int -- ^ Port to bind to
+          -> IO () -- ^ IO to be performed immediately after binding.
+          -> IO () -- ^ IO to be performed on shutdown.
+          -> Application -- ^ WAI 'Application'.
           -> IO ()
-runSecure cert key sock set app = do
-  settingsInstallShutdownHandler set (sClose sock) -- ignored by Warp's @runTLSSocket@
-  runTLSSocket tset set sock app
-  where tset = (tlsSettings cert key) { onInsecure = AllowInsecure }
+runServer cp port pre teardown app = withSocketsDo $ bracket (bindTCP port) close logic
+  where
+    logic sock = pre >> do
+      installHandler sigTERM handler Nothing
+      installHandler sigINT handler Nothing
+      run cp sock
+      where handler = Catch $ close sock >> teardown >> exitImmediately ExitSuccess
+    run (Just (c, k)) sock = runTLSSocket tset set sock app
+      where tset = (tlsSettings c k) { onInsecure = AllowInsecure }
+    run _ sock = runSettingsSocket set sock app
+    set = setPort port defaultSettings
 
--- |Serve a WAI app using Warp over unencrypted HTTP.
-runInsecure :: Socket -- ^ Socket to use
-            -> Settings -- ^ Warp settings structure
-            -> Application -- ^ WAI application
-            -> IO ()
-runInsecure = flip runSettingsSocket
+-- | Find a Socket capable of transmitting/receiving TCP data.
+-- Has preference for IPv4 hosts over IPv6 hosts.
+bindTCP :: Int -> IO Socket
+bindTCP p = tryAddrs . sortOn sortf =<< getAddrInfo (Just hints) Nothing (Just $ show p)
+  where hints = defaultHints {
+                  addrFlags = [AI_PASSIVE, AI_NUMERICSERV, AI_NUMERICHOST],
+                  addrSocketType = Stream
+                } -- TODO: add port to these hints somehow
+        sortf = (==) AF_INET6 . addrFamily -- sortOn odd [True, False, True] == [False, True, True]
+        tryAddrs = fmap fromJust . findMLazy isListening . map theBody -- uses laziness
+        theBody addr = bracketOnError aquire close action
+          where aquire = socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
+                action sock = do
+                  setSocketOption sock NoDelay 1
+                  setSocketOption sock ReuseAddr 1
+                  bind sock $ addrAddress addr
+                  listen sock $ max 2048 maxListenQueue
+                  return sock
+      
+-- | Performs monadic computations until it finds a result
+-- for which p returns true. It returns this value.           
+findMLazy :: (Monad m) => (a -> m Bool) -> [m a] -> m (Maybe a)
+findMLazy _ [] = return Nothing
+findMLazy p (x:xs) = x >>= \x' -> p x' >>= bool (findMLazy p xs) (return $ Just x') 
diff --git a/Web/App/Main.hs b/Web/App/Main.hs
--- a/Web/App/Main.hs
+++ b/Web/App/Main.hs
@@ -14,27 +14,35 @@
 
 import Web.App
 
+routes :: (WebAppState s, MonadIO m) => [Route s m]
+routes = [] -- routes go here
+
 main :: IO ()
-main = webappMain' app "My Application!"
+main = webappMain' routes []
 @
 
 -}
 
+{-# LANGUAGE TupleSections #-}
+
 module Web.App.Main
 (
   webappMain,
   webappMain',
   webappMainIO,
-  webappMainIO'
+  webappMainIO',
+  webappMainSimple,
 )
 where
 
-import Web.App.WebApp
-import Web.App.RouteT (RouteResult)
+import Web.App.RouteT
 import Web.App.State
 import Web.App.HTTP
 
+import Network.Wai (Middleware, Response)
+
 import Data.Maybe
+import Data.Monoid
 import Control.Monad
 import Control.Monad.IO.Class
 
@@ -58,15 +66,20 @@
   _optionsErrorPath :: Maybe FilePath
 }
 
+webappMainSimple :: (WebAppState s) => [Route s IO] -> IO ()
+webappMainSimple = flip webappMainIO' []
+
 -- |Like 'webappMainIO' without the CLI extension arguments.
 webappMainIO' :: (WebAppState s)
-              => WebApp s IO -- ^ app to start
+              => [Route s IO] -- ^ routes
+              -> [Middleware] -- ^ middlewares
               -> IO ()
-webappMainIO' a = webappMainIO a Nothing (const $ return ())
+webappMainIO' a m = webappMainIO a m Nothing (const $ return ())
   
 -- |Run a webapp based on IO.
 webappMainIO :: (WebAppState s)
-             => WebApp s IO -- ^ app to start
+             => [Route s IO] -- ^ routes
+             -> [Middleware] -- ^ middlewares
              -> Maybe (Parser a) -- ^ extra CLI parser (available under @util@ subcommand)
              -> (a -> IO ()) -- ^ action to apply to parse result of 'utilParser'
              -> IO ()
@@ -74,20 +87,22 @@
 
 -- |Like 'webappMain' without the CLI extension arguments.
 webappMain' :: (WebAppState s, MonadIO m)
-            => (m RouteResult -> IO RouteResult) -- ^ action to eval a monadic computation in @m@ in @IO@
-            -> WebApp s m -- ^ app to start
+            => (m (Maybe Response) -> IO (Maybe Response)) -- ^ action to eval a monadic computation in @m@ in @IO@
+            -> [Route s m] -- ^ routes
+            -> [Middleware] -- ^ middlewares
             -> IO ()
-webappMain' f a = webappMain f a Nothing (const $ return ())
+webappMain' f a m = webappMain f a m Nothing (const $ return ())
 
 -- | Read commandline arguments and start webapp accordingly. When passing an
 -- additional CLI parser, it is made available under the @util@ subcommand.
 webappMain :: (WebAppState s, MonadIO m)
-           => (m RouteResult -> IO RouteResult) -- ^ action to eval a monadic computation in @m@ in @IO@
-           -> WebApp s m -- ^ app to start
+           => (m (Maybe Response) -> IO (Maybe Response)) -- ^ action to eval a monadic computation in @m@ in @IO@
+           -> [Route s m] -- ^ routes
+           -> [Middleware] -- ^ middlewares
            -> Maybe (Parser a) -- ^ extra CLI parser, parsed after the built-in parser
            -> (a -> IO ()) -- ^ action to apply the result of 'extraParser'
            -> IO ()
-webappMain runToIO app extraParser extraf = parseArgs extraParser >>= either extraf f
+webappMain runToIO app mws extraParser extraf = parseArgs extraParser >>= either extraf f
   where
     f (Options Nothing p c k o e) = start p c k o e
     f (Options (Just pidFile) p c k o e) = do
@@ -102,22 +117,19 @@
           start p c k Nothing Nothing
         exitImmediately ExitSuccess
       exitImmediately ExitSuccess
-    start port cert key out err = do
-      bindTCP port $ \sock -> do
-        -- drop privileges after binding to a port
-        getRealGroupID >>= setEffectiveGroupID
-        getRealUserID >>= setEffectiveUserID
-        -- redirect I/O
-        maybe (return ()) (redirectHandle stdout) out
-        maybe (return ()) (redirectHandle stderr) err
-        -- serve webapp
-        (wai,teardown) <- toApplication runToIO app
-        serveFunc cert key sock (mkWarpSettings teardown port) wai
-      where serveFunc c k = fromMaybe runInsecure $ runSecure <$> c <*> k
-    redirectHandle hdl path = do
-      exists <- fileExist path
-      when (not exists) $ writeFile path ""
-      h <- openFile path WriteMode
+    start port cert key out err = runServer ((,) <$> cert <*> key) port pre teardown wai
+      where (wai, teardown) = toApplication runToIO app mws
+            pre = do
+              -- drop privileges after binding to a port
+              getRealGroupID >>= setEffectiveGroupID
+              getRealUserID >>= setEffectiveUserID
+              -- redirect I/O
+              maybe (return ()) (redirectHandle stdout) out
+              maybe (return ()) (redirectHandle stderr) err
+    redirectHandle hdl pth = do
+      exists <- fileExist pth
+      when (not exists) $ writeFile pth ""
+      h <- openFile pth WriteMode
       hDuplicateTo h hdl
       hClose h
       hSetBuffering hdl NoBuffering
@@ -127,7 +139,7 @@
   defaultPort <- ((=<<) readMaybe) <$> lookupEnv "PORT"
   customExecParser pprefs $ info (helper <*> parser defaultPort) fullDesc
   where
-    pprefs = ParserPrefs "" False False True 80
+    pprefs = ParserPrefs "" False False True True 80
     parser port = (Right <$> parseStart port) <|> (maybe empty (fmap Left) extra)--  <|> (Right <$> showHelp)
     parseStart port = Options
       <$> (optional $ strOption $ long "daemonize"  <> short 'd' <> metavar "FILEPATH" <> help "Daemonize server and write its pid to FILEPATH.")
diff --git a/Web/App/Middleware.hs b/Web/App/Middleware.hs
deleted file mode 100644
--- a/Web/App/Middleware.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-module Web.App.Middleware
-(
-  module Web.App.Middleware.Gzip
-)
-where
-
-import Web.App.Middleware.Gzip
diff --git a/Web/App/Middleware/Gzip.hs b/Web/App/Middleware/Gzip.hs
deleted file mode 100644
--- a/Web/App/Middleware/Gzip.hs
+++ /dev/null
@@ -1,53 +0,0 @@
-{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
-{-|
-Module      : Web.App.Middleware.Gzip
-Copyright   : (c) Nathaniel Symer, 2015
-License     : MIT
-Maintainer  : nate@symer.io
-Stability   : experimental
-Portability : Cross-Platform
-
-WAI middleware to GZIP HTTP responses.
--}
-
-module Web.App.Middleware.Gzip
-(
-  gzip
-)
-where
-
-import Network.Wai
-import Network.Wai.Internal
-import Data.Maybe (fromMaybe)
-import qualified Codec.Compression.GZip as GZIP (compress)
-import qualified Data.ByteString.Char8 as B (readInt,isInfixOf)
-import Blaze.ByteString.Builder (toLazyByteString)
-import Blaze.ByteString.Builder.ByteString (fromLazyByteString)
-
--- | Creates a 'Middleware' that GZIPs HTTP responses
-gzip :: Int -- ^ Minimum response length that's GZIP'd
-     -> Middleware
-gzip minLen app env sendResponse = app env f
-  where
-    f res@ResponseRaw{} = sendResponse res
-    f res
-      | isCompressable = sendCompressed res sendResponse
-      | otherwise = sendResponse res
-      where
-        isCompressable = isAccepted && not isIE6 && isBigEnough && not isEncoded
-        isAccepted = headerContains "gzip" "Accept-Encoding" $ requestHeaders env
-        isIE6 = headerContains "MSIE 6" "User-Agent" $ requestHeaders env
-        isEncoded = headerContains "gzip" "Content-Encoding" $ responseHeaders res
-        isBigEnough = maybe True ((<=) minLen) contentLength
-        contentLength = getResHeader "Content-Length" >>= fmap fst . B.readInt
-        getResHeader k = lookup k $ responseHeaders res
-        headerContains s k hdrs = fromMaybe False $ B.isInfixOf s <$> lookup k hdrs
-
-sendCompressed :: Response -> (Response -> IO a) -> IO a
-sendCompressed res f = maybe (f res) (const send) $ lookup "Content-Type" hs
-  where
-    (s,hs,wb) = responseToStream res
-    invariants = [("Vary","Accept-Encoding"),("Content-Encoding","gzip")]
-    hs' = invariants ++ (filter ((/=) "Content-Length" . fst) hs)
-    send = wb $ \b -> f $ responseStream s hs' (b . (. compress))
-    compress = fromLazyByteString . GZIP.compress . toLazyByteString
diff --git a/Web/App/Parameter.hs b/Web/App/Parameter.hs
--- a/Web/App/Parameter.hs
+++ b/Web/App/Parameter.hs
@@ -41,6 +41,7 @@
       f "n" = Just False
       f "on" = Just True -- HTML checkboxes
       f "off" = Just False -- HTML checkboxes
+      f "null" = Just False -- treat nulls like False
       f _ = Nothing
 
 instance Parameter T.Text where
@@ -70,7 +71,7 @@
   maybeRead (B.uncons -> Just ('.',v)) = (*) e <$> v''
     where v'  = B.takeWhile isDigit v
           v'' = fromIntegral . fst <$> B.readInt v'
-          e   = 10**(negate $ fromIntegral $ B.length v')
+          e   = 10 ** (negate $ fromIntegral $ B.length v')
   maybeRead x = (+) (fromMaybe 0 $ maybeRead xs) <$> x''
     where (x',xs) = B.span isDigit x
           x''     = fromInteger . fst <$> B.readInteger x'
diff --git a/Web/App/Path.hs b/Web/App/Path.hs
--- a/Web/App/Path.hs
+++ b/Web/App/Path.hs
@@ -6,7 +6,7 @@
 Stability   : experimental
 Portability : POSIX
 
-A URI pathInfo wrapper.
+Path matching/parsing functionality
 -}
 
 {-# LANGUAGE OverloadedStrings #-}
@@ -15,19 +15,18 @@
 (
   -- * Types
   Path,
-  PathInfo,
   -- * Path Constructors
   literal,
   captured,
   regex,
   -- * Path Operations
-  pathMatches,
-  -- * Text-Based Path Operations
   isRoot,
-  splitPath,
-  mkPathInfo,
-  joinPath,
-  pathCaptures
+  pathMatches,
+  pathCaptures,
+  -- * Path Components Operations
+  splitPathComps,
+  joinPathComps,
+  isValidPathComps
 )
 where
 
@@ -38,92 +37,87 @@
 import Text.Regex.Posix
 import Data.List
 import Data.String
+import Data.Char (isAlphaNum)
+import Data.Maybe (isJust)
+import Data.Bool (bool)
+import Control.Monad (join)
 
--- |Represents a PathInfo (e.g. from WAI)
-type PathInfo = [Text]
+{- TYPES -}
 
+-- TODO: CapturedPath needs to store indeces of captures as an optimization
+
 -- |Describes a matchable path.
-data Path = LiteralPath PathInfo -- ^ Match a path as-is
-          | CapturedPath PathInfo -- ^ Match a path with wildcards
-          | RegexPath Regex -- ^ Match a path with a regex
-          
+data Path = LiteralPath [Text] -- ^ as-is
+          | CapturedPath [Text] -- ^ path with named wildcards starting with a colon
+          | RegexPath Regex -- ^ path with a regex
+
 instance IsString Path where
-  fromString = f . mkPathInfo . T.pack
-    where f pinfo = case find (T.isPrefixOf ":") pinfo of
-            Just _ -> CapturedPath pinfo
-            Nothing -> LiteralPath pinfo
-      
+  fromString = join getCtor . splitPathComps . T.pack
+    where
+      hasCapture = isJust . find (T.isPrefixOf ":")
+      getCtor = bool LiteralPath CapturedPath . hasCapture
+
+{- PATH CONSTRUCTORS -}
+
 -- |Construct a literal 'Path'.
 literal :: Text -> Path
-literal = LiteralPath . mkPathInfo
+literal = LiteralPath . splitPathComps
 
 -- |Construct a captured 'Path'.
 captured :: Text -> Path
-captured = CapturedPath . mkPathInfo
+captured = CapturedPath . splitPathComps
 
 -- |Construct a regex 'Path'.
 regex :: Text -> Path
 regex = RegexPath . makeRegex . T.encodeUtf8
 
--- |Returns @True@ if the given 'Path' matches the given 'PathInfo'
-pathMatches :: Path -> PathInfo -> Bool
-pathMatches (RegexPath ex) pin = matchTest ex $ T.encodeUtf8 $ joinPath pin
-pathMatches (LiteralPath pin) pin2 = pin == pin2
-pathMatches (CapturedPath pin) pin2 = f pin pin2
-  where f [] [] = True
-        f _  [] = False
-        f [] _  = False
-        f (c:cs) (p:ps)
-          | T.head c == ':' = f cs ps
-          | p == c = f cs ps
-          | otherwise = False
+{- PATH OPERATIONS -}
 
--- |Returns true if given path is the root.
+-- | Returns @True@ if given path is the root.
 isRoot :: Path -> Bool
-isRoot (RegexPath ex) = matchTest ex ("/" :: String)
-isRoot (LiteralPath pin) = null pin
-isRoot (CapturedPath pin) = null pin
-
-{- General path operations -}
-
--- |Splits path into (path,queryString).
-splitPath :: Text -- ^ path
-          -> (Text,Text)
-splitPath pth = (p,T.drop 1 q)
-  where (p,q) = T.break (== '?') pth
-
--- |Split @path@ into a pathInfo list.
-mkPathInfo :: Text -- ^ path
-           -> PathInfo
-mkPathInfo = filter (not . T.null) . T.splitOn "/" . fst . splitPath
+isRoot = flip pathMatches (splitPathComps "/")
 
--- |Join pathInfo into a 'Text'ual path.
-joinPath :: PathInfo -- ^ pathInfo
-         -> Text
-joinPath = mconcat . (:) "/" . intersperse "/"
+-- | Returns @True@ if the given 'Path' matches the given path components.
+pathMatches :: Path -- ^ path
+            -> [Text] -- ^ pathComps
+            -> Bool
+pathMatches (RegexPath ex) pin = matchTest ex $ T.encodeUtf8 $ joinPathComps $ delete "/" pin
+pathMatches (LiteralPath lpin) pin = lpin == delete "/" pin
+pathMatches (CapturedPath cpin) pin = pin == sanitizeCapts cpin (delete "/" pin)
+  where sanitizeCapts = zipWith (\c p -> bool c p $ (fst <$> T.uncons c) == Just ':')
 
-{- Captures & Regex Captures -}
-      
--- |Returns path captures by comparing @path@ to @pathInfo@.
+-- | Returns path captures by comparing @path@ to @pathComps@.
 pathCaptures :: Path -- ^ path
-             -> PathInfo -- ^ pathInfo
-             -> [(Text,Text)]
+             -> [Text] -- ^ pathComps
+             -> [(Text, Text)]
 pathCaptures (LiteralPath _) _ = []
-pathCaptures (RegexPath r) pin = maybe [] (\(_,x,_,xs) -> f (x:xs)) matched
+pathCaptures (RegexPath r) pin = maybe [] (\(_, x, _, xs) -> f (x:xs)) matched
   where
-    f = numberList . map T.decodeUtf8
-    matched :: Maybe (B.ByteString,B.ByteString,B.ByteString,[B.ByteString])
-    matched = matchM r $ T.encodeUtf8 $ joinPath pin
-
+    f = indexedList . map T.decodeUtf8
+    indexedList = zipWith (\a b -> (T.pack $ show a, b)) ([0..] :: [Integer])
+    
+    matched :: Maybe (B.ByteString, B.ByteString, B.ByteString, [B.ByteString])
+    matched = matchM r $ T.encodeUtf8 $ joinPathComps pin
 pathCaptures (CapturedPath cap) pin = f [] cap pin
   where
     f acc [] [] = acc
-    f _   _  [] = []
-    f _   [] _  = []
-    f acc (c:cs) (p:ps)
-      | T.head c == ':' = f ((T.tail c,p):acc) cs ps
-      | p == c = f acc cs ps
-      | otherwise = []
-      
-numberList :: [Text] -> [(Text,Text)]
-numberList = zipWith (\a b -> (T.pack $ show a, b)) ([0..] :: [Integer])
+    f _ [] _ = []
+    f _ _ [] = []
+    f acc (c:cs) (p:ps) = g $ T.uncons c
+      where g (Just (':', xs)) = f ((xs, p):acc) cs ps
+            g _ = bool [] (f acc cs ps) $ p == c
+
+{- PATH COMPONENTS OPERATIONS -}
+
+-- | Split a 'Text' into a path components.
+splitPathComps :: Text -> [Text]
+splitPathComps = filter (not . T.null) . T.split (== '/') . T.takeWhile (/= '?')
+
+-- | Join path components into a 'Text'ual path.
+joinPathComps :: [Text] -> Text
+joinPathComps = mconcat . intersperse "/" . (:) ""
+
+-- | Determine if some path components contain only valid characters.
+isValidPathComps :: [Text] -> Bool
+isValidPathComps = all $ \v -> not (T.null v) && T.all isPathChar v
+  where isPathChar c = isAlphaNum c || (c `elem` ("-.~!$&'()*+,;=:@%" :: String))
diff --git a/Web/App/RouteT.hs b/Web/App/RouteT.hs
--- a/Web/App/RouteT.hs
+++ b/Web/App/RouteT.hs
@@ -1,35 +1,35 @@
 {-|
-Module      : Web.App.Monad.WebAppT
+Module      : Web.App.RouteT
 Copyright   : (c) Nathaniel Symer, 2015
 License     : MIT
 Maintainer  : nate@symer.io
 Stability   : experimental
 Portability : POSIX
 
-Defines a monad transformer used for defining routes
-and using middleware.
+Monad transformer used for defining web app routes.
 -}
 
-{-# LANGUAGE OverloadedStrings, TupleSections, FlexibleInstances, MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings, TupleSections #-}
 
 module Web.App.RouteT
 (
-  -- * RouteT monad transformer
+  toApplication,
+  -- * Types
   RouteT,
-  evalRouteT,
+  Route(..),
   -- * Routes
-  RouteResult,
-  Predicate,
-  Route,
-  RouteInterrupt(..),
-  findRoute,
+  get,
+  post,
+  put,
+  patch,
+  delete,
+  options,
+  method,
+  matchAll,
   -- * Monadic actions
   halt,
-  halt',
   next,
   writeBody,
-  writeBodyBytes,
-  writeJSON,
   request,
   addHeader,
   status,
@@ -39,234 +39,176 @@
   params,
   param,
   maybeParam,
-  bodyReader,
   body,
-  urlencodedBody,
-  path
+  getState,
+  putState
 )
 where
-  
-{-
 
-TODO
-
-  * Rewite 'param' and 'maybeParam' to avoid calling next
-  * Allow 'InterruptNext' to carry state into
-    the evaluation of the next route.
-  
--}
-  
 import Web.App.State
 import Web.App.Path
 import Web.App.Stream
 import Web.App.Parameter
-  
-import Control.Monad (ap)
+
+import Control.Exception
+import Control.Monad
 import Control.Monad.IO.Class
 import Control.Monad.Trans.Class
-import Control.Monad.State.Class
-import Control.Monad.Writer.Class
 import Control.Concurrent.STM
-import Control.Applicative
 
 import Network.Wai
 import Network.HTTP.Types.Status
 import Network.HTTP.Types.Header
 import Network.HTTP.Types.URI
+import Network.HTTP.Types.Method
 
-import Data.Maybe
+import Data.Bool
 import Data.Monoid
-import Data.Aeson
+import Data.Functor
+
+import Data.IORef
+import Data.Vault.Lazy (Vault, Key)
+import qualified Data.Vault.Lazy as V
 import Data.CaseInsensitive (mk)
 
 import System.IO.Unsafe
 
 import Data.ByteString.Char8 (ByteString)
-import qualified Data.ByteString.Lazy.Internal as BL (ByteString(Empty,Chunk))
+import qualified Data.ByteString.Lazy.Internal as BL (ByteString(..))
 import qualified Data.ByteString.Char8 as B
 import qualified Data.ByteString.Lazy.Char8 as BL
 import qualified Data.Text.Encoding as T
 
-type Predicate = Request -> Bool -- ^ Used to determine if a route can handle a request
-type Route s m = (Predicate, Path, RouteT s m ()) 
-type RouteResult = Maybe (Status, ResponseHeaders, Stream) -- kind of like a Ruby Rack response.
+data Route s m = Route {
+  _routePredicate :: Request -> Bool,
+  _routePath :: Path,
+  _routeAction :: RouteT s m ()
+}
 
-data RouteInterrupt = InterruptNext -- ^ halt current route evaluation and start evaluating next route
-                    | InterruptHalt (Maybe Status) ResponseHeaders (Maybe Stream) -- ^ halt & provide a response
+data EvalState = Normal -- ^ continue executing as normal
+               | Next -- ^ skip to the next route
+               | Halt -- ^ stop evaluation altogether
+  deriving (Eq)
 
--- |Monad transformer in which routes are evaluated. It's essentially
--- an ExceptT crossed with an RWST with the path, body, and push func
--- as the "Reader" state, the response as the "Writer" state, and no
--- "State" state.
+data ResponseChange = SetStatus Status
+                    | AddHeaders [Header]
+                    | AddBytes Stream
+
+-- | INTERNAL: apply ResponseChanges to an empty response and return the result
+flattenResponse :: [ResponseChange] -> Response
+flattenResponse = f (status200, [], mempty)
+  where f (s, hdrs, sm) [] = responseStream s hdrs $ runStream sm
+        f (_, hdrs, sm) ((SetStatus s):rs) = f (s, hdrs, sm) rs
+        f (s, hdrs, sm) ((AddHeaders hdrs'):rs) = f (s, hdrs ++ hdrs', sm) rs
+        f (s, hdrs, sm) ((AddBytes bs):rs) = f (s, hdrs, sm <> bs) rs
+
+-- |Monad transformer in which routes are evaluated. Evaluation may be stopped
+-- via 'halt', 'next', or 'redirect'.
 newtype RouteT s m a = RouteT {
   runRouteT :: TVar s -- ^ tvar containing state
             -> Path -- ^ path of route
-            -> TVar BL.ByteString -- ^ request body
             -> Request -- ^ request being served
-            -> m (Either RouteInterrupt (a, Maybe Status, ResponseHeaders, Maybe Stream))
+            -> m (a, (EvalState, [ResponseChange]))
 }
 
--- |Evaluate a 'RouteT' action into a 'RouteResult'.
-evalRouteT :: (WebAppState s, MonadIO m)
-           => RouteT s m () -- ^ route to evaluate
-           -> TVar s -- ^ tvar containing state
-           -> Path -- ^ path of route
-           -> Request -- ^ request being served
-           -> m (RouteResult)
-evalRouteT act st pth req = do
-  bdy <- liftIO $ newTVarIO BL.Empty
-  f <$> runRouteT act st pth bdy req
-  where
-    f (Left InterruptNext) = Nothing
-    f (Left (InterruptHalt s h b)) = Just (fromMaybe status200 s,h,maybe mempty flush b)
-    f (Right ~(_,s,h,b)) = Just (fromMaybe status200 s,h,maybe mempty flush b)
-            
 instance (WebAppState s, Functor m) => Functor (RouteT s m) where
-  fmap f m = RouteT $ \st pth bdy req -> fmap apply $ runRouteT m st pth bdy req
-    where
-      apply (Right (a,s,h,b)) = Right (f a,s,h,b)
-      apply (Left e) = Left e
+  fmap f m = RouteT $ \st pth req -> fmap apply $ runRouteT m st pth req
+    where apply (a, c) = (f a, c)
   
 instance (WebAppState s, Monad m) => Applicative (RouteT s m) where
-  pure a = RouteT $ \_ _ _ _ -> return $ Right (a,Nothing,[],Nothing)
+  pure a = RouteT $ \_ _ _ -> return (a, (Normal, mempty))
   (<*>) = ap
 
 instance (WebAppState s, Monad m) => Monad (RouteT s m) where
-  fail msg = RouteT $ \_ _ _ _ -> fail msg
-  m >>= k = RouteT $ \st pth bdy req -> do
-    v <- runRouteT m st pth bdy req
-    case v of
-      Left e -> return $ Left e
-      Right ~(x, s, h, b) -> do
-        v' <- runRouteT (k x) st pth bdy req
-        case v' of
-          Left InterruptNext -> return $ Left InterruptNext
-          Left (InterruptHalt s' h' b') -> return $ Left combined
-            where combined = InterruptHalt (s' <|> s) (h' <> h) (b <> b')
-          Right ~(y, s', h', b') -> return $ Right $ combined
-            where combined = (y, s' <|> s, h' <> h, b <> b')
+  fail msg = RouteT $ \_ _ _ -> fail msg
+  m >>= k = RouteT actionf
+    where
+      actionf st pth req = runRouteT m st pth req >>= handleFirst
+        where
+          handleFirst (x, (Normal, cng1)) = runRouteT (k x) st pth req >>= handleSecond
+            where
+              handleSecond (x', (sts, cng2)) = return (x', (sts, cng3))
+                where cng3 = bool (cng1 <> cng2) cng2 $ sts == Halt
+          handleFirst (_, c) = return (undefined, c)
 
 instance (WebAppState s) => MonadTrans (RouteT s) where
-  lift m = RouteT $ \_ _ _ _ -> m >>= return . Right . (,Nothing,[],Nothing)
+  lift m = RouteT $ \_ _ _ -> m >>= return . (,(Normal, mempty))
 
 instance (WebAppState s, MonadIO m) => MonadIO (RouteT s m) where
   liftIO = lift . liftIO
-  
--- |MonadState instance for accessing the mutable state.
-instance (WebAppState s, MonadIO m) => MonadState s (RouteT s m) where
-  get = RouteT $ \st _ _ _ ->
-    Right . (,Nothing,[],Nothing) <$> (liftIO $ readTVarIO st)
-  put v = RouteT $ \st _ _ _ ->
-    Right . (,Nothing,[],Nothing) <$> (liftIO $ atomically $ writeTVar st v)
 
--- |MonadWriter instance for writing to the HTTP response body.
-instance (WebAppState s, Monad m) => MonadWriter Stream (RouteT s m) where
-  tell s = RouteT $ \_ _ _ _ -> return $ Right ((),Nothing,[],Just s)
-  listen act = RouteT $ \st pth bdy req -> do
-    v <- runRouteT act st pth bdy req
-    case v of
-      Left e -> return $ Left e
-      Right (a,_,_,mw) -> return $ Right ((a,fromMaybe mempty mw),Nothing,[],Nothing)
-  pass act = RouteT $ \st pth bdy req -> do
-    v <- runRouteT act st pth bdy req
-    case v of
-      Left e -> return $ Left e
-      Right ((a,f),_,_,mw) ->
-        return $ Right (a,Nothing,[],maybe mempty (Just . f) mw)
-
-{- Route Evaluation -}
-
--- |Find the first route that can respond to @request@ in @routes@.
-findRoute :: (WebAppState s, Monad m)
-          => [Route s m] -- ^ routes
-          -> Request -- ^ request
-          -> Maybe ([Route s m],Route s m)
-findRoute [] _ = Nothing
-findRoute (x@(pd,pth,_):xs) req
-  | pd req && pathMatches pth (pathInfo req) = Just (xs,x)
-  | otherwise = findRoute xs req
-
 {- Monadic actions -}
 
--- |Halt route evaluation and provide the given 'Status',
--- 'ResponseHeaders', and 'Stream'.
-halt :: (WebAppState s, Monad m)
-     => Status -- ^ status with which to terminate
-     -> ResponseHeaders -- ^ headers with which to terminate
-     -> Stream -- ^ body with which to terminate
-     -> RouteT s m a
-halt s h b = act >> let x = x in x -- second action will never be evaluated
-  where act = RouteT $ \_ _ _ _ ->
-                return $ Left $ InterruptHalt (Just s) h (Just b)
-  
--- |Halt route evaluation and provide the accumulated 'Status',
--- 'ResponseHeaders', and 'Stream'.
-halt' :: (WebAppState s, Monad m) => RouteT s m a
-halt' = act >> let x = x in x -- second action will never be evaluated
-  where act = RouteT $ \_ _ _ _ ->
-                return $ Left $ InterruptHalt Nothing [] Nothing
+-- |INTERNAL A helper to avoid having the RouteT constructor appear everywhere (DRY)
+{-# INLINE context #-}
+context :: (WebAppState s, Monad m) => EvalState -> [ResponseChange] -> RouteT s m ()
+context st hc = RouteT $ \_ _ _ -> return ((), (st, hc))
 
--- |Halt route evaluation and move onto the next
--- route that passes.
-next :: (WebAppState s, Monad m) => RouteT s m a
-next = act >> let x = x in x -- second action will never be evaluated
-  where act = RouteT $ \_ _ _ _ -> return $ Left InterruptNext
+-- |INTERNAL Get the state TVar.
+stateTVar :: (WebAppState s, Monad m) => RouteT s m (TVar s)
+stateTVar = RouteT $ \st _ _ -> return (st, (Normal, mempty))
 
--- |Write a 'Stream' to the response body.
-writeBody :: (WebAppState s, Monad m, ToStream w) => w -> RouteT s m ()
-writeBody w = RouteT $ \_ _ _ _ ->
-  return $ Right ((),Nothing,[],Just $ stream' w)
-  
--- |Same as 'writeBody', but designed for use
--- with literals via OverloadedStrings
-writeBodyBytes :: (WebAppState s, Monad m) => ByteString -> RouteT s m ()
-writeBodyBytes = writeBody
-  
--- |Write a JSON object to the response body.
-writeJSON :: (WebAppState s, Monad m, ToJSON j) => j -> RouteT s m ()
-writeJSON = writeBody . encode
+-- |INTERNAL Get the route's path. This can be different across different evaluations of the same route.
+path :: (WebAppState s, Monad m) => RouteT s m Path
+path = RouteT $ \_ pth _ -> return (pth, (Normal, mempty))
 
 -- |Get the 'Request' being served.
 request :: (WebAppState s, Monad m) => RouteT s m Request
-request = RouteT $ \_ _ _ req -> return $ Right (req,Nothing,[],Nothing)
+request = RouteT $ \_ _ req -> return (req, (Normal, mempty))
 
+-- | Get the web app state.
+getState :: (WebAppState s, MonadIO m) => RouteT s m s
+getState = liftIO . readTVarIO =<< stateTVar
+
+-- | Set the web app state.
+putState :: (WebAppState s, MonadIO m) => s -> RouteT s m ()
+putState v = liftIO . atomically . flip writeTVar v =<< stateTVar
+
 -- |Add an HTTP header.
 addHeader :: (WebAppState s, Monad m) => HeaderName -> ByteString -> RouteT s m ()
-addHeader k v = RouteT $ \_ _ _ _ -> return $ Right ((),Nothing,[(k,v)],Nothing)
+addHeader k v = context Normal [AddHeaders [(k, v)]]
 
 -- |Set the HTTP status.
 status :: (WebAppState s, Monad m) => Status -> RouteT s m ()
-status s = RouteT $ \_ _ _ _ -> return $ Right ((),Just s,[],Nothing)
+status s = context Normal [SetStatus s]
 
+-- |Write a 'Stream' to the response body.
+writeBody :: (WebAppState s, Monad m, ToStream w) => w -> RouteT s m ()
+writeBody w = context Normal [AddBytes (stream' w)]
+
+-- |Halt route evaluation and provide a 'Status', '[Header]', and 'Stream'.
+halt :: (WebAppState s, Monad m) => Status -> [Header] -> Stream -> RouteT s m a
+halt s h b = context Halt [SetStatus s, AddBytes b, AddHeaders h] >> undefined
+
+-- |Halt route evaluation and move onto the next matched route.
+next :: (WebAppState s, Monad m) => RouteT s m a
+next = context Next [] >> undefined
+
 -- |Redirect to the given path using a @Location@ header and
--- an HTTP status of 302. Route evaluation continues.
+-- an HTTP status of 302. Route evaluation halts.
+{-# INLINE redirect #-}
 redirect :: (WebAppState s, MonadIO m) => ByteString -> RouteT s m ()
 redirect url = halt status302 [("Location",url)] mempty
   
 -- |Get the 'Request''s headers.
-headers :: (WebAppState s, Monad m) => RouteT s m RequestHeaders
+headers :: (WebAppState s, Monad m) => RouteT s m [Header]
 headers = requestHeaders <$> request
 
 -- |Get a specific header.
 header :: (WebAppState s, Monad m) => ByteString -> RouteT s m (Maybe ByteString)
 header k = lookup (mk k) <$> headers
 
--- |Get the 'Request''s parameters (in order captures, HTTP body, URI query).
+-- |Read the 'Request''s parameters (in order captures, HTTP body, URI query).
 params :: (WebAppState s, MonadIO m) => RouteT s m Query
-params = fmap mconcat $ sequence [cap, bdy, q]
+params = maybe (readAll >>= insertMutVault cachedParamsKey) return =<< lookupMutVault cachedParamsKey
   where
-    cap = do
-      pinfo <- pathInfo <$> request
-      pth <- path
-      return $ map toQueryItem $ pathCaptures pth pinfo
-      where toQueryItem (a,b) = (T.encodeUtf8 a, Just $ T.encodeUtf8 b)
-    bdy = do
-      h <- requestHeaders <$> request
-      case lookup (mk "Content-Type") h of
-        Just "application/x-www-form-urlencoded" -> parseQuery . BL.toStrict <$> body
-        Just "multipart/form-data" -> return [] -- TODO: implement me
-        _ -> return []
-    q = queryString <$> request
+    readAll = mconcat <$> sequence [cap, bdy, queryString <$> request]
+    cap = map toQueryItem <$> (pathCaptures <$> path <*> (pathInfo <$> request))
+    bdy = request >>= maybe (return []) bodyParamsFor . lookup (mk "Content-Type") . requestHeaders
+    bodyParamsFor "application/x-www-form-urlencoded" = parseQuery . BL.toStrict <$> body
+    bodyParamsFor "multipart/form-data" = return [] -- TODO: implement me
+    bodyParamsFor _ = return []
+    toQueryItem (a, b) = (T.encodeUtf8 a, Just $ T.encodeUtf8 b)
 
 -- |Get a specific header. Will call 'next' if the parameter isn't present.
 param :: (WebAppState s, MonadIO m, Parameter a) => ByteString -> RouteT s m a
@@ -274,38 +216,104 @@
 
 -- |Get a specific header. Will not interfere with route evaluation.
 maybeParam :: (WebAppState s, MonadIO m, Parameter a) => ByteString -> RouteT s m (Maybe a)
-maybeParam k = f . lookup k <$> params
-  where f (Just (Just v)) = maybeRead v
-        f _ = Nothing
-
--- |Get an action that reads a chunk from the request body.
--- Incompatible with 'body'.
-bodyReader :: (WebAppState s, MonadIO m) => RouteT s m (IO ByteString)
-bodyReader = RouteT $ \_ _ _ r -> return $ Right (requestBody r,Nothing,[],Nothing)
+maybeParam k = f . lookup k <$> params where f x = join x >>= maybeRead
 
 -- |Read the request body as a lazy 'ByteString'.
--- Incompatible with 'bodyReader'.
 body :: (WebAppState s, MonadIO m) => RouteT s m BL.ByteString
-body = do
-  tvar <- bodyTVar
-  act <- fmap requestBody request
-  liftIO $ persisted tvar act
+body = maybe (request >>= liftIO . lazyRead . requestBody >>= insertMutVault cachedBodyKey) return =<< lookupMutVault cachedBodyKey
+  where lazyRead rd = unsafeInterleaveIO $ rd >>= f
+          where f c = bool (BL.Chunk c <$> lazyRead rd) (return BL.Empty) $ B.null c
+
+{- ROUTE DEFINITION -}
+
+-- |Match all requests and paths.
+matchAll :: (WebAppState s, Monad m) => RouteT s m () -> Route s m
+matchAll = Route (const True) (regex ".*")
+
+{-# INLINE get #-}
+{-# INLINE post #-}
+{-# INLINE put #-}
+{-# INLINE patch #-}
+{-# INLINE delete #-}
+{-# INLINE options #-}
+get,post,put,patch,delete,options :: (WebAppState s, Monad m) => Path -> RouteT s m () -> Route s m
+get        = method methodGet
+post       = method methodPost
+put        = method methodPut
+patch      = method methodPatch
+delete     = method methodDelete
+options    = method methodOptions
+
+-- |Match a route based on the request's HTTP method.
+method :: (WebAppState s, Monad m) => Method -> Path -> RouteT s m () -> Route s m
+method m = Route $ (==) m . requestMethod
+
+{- Route Evaluation -}
+
+-- |Makes an 'Application' from routes and middleware.
+toApplication :: (WebAppState s, Monad m)
+              => (m (Maybe Response) -> IO (Maybe Response)) -- ^ run your monadic type to IO
+              -> [Route s m] -- ^ routes
+              -> [Middleware] -- ^ middlewares
+              -> (Application, IO ()) -- ^ (application, teardown action)
+toApplication runToIO routes mws = (app', readTVarIO st >>= destroyState)
   where
-    bodyTVar = RouteT $ \_ _ b _ -> return $ Right (b,Nothing,[],Nothing)
-    persisted tvar act = do
-      remaining <- lazyRead act
-      atomically $ do
-        alreadyRead <- readTVar tvar
-        writeTVar tvar $ alreadyRead <> remaining
-        readTVar tvar
-    lazyRead f = unsafeInterleaveIO $ do
-      c <- f
-      if B.null c
-        then return BL.Empty
-        else BL.Chunk c <$> (lazyRead f)
+    st = unsafePerformIO (newTVarIO =<< initState)
+    {-# NOINLINE st #-}
+    plainText = [("Content-Type", "text/plain; charset=utf-8")]
+    app' = foldl (flip ($)) app mws
+    app req callback = go =<< findSuccessful routeMatches runRoute routes
+      where
+        runRoute (Route _ pth act) = runToIO $ toResponse <$> runRouteT act st pth (addMutVault req)
+        routeMatches (Route pd pth _) = pd req && pathMatches pth (pathInfo req)
+        toResponse (_, (Next, _)) = Nothing
+        toResponse (_, (_, cng)) = Just $ flattenResponse cng
+        go Nothing = callback $ responseLBS status404 [] mempty
+        go (Just (Left e)) = callback $ responseLBS status500 plainText $ BL.pack $ show (e :: SomeException)
+        go (Just (Right jawn)) = callback jawn
 
-urlencodedBody :: (WebAppState s, MonadIO m) => RouteT s m Query
-urlencodedBody = parseQuery . BL.toStrict <$> body
+-- | INTERNAL Find an a that passes a predicate and either errors out or returns something. Returns the error or the something.
+findSuccessful :: (Exception e) => (a -> Bool) -> (a -> IO (Maybe b)) -> [a] -> IO (Maybe (Either e b))
+findSuccessful _ _ [] = return Nothing
+findSuccessful p f (x:xs) = if p x then (try $ f x) >>= go else findSuccessful p f xs
+    where
+      go (Right Nothing) = findSuccessful p f xs
+      go (Right (Just v)) = return $ Just $ Right v
+      go (Left e) = return $ Just $ Left e
+    
+    
+{- Vault (internal) -}
 
-path :: (WebAppState s, Monad m) => RouteT s m Path
-path = RouteT $ \_ pth _ _ -> return $ Right (pth,Nothing,[],Nothing)
+-- Used to implement framework functionality    
+          
+cachedBodyKey :: Key BL.ByteString
+cachedBodyKey = unsafePerformIO V.newKey
+{-# NOINLINE cachedBodyKey #-}
+
+cachedParamsKey :: Key Query
+cachedParamsKey = unsafePerformIO V.newKey
+{-# NOINLINE cachedParamsKey #-}
+              
+-- Used to implement mutable Vault.
+              
+mutableVaultKey :: Key (IORef Vault)
+mutableVaultKey = unsafePerformIO V.newKey
+{-# NOINLINE mutableVaultKey #-}
+
+-- | Adds a "mutable" 'Vault' (@'IORef' 'Vault'@) to a 'Request'.
+addMutVault :: Request -> Request
+addMutVault r = r { vault = V.insert mutableVaultKey ioRef (vault r) }
+  where ioRef = unsafePerformIO $ newIORef V.empty
+        {-# NOINLINE ioRef #-}
+
+-- | Exposes the "mutable" 'Vault' for modification/access. DRY.
+withMutVaultM :: (WebAppState s, MonadIO m) => (Vault -> RouteT s m (Maybe a, Vault)) -> RouteT s m (Maybe a)
+withMutVaultM xform = request >>= maybe (return Nothing) f . V.lookup mutableVaultKey . vault
+  where f ior = (liftIO $ readIORef ior) >>= xform >>= uncurry (g ior)
+        g ior ret vlt' = (liftIO $ writeIORef ior vlt') $> ret
+
+lookupMutVault :: (WebAppState s, MonadIO m) => Key a -> RouteT s m (Maybe a)
+lookupMutVault k = withMutVaultM $ \vlt -> return (V.lookup k vlt, vlt)
+
+insertMutVault :: (WebAppState s, MonadIO m) => Key a -> a -> RouteT s m a
+insertMutVault k v = (withMutVaultM $ return . (Nothing,) . V.insert k v) $> v
diff --git a/Web/App/Stream.hs b/Web/App/Stream.hs
--- a/Web/App/Stream.hs
+++ b/Web/App/Stream.hs
@@ -30,6 +30,7 @@
 import qualified Data.Text.Lazy as TL
 import qualified Data.Text.Lazy.Encoding as TL
 import Data.Monoid
+import Data.Bool
 
 -- |An HTTP response body stream.
 newtype Stream = Stream { runStream :: StreamingBody }
@@ -55,6 +56,9 @@
 instance (ToStream a) => ToStream [a] where
   stream True b = (stream False b) <> flusher
   stream False b = mconcat $ map (stream False) b
+  
+instance ToStream Stream where
+  stream = bool id flush
   
 instance ToStream () where
   stream _ _ = mempty
diff --git a/Web/App/WebApp.hs b/Web/App/WebApp.hs
deleted file mode 100644
--- a/Web/App/WebApp.hs
+++ /dev/null
@@ -1,119 +0,0 @@
-{-|
-Module      : Web.App.Monad.WebAppT
-Copyright   : (c) Nathaniel Symer, 2015
-License     : MIT
-Maintainer  : nate@symer.io
-Stability   : experimental
-Portability : POSIX
-
-Defines a monoid used for defining routes
-and using middleware.
--}
-
-{-# LANGUAGE OverloadedStrings, TupleSections, FlexibleInstances #-}
-
-module Web.App.WebApp
-(
-  WebApp(..),
-  toApplication,
-  -- * Web App Operations
-  middleware,
-  route,
-  get,
-  post,
-  put,
-  patch,
-  delete,
-  options,
-  anyRequest,
-  matchAll
-) where
-
-import Web.App.State
-import Web.App.RouteT
-import Web.App.Path
-import Web.App.Stream
-
-import Control.Monad.IO.Class
-import Control.Concurrent.STM
-
-import Network.Wai
-import Network.HTTP.Types.Status
-import Network.HTTP.Types.Method
-
-import Data.Monoid
-
--- |Monoid for defining routes & adding middleware.
-newtype WebApp s m = WebApp { runWebApp :: ([Route s m],[Middleware]) }
-
-instance (WebAppState s, Monad m) => Monoid (WebApp s m) where
-  mempty = WebApp ([],[])
-  mappend (WebApp (r,mw)) (WebApp (r',mw')) = WebApp (r <> r',mw <> mw')
-
--- |Turn a 'WebAppT' computation into a WAI 'Application'.
-toApplication :: (WebAppState s, MonadIO m, MonadIO n)
-              => (m RouteResult -> IO RouteResult) -- ^ function to run WebApp-transformed monad m to IO
-              -> WebApp s m -- ^ a web app
-              -> n (Application, -- ^ WAI application
-                    IO ()) -- ^ teardown action; call when shutting down app server
-toApplication runToIO webapp = do
-  st <- liftIO $ newTVarIO =<< initState
-  let (rts,mws) = runWebApp webapp
-      app = foldl (flip ($)) (mkApp st rts) mws
-      teardown = readTVarIO st >>= destroyState
-  return (app, teardown)
-  where
-    plainText = [("Content-Type", "text/plain; charset=utf-8")]
-    mkApp tvar routes req callback = case findRoute routes req of
-      Nothing -> callback $ responseLBS status404 plainText "Not found."
-      Just (remainder,(_,pth,act)) -> do
-        res <- runToIO $ evalRouteT act tvar pth req
-        case res of
-          Nothing -> mkApp tvar remainder req callback
-          Just (s,h,b) -> callback $ responseStream s h $ runStream b
-
--- |Use a middleware
-middleware :: (WebAppState s, Monad m) => Middleware -> WebApp s m
-middleware m = WebApp ([],[m])
-
--- |Define a route
-route :: (WebAppState s, Monad m) => Predicate -> Path -> RouteT s m () -> WebApp s m
-route p pth act = WebApp ([(p,pth,act)],[])
-
-{-# INLINE matchMethod #-}
-matchMethod :: Method -> Predicate
-matchMethod meth = \r -> (requestMethod r) == meth
-
-{- Monadic matchers -}
-
--- |Match a `GET` request.
-get :: (WebAppState s, Monad m) => Path -> RouteT s m () -> WebApp s m
-get p act = route (matchMethod methodGet) p act
-
--- |Match a `POST` request.
-post :: (WebAppState s, Monad m) => Path -> RouteT s m () -> WebApp s m
-post p act = route (matchMethod methodPost) p act
-
--- |Match a `PUT` request.
-put :: (WebAppState s, Monad m) => Path -> RouteT s m () -> WebApp s m
-put p act = route (matchMethod methodPut) p act
-
--- |Match a `PATCH` request.
-patch :: (WebAppState s, Monad m) => Path -> RouteT s m () -> WebApp s m
-patch p act = route (matchMethod methodPatch) p act
-
--- |Match a `DELETE` request.
-delete :: (WebAppState s, Monad m) => Path -> RouteT s m () -> WebApp s m
-delete p act = route (matchMethod methodDelete) p act
-
--- |Match a `OPTIONS` request.
-options :: (WebAppState s, Monad m) => Path -> RouteT s m () -> WebApp s m
-options p act = route (matchMethod methodOptions) p act
-
--- |Match any request given a path.
-anyRequest :: (WebAppState s, Monad m) => Path -> RouteT s m () -> WebApp s m
-anyRequest = route (const True)
-
--- |Match all requests and paths.
-matchAll :: (WebAppState s, Monad m) => RouteT s m () -> WebApp s m
-matchAll = route (const True) (regex ".*")
diff --git a/webapp.cabal b/webapp.cabal
--- a/webapp.cabal
+++ b/webapp.cabal
@@ -1,8 +1,8 @@
 Name:                webapp
-Version:             0.3.6
+Version:             0.4.0
 Synopsis:            Haskell web app framework based on WAI & Warp
-Homepage:            https://github.com/fhsjaagshs/webapp
-Bug-reports:         https://github.com/fhsjaagshs/webapp/issues
+Homepage:            https://github.com/natesymer/webapp
+Bug-reports:         https://github.com/natesymer/webapp/issues
 License:             MIT
 License-file:        LICENSE
 Author:              Nathaniel Symer <nate@symer.io>
@@ -21,35 +21,28 @@
   Exposed-modules:   Web.App
                      Web.App.HTTP
                      Web.App.State
-                     Web.App.Middleware
-                     Web.App.Middleware.Gzip
                      Web.App.Parameter
                      Web.App.Path
                      Web.App.RouteT
                      Web.App.Stream
                      Web.App.Main
-                     Web.App.WebApp
   default-language:  Haskell2010
-  build-depends:     base <4.9,
+  build-depends:     base < 5.0,
                      bytestring,
-                     base16-bytestring,
                      text,
-                     mtl,
                      stm,
                      transformers,
                      wai,
                      warp,
                      warp-tls,
                      network,
-                     streaming-commons,
                      regex-posix,
                      http-types,
                      unix,
                      blaze-builder,
-                     zlib,
                      optparse-applicative,
-                     aeson,
-                     case-insensitive
+                     case-insensitive,
+                     vault
 
 source-repository head
   type:     git
