diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for wai-middleware-delegate
+
+## 0.1.0.0 -- 2018-07-28
+
+* Initial version.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2018, Tim Emiola
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Tim Emiola nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# wai-middleware-delegate [![CircleCI](https://circleci.com/gh/adetokunbo/wai-middleware-delegate.svg?style=svg)](https://circleci.com/gh/adetokunbo/wai-middleware-delegate) [![BSD3](https://img.shields.io/badge/license-BSD3-green.svg?dummy)](https://github.com/adetokunbo/wai-middleware-delegate/blob/master/LICENSE)
+
+__wai-middleware-delegate__ is a [WAI](https://hackage.haskell.org/package/wai) middleware that allows requests to be handled by a delegate application that proxies requests to another server.
+
+## Example
+
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+
+import           Data.Default                    (Default (..))
+import           Network.HTTP.Client.TLS         (newTlsManager)
+import           Network.HTTP.Types              (status500)
+import           Network.Wai
+import           Network.Wai.Handler.Warp (run)
+import           Network.Wai.Middleware.Delegate (ProxySettings (..),
+                                                  delegateToProxy)
+
+sampleSettings :: ProxySettings
+sampleSettings = def { proxyHost = "httpbin.org" }
+
+-- | Create an application that proxies every request to httpbin.org
+httpBinDelegate :: ProxySettings -> IO Application
+httpBinDelegate s = do
+  -- delegate everything!
+  let takeItAll = const True
+      dummyApp _ respond = respond $ responseLBS status500 [] "I should have been proxied"
+
+  manager <- newTlsManager
+  return $ delegateToProxy s manager (takeItAll) dummyApp
+
+main :: IO ()
+main = httpBinDelegate sampleSettings >>= run 3000
+
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Network/Wai/Middleware/Delegate.hs b/src/Network/Wai/Middleware/Delegate.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Wai/Middleware/Delegate.hs
@@ -0,0 +1,187 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-|
+Module      : Network.Wai.Middleware.Delegate
+Description :
+  Provides a Wai middleware that delegates handling of requests.
+
+  - delegateTo: delegates handling of requests matching a predicate to a
+    delegate Application
+
+  - delegateToProxy : delegates handling of requests matching a predicate to
+    different host
+
+  - simpleProxy: is a simple reverse proxy, based on proxyApp of http-proxy by Erik
+    de Castro Lopo/Michael Snoyman
+
+Copyright   : (c) Tim Emiola, 2018
+License     : C8D3
+Maintainer  : tim.emiola@gmail.com
+Stability   : experimental
+-}
+
+module Network.Wai.Middleware.Delegate
+  ( delegateTo
+  , delegateToProxy
+  , simpleProxy
+  , ProxySettings(..)
+  , RequestPredicate
+  )
+
+where
+
+import           Control.Exception           (SomeException, handle,
+                                              toException)
+import           Control.Monad.IO.Class      (MonadIO, liftIO)
+import qualified Data.ByteString             as BS
+import qualified Data.ByteString.Char8       as C8
+import qualified Data.ByteString.Lazy.Char8  as LC8
+import           Data.Monoid                 ((<>))
+import           Data.String                 (IsString)
+
+import           Blaze.ByteString.Builder    (fromByteString)
+import           Control.Concurrent.Async    (race_)
+import           Data.CaseInsensitive        (mk)
+import           Data.Conduit                (ConduitT, Flush (..), Void,
+                                              mapOutput, runConduit, yield,
+                                              (.|))
+import           Data.Conduit.Network        (appSink, appSource)
+import           Data.Default                (Default (..))
+import           Data.Streaming.Network      (ClientSettings, clientSettingsTCP,
+                                              runTCPClient)
+import           Network.HTTP.Client         (Manager, Request (..),
+                                              Response (..), parseRequest,
+                                              withResponse)
+import           Network.HTTP.Client.Conduit (bodyReaderSource)
+import           Network.HTTP.Conduit        (requestBodySourceChunkedIO,
+                                              requestBodySourceIO)
+import           Network.HTTP.Types          (hContentType,
+                                              internalServerError500, status304,
+                                              status500)
+import           Network.HTTP.Types.Header   (hHost)
+import qualified Network.Wai                 as Wai
+import           Network.Wai.Conduit         (responseRawSource, responseSource,
+                                              sourceRequestBody)
+
+-- | Type alias for a function that determines if a request should be handled by
+-- a delegate.
+type RequestPredicate = Wai.Request -> Bool
+
+-- | Create a middleware that handles all requests matching a predicate by
+-- delegating to an alternate Application.
+delegateTo :: Wai.Application -> RequestPredicate -> Wai.Middleware
+delegateTo alt f actual req
+  | f req = alt req
+  | otherwise = actual req
+
+-- | Creates a middleware that handles all requests matching a predicate by
+-- proxing them to a host specified by ProxySettings.
+delegateToProxy :: ProxySettings -> Manager -> RequestPredicate -> Wai.Middleware
+delegateToProxy settings mgr = delegateTo (simpleProxy settings mgr)
+
+-- | Settings that configure the proxy endpoint.
+data ProxySettings =
+  ProxySettings
+  { -- | What to do with exceptions thrown by either the application or server.
+    proxyOnException :: SomeException -> Wai.Response
+    -- | Timeout value in seconds. Default value: 30
+  , proxyTimeout     :: Int
+    -- | The host being proxied
+  , proxyHost        :: BS.ByteString
+  }
+
+instance Default ProxySettings where
+  -- | The default settings for the Proxy server. See the individual settings for
+  -- the default value.
+  def = ProxySettings
+    { -- defaults to returning internal server error showing the error in the body
+      proxyOnException = onException
+      -- default to 15 seconds
+    , proxyTimeout = 15
+    , proxyHost = "localhost"
+    }
+    where
+      onException :: SomeException -> Wai.Response
+      onException e =
+        Wai.responseLBS internalServerError500
+        [ (hContentType, "text/plain; charset=utf-8") ] $
+        LC8.fromChunks [C8.pack $ show e]
+
+-- | A Wai Application that acts as a http/https proxy.
+simpleProxy
+  :: ProxySettings
+  -> Manager
+  -> Wai.Application
+simpleProxy settings manager req respond
+    -- we may connect requests to secure sites, when we do, we will not have
+    -- seen their URI properly
+    | Wai.requestMethod req == "CONNECT" = do
+        putStrLn $ "Seen a CONNECT !!! to path " ++ (C8.unpack $ Wai.rawPathInfo req)
+        respond $ responseRawSource (handleConnect req)
+                    (Wai.responseLBS status500 [("Content-Type", "text/plain")] "method CONNECT is not supported")
+    | otherwise = do
+        let scheme
+              | Wai.isSecure req = "https"
+              | otherwise = "http"
+            rawUrl = Wai.rawPathInfo req <> Wai.rawQueryString req
+            effectiveUrl = scheme ++ "://" ++ (C8.unpack $ proxyHost settings) ++ C8.unpack (rawUrl)
+            newHost = proxyHost settings
+            addHostHeader = (:) (hHost, newHost)
+
+        proxyReq' <- parseRequest effectiveUrl
+        let onException :: SomeException -> Wai.Response
+            onException = proxyOnException settings . toException
+
+            proxyReq = proxyReq'
+              { method = Wai.requestMethod req
+              , requestHeaders = addHostHeader $ filter dropUpstreamHeaders $ Wai.requestHeaders req
+                -- always pass redirects back to the client.
+              , redirectCount = 0
+              , requestBody =
+                  case Wai.requestBodyLength req of
+                    Wai.ChunkedBody ->
+                      requestBodySourceChunkedIO (sourceRequestBody req)
+                    Wai.KnownLength l ->
+                      requestBodySourceIO (fromIntegral l) (sourceRequestBody req)
+              -- don't modify the response to ensure consistency with the response headers
+              , decompress = const False
+              , host = newHost
+              }
+
+            respondUpstream = withResponse proxyReq manager $ \res -> do
+              let body = mapOutput (Chunk . fromByteString) . bodyReaderSource $ responseBody res
+                  headers = (mk "X-Via-Proxy", "yes") : (responseHeaders res)
+              respond $ responseSource (responseStatus res) headers body
+
+        handle (respond . onException) respondUpstream
+
+handleConnect
+  :: Wai.Request
+  -> ConduitT () C8.ByteString IO ()
+  -> ConduitT C8.ByteString Void IO ()
+  -> IO ()
+handleConnect req fromClient toClient =
+  runTCPClient (toClientSettings req) $ \ad -> do
+  runConduit $ yield "HTTP/1.1 200 OK\r\n\r\n" .| toClient
+  race_
+    (runConduit $ fromClient .| appSink ad)
+    (runConduit $ appSource ad .| toClient)
+
+defaultClientPort :: Wai.Request -> Int
+defaultClientPort req
+  | Wai.isSecure req = 443
+  | otherwise = 90
+
+toClientSettings :: Wai.Request -> ClientSettings
+toClientSettings req =
+  case C8.break (== ':') $ Wai.rawPathInfo req of
+    (host, "") -> clientSettingsTCP (defaultClientPort req) host
+    (host, port') -> case C8.readInt $ C8.drop 1 port' of
+      Just (port, _) -> clientSettingsTCP port host
+      Nothing        -> clientSettingsTCP (defaultClientPort req) host
+
+dropUpstreamHeaders :: (Eq a, IsString a) => (a, b) -> Bool
+dropUpstreamHeaders (k, _) = k `notElem`
+  [ "content-encoding"
+  , "content-length"
+  , "host"
+  ]
diff --git a/test/IntegrationTest.hs b/test/IntegrationTest.hs
new file mode 100644
--- /dev/null
+++ b/test/IntegrationTest.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE NamedFieldPuns    #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import           Control.Monad                   (when)
+import           Data.Foldable                   (for_)
+import           Data.Maybe                      (maybe)
+import           System.Environment              (lookupEnv)
+
+import           Data.Default                    (Default (..))
+import           Network.HTTP.Client.TLS         (newTlsManager)
+import           Network.HTTP.Types              (status500)
+import           Network.Wai                     (Application, rawPathInfo,
+                                                  responseLBS)
+import           Network.Wai.Handler.Warp        (Port, testWithApplication)
+
+
+import           Network.Wai.Middleware.Delegate (ProxySettings (..),
+                                                  delegateToProxy)
+
+import           Test.Fetch                      (fetch)
+import           Test.Hspec
+import           Test.HttpReply
+import           Test.TestRequests               (RequestBuilder (..),
+                                                  buildRequest,
+                                                  testNotProxiedRequests,
+                                                  testRequests)
+import           Test.WithExtras                 (defaultTlsSettings,
+                                                  testWithTLSApplication)
+
+defaultTestSettings :: ProxySettings
+defaultTestSettings = def { proxyHost = "httpbin.org", proxyTimeout = 2 }
+
+main :: IO ()
+main = do
+  dumpDebug' <- lookupEnv "DEBUG"
+  let dumpDebug = maybe False (const True) dumpDebug'
+  hspec $ do
+    insecureProxyTest dumpDebug
+    secureProxyTest dumpDebug
+
+defaultTestDelegate :: ProxySettings -> IO Application
+defaultTestDelegate s = do
+  -- delegate everything but /status/418
+  let handleFunnyStatus = \req -> rawPathInfo req /= "/status/418"
+      dummyApp _ respond = respond $ responseLBS status500 [] "I should have been proxied"
+
+  manager <- newTlsManager
+  return $ delegateToProxy s manager (handleFunnyStatus) dummyApp
+
+testWithInsecureProxy :: (Port -> IO ()) -> IO ()
+testWithInsecureProxy = testWithApplication (defaultTestDelegate defaultTestSettings)
+
+testWithSecureProxy :: (Port -> IO ()) -> IO ()
+testWithSecureProxy = testWithTLSApplication defaultTlsSettings (defaultTestDelegate defaultTestSettings)
+
+onDirectAndProxy :: (HttpReply -> HttpReply -> IO ()) -> Bool -> Int -> RequestBuilder -> IO ()
+onDirectAndProxy f debug testProxyPort builder = do
+  let proxiedBuilder = builder { rbHost = "localhost", rbPort = Just testProxyPort }
+  directReq <- buildRequest builder
+  proxiedReq <- buildRequest proxiedBuilder
+
+  when debug $ do
+    putStrLn "---------------"
+    putStrLn "Direct Request:"
+    putStrLn "---------------"
+    print directReq
+    putStrLn "----------------"
+    putStrLn "Proxied Request:"
+    putStrLn "----------------"
+    print proxiedReq
+  proxied <- fetch proxiedReq
+  direct <- fetch directReq
+  when debug $ do
+    putStrLn "Direct:"
+    putStrLn "-------"
+    print direct
+    putStrLn "Proxied:"
+    putStrLn "--------"
+    print proxied
+  f direct proxied
+
+insecureProxyTest :: Bool -> Spec
+insecureProxyTest debug =
+  let scheme = "HTTP"
+      desc = "Simple " ++ scheme ++ " proxying:"
+      assertEq = onDirectAndProxy assertHttpRepliesAreEq debug
+      assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug
+  in
+    around testWithInsecureProxy $ describe desc $ do
+    for_ testRequests $ \(title, modifier) ->
+      it (scheme ++ " " ++ title) $ \port -> assertEq port $ modifier def
+    for_ testNotProxiedRequests $ \(title, modifier) ->
+      it (scheme ++ " " ++ title) $ \port -> assertNeq port $ modifier def
+
+secureProxyTest :: Bool -> Spec
+secureProxyTest debug =
+  let
+    scheme = "HTTPS"
+    desc = "Simple " ++ scheme ++ " proxying:"
+    assertEq = onDirectAndProxy assertHttpRepliesAreEq debug
+    assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug
+    def' = def { rbSecure = True }
+  in
+    around testWithSecureProxy $ describe desc $ do
+    for_ testRequests $ \(title, modifier) ->
+      it (scheme ++ " " ++ title) $ \port -> assertEq port $ modifier def'
+    for_ testNotProxiedRequests $ \(title, modifier) ->
+      it (scheme ++ " " ++ title) $ \port -> assertNeq port $ modifier def'
diff --git a/test/Test/Fetch.hs b/test/Test/Fetch.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Fetch.hs
@@ -0,0 +1,68 @@
+{-# LANGUAGE BangPatterns      #-}
+{-# LANGUAGE NamedFieldPuns    #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Test.Fetch
+  ( fetch
+  )
+where
+
+import qualified Data.ByteString              as BS
+import qualified Data.ByteString.Char8        as C8
+import qualified Data.ByteString.Lazy         as LBS
+
+import           Control.Monad.Trans.Resource (runResourceT)
+import           Data.Conduit                 (ConduitT, SealedConduitT, Void,
+                                               await, sealConduitT, ($$+-))
+import qualified Data.Conduit.Binary          as CB
+import           Data.Int                     (Int64)
+import           Data.Maybe                   (fromMaybe)
+import           Network.Connection           (TLSSettings (..))
+import           Network.HTTP.Client          (Request, newManager,
+                                               responseBody, responseHeaders,
+                                               responseStatus, secure, managerSetProxy, proxyFromRequest)
+import           Network.HTTP.Client.TLS      (mkManagerSettings)
+import           Network.HTTP.Simple          (setRequestManager, withResponse)
+import           Network.HTTP.Types           (hContentLength, statusCode)
+
+import           Test.HttpReply
+
+
+fetch :: Request -> IO HttpReply
+fetch req = do
+    let mSettings = mkManagerSettings (TLSSettingsSimple True False False) Nothing
+        mSettings' = managerSetProxy proxyFromRequest mSettings
+    m <- newManager mSettings'
+    runResourceT $ withResponse (setRequestManager m req) getSrc
+  where
+    getSrc resp = do
+      let mbBodyLength = readInt64 <$> lookup hContentLength (responseHeaders resp)
+      bodyText <- checkBodySize (sealConduitT $ responseBody resp) mbBodyLength
+      return $ HttpReply (secure req) (statusCode $ responseStatus resp) (responseHeaders resp) bodyText
+
+bodyCheckBlock :: Int64
+bodyCheckBlock = 1000
+
+checkBodySize :: (Monad m) => SealedConduitT () BS.ByteString m () -> Maybe Int64 -> m BS.ByteString
+checkBodySize bodySrc Nothing = fmap (BS.concat . LBS.toChunks) $ bodySrc $$+- CB.take $ fromIntegral bodyCheckBlock
+checkBodySize bodySrc (Just len)
+  | len <= bodyCheckBlock = checkBodySize bodySrc Nothing
+  | otherwise = fromMaybe "Success" <$> (bodySrc $$+- sizeCheckSink len)
+
+-- A pipe that counts the size of each incoming C8.Bytestring, when the last is
+-- received, the result is Nothing if the size matches the expected value or an
+-- error message if it does not.
+sizeCheckSink :: Monad m => Int64 -> ConduitT C8.ByteString Void m (Maybe C8.ByteString)
+sizeCheckSink expectedSize = sink 0
+  where
+    sink !count = await >>= maybe (closeSink count) (sinkBlock count)
+    sinkBlock !count bs = sink (count + fromIntegral (BS.length bs))
+
+    -- | no more bytes: return Nothing if the count so far matches the expected value
+    closeSink !count
+      | count == expectedSize = return Nothing
+      | otherwise = return $ Just . C8.pack $ "Error : Body length " ++ show count
+                     ++ " should have been " ++ show expectedSize ++ "."
+
+readInt64 :: C8.ByteString -> Int64
+readInt64 = read . C8.unpack
diff --git a/test/Test/HttpReply.hs b/test/Test/HttpReply.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/HttpReply.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE NamedFieldPuns    #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Test.HttpReply
+  ( HttpReply(..)
+  , HttpReplyMismatch(..)
+  , compareHttpReplies
+  , assertHttpRepliesAreEq
+  , assertHttpRepliesDiffer
+  )
+
+where
+
+import qualified Data.ByteString       as BS
+import qualified Data.ByteString.Char8 as C8
+import           Data.List             (intercalate)
+import           Data.Maybe            (catMaybes, isJust, isNothing)
+import           Network.HTTP.Types    (Header, HeaderName)
+
+import           Data.CaseInsensitive  (original)
+
+data HttpReply =
+  HttpReply
+  { hrSecure  :: !Bool
+  , hrStatus  :: !Int
+  , hrHeaders :: ![Header]
+  , hrBytes   :: !BS.ByteString
+  }
+
+instance Show HttpReply where
+  show r = intercalate "\n" $
+    [ "Status: " ++ (show . hrStatus) r
+    , "Body:"
+    , C8.unpack $ hrBytes r
+    ] <> (map (show . concatHeader) $ hrHeaders r)
+    where
+      concatHeader (f, v) = BS.concat [ "  ", original f , ": " , v]
+
+data HttpReplyMismatch
+  = StatusMismatch Int Int
+  | HeaderMismatch HeaderName (Maybe C8.ByteString) (Maybe C8.ByteString)
+  | BodyMismatch BS.ByteString BS.ByteString
+  | MissingViaHeader
+  | UnexpectedViaHeader
+  deriving (Eq)
+
+instance Show HttpReplyMismatch where
+  show (StatusMismatch x y)= "HTTP status codes don't match : " ++ show x ++ " /= " ++ show y
+  show (HeaderMismatch name x y) = "Header field '" ++ show name ++ "' doesn't match : '" ++ show x ++ "' /= '" ++ show y
+  show (BodyMismatch x y) = "HTTP response bodies are different :\n" ++ C8.unpack x ++ "\n-----------\n" ++ C8.unpack y
+  show MissingViaHeader = "Error: Proxy connection should contain 'X-Via-Proxy' header."
+  show UnexpectedViaHeader = "Error: Direct connection should not contain 'X-Via-Proxy' header."
+
+assertHttpRepliesAreEq :: HttpReply -> HttpReply -> IO ()
+assertHttpRepliesAreEq direct proxied = do
+  let assertNoMismatches [] = return ()
+      assertNoMismatches xs = error $ intercalate "\n" $ map show xs
+  assertNoMismatches $ compareHttpReplies direct proxied
+
+assertHttpRepliesDiffer :: HttpReply -> HttpReply -> IO ()
+assertHttpRepliesDiffer direct proxied = do
+  let assertHasMismatches [] = error "Responses should be different!"
+      assertHasMismatches _ = return ()
+  assertHasMismatches $ compareHttpReplies direct proxied
+
+compareHttpReplies :: HttpReply -> HttpReply -> [HttpReplyMismatch]
+compareHttpReplies direct proxied = catMaybes mbMismatches
+  where
+    mbMismatches =
+      [ compare' hrStatus StatusMismatch
+      , compare' hrBytes BodyMismatch
+      , missingViaHeader
+      , unexpectedViaHeader
+      , mismatchedHeader "server"
+      , mismatchedHeader "content-type"
+      , mismatchedHeader "content-length"
+      ]
+    wantSecure = hrSecure direct
+    maybeHeader n r = lookup n $ hrHeaders r
+    compare' f g
+      | f direct == f proxied = Nothing
+      | otherwise = Just $ g (f direct) (f proxied)
+    missingViaHeader
+      | not wantSecure && (isJust $ maybeHeader "X-Via-Proxy" direct) = Just UnexpectedViaHeader
+      | otherwise = Nothing
+    unexpectedViaHeader
+      | not wantSecure && (isNothing $ maybeHeader "X-Via-Proxy" proxied) = Just MissingViaHeader
+      | otherwise = Nothing
+    mismatchedHeader name
+      | x' /= y' = Just $ HeaderMismatch name x' y'
+      | otherwise = Nothing
+        where x' = maybeHeader name direct
+              y' = maybeHeader name proxied
diff --git a/test/Test/TestRequests.hs b/test/Test/TestRequests.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/TestRequests.hs
@@ -0,0 +1,101 @@
+{-# LANGUAGE NamedFieldPuns    #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Test.TestRequests
+    ( RequestBuilder(..)
+    , buildRequest
+    , testRequests
+    , testGetRequests
+    , testPostRequests
+    , testNotProxiedRequests
+    ) where
+
+import qualified Data.ByteString       as BS
+import qualified Data.ByteString.Char8 as C8
+import           Data.Maybe            (fromMaybe, maybe)
+
+import           Data.Default          (Default (..))
+import           Network.HTTP.Client   (Request, RequestBody (..), host, method,
+                                        parseRequest, redirectCount,
+                                        requestBody)
+import           Network.HTTP.Types    (Method, methodGet, methodPost)
+
+data RequestBuilder
+  = RequestBuilder
+  { rbMethod :: !Method
+  , rbSecure :: !Bool
+  , rbPath   :: !String
+  , rbBody   :: !(Maybe RequestBody)
+  , rbHost   :: !BS.ByteString
+  , rbPort   :: !(Maybe Int)
+  }
+
+instance Default RequestBuilder where
+  def = RequestBuilder
+    { rbMethod = methodGet
+    , rbSecure = False
+    , rbPath = "/"
+    , rbBody = Nothing
+    , rbHost = "httpbin.org"
+    , rbPort = Nothing
+    }
+
+testRequests :: [(String, RequestBuilder -> RequestBuilder)]
+testRequests = testGetRequests <> testPostRequests
+
+testGetRequests :: [(String, RequestBuilder -> RequestBuilder)]
+testGetRequests =
+  [ ("GET"
+    , (\builder -> builder {rbPath = "/get"}))
+  , ("GET (with a query)"
+    , (\builder -> builder {rbPath = "/get?a=10&b=whatever"}))
+  , ("GET (with a body)"
+    , (\builder -> builder
+        { rbPath = "/get"
+        , rbBody = Just $ RequestBodyBS "Hello httpbin!"
+        }))
+  , ("GET (forbidden resource)"
+    , (\builder -> builder {rbPath = "/status/403"}))
+  , ("GET (missing resource)"
+    , (\builder -> builder {rbPath = "/status/404"}))
+  ]
+
+testNotProxiedRequests :: [(String, RequestBuilder -> RequestBuilder)]
+testNotProxiedRequests =
+  [ ("GET (funny resource - differs on proxy)"
+    , (\builder -> builder {rbPath = "/status/418"}))
+  ]
+
+testPostRequests :: [(String, RequestBuilder -> RequestBuilder)]
+testPostRequests =
+  [ ("POST"
+    , (\builder -> builder {rbMethod = methodPost, rbPath = "/post"}))
+  , ("POST (with a query)"
+    , (\builder -> builder {rbMethod = methodPost, rbPath = "/post?a=10&b=whatever"}))
+  , ("POST (with a body)"
+    , (\builder -> builder
+        { rbPath = "/post"
+        , rbBody = Just $ RequestBodyBS "Hello httpbin!"
+        , rbMethod = methodPost
+        }))
+  , ("POST (forbidden resource)"
+    , (\builder -> builder {rbMethod = methodPost, rbPath = "/status/403"}))
+  , ("POST (missing resource)"
+    , (\builder -> builder {rbMethod = methodPost, rbPath = "/status/404"}))
+  ]
+
+-- | Simplifies creation of the requests used in testing
+buildRequest :: RequestBuilder -> IO Request
+buildRequest RequestBuilder { rbMethod, rbSecure, rbPath , rbBody, rbHost, rbPort } = do
+    let scheme
+          | rbSecure = "https"
+          | otherwise = "http"
+        portStr = maybe "" (\x -> ":" ++ show x) rbPort
+        url = scheme ++ "://" ++ (C8.unpack rbHost) ++ portStr ++ rbPath
+    req <- parseRequest url
+    return $ req
+        { method = rbMethod
+        , requestBody = fromMaybe (requestBody req) rbBody
+        , host = rbHost
+        , redirectCount = 0
+        }
diff --git a/test/Test/WithExtras.hs b/test/Test/WithExtras.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/WithExtras.hs
@@ -0,0 +1,85 @@
+module Test.WithExtras
+  ( defaultTlsSettings
+  , testWithTLSApplication
+  , testWithTLSApplicationSettings
+  , withTLSApplication
+  , withTLSApplicationSettings
+  ) where
+
+import           Control.Concurrent
+import           Control.Concurrent.Async
+import           Control.Exception
+import           Control.Monad                     (when)
+
+import           Network.Socket                    (Socket, close)
+import           Network.Wai                       (Application)
+import           Network.Wai.Handler.Warp.Internal (settingsBeforeMainLoop)
+
+import           Network.Wai.Handler.Warp          (Port, Settings,
+                                                    defaultShouldDisplayException,
+                                                    defaultSettings,
+                                                    openFreePort)
+import           Network.Wai.Handler.WarpTLS       (TLSSettings,
+                                                    runTLSSocket, tlsSettings)
+
+-- | The settings used in the integration tests
+defaultTlsSettings :: TLSSettings
+defaultTlsSettings = tlsSettings "test/certificate.pem" "test/key.pem"
+
+-- | Runs the given 'Application' on a free port. Passes the port to the given
+-- operation and executes it, while the 'Application' is running. Shuts down the
+-- server before returning.
+withTLSApplication :: TLSSettings -> IO Application -> (Port -> IO a) -> IO a
+withTLSApplication = withTLSApplicationSettings defaultSettings
+
+-- | 'withTLSApplication' with given 'Settings'. This will ignore the port value
+-- set by 'setPort' in 'Settings'.
+withTLSApplicationSettings :: Settings -> TLSSettings -> IO Application -> (Port -> IO a) -> IO a
+withTLSApplicationSettings settings' tlsSettings' mkApp action = do
+  app <- mkApp
+  withFreePort $ \ (port, sock) -> do
+    started <- mkWaiter
+    let settings =
+          settings' {
+            settingsBeforeMainLoop
+              = notify started () >> settingsBeforeMainLoop settings'
+          }
+    result <- race
+      (runTLSSocket tlsSettings' settings sock app)
+      (waitFor started >> action port)
+    case result of
+      Left () -> throwIO $ ErrorCall "Unexpected: runSettingsSocket exited"
+      Right x -> return x
+
+testWithTLSApplication :: TLSSettings -> IO Application -> (Port -> IO a) -> IO a
+testWithTLSApplication = testWithTLSApplicationSettings defaultSettings
+
+testWithTLSApplicationSettings :: Settings -> TLSSettings -> IO Application -> (Port -> IO a) -> IO a
+testWithTLSApplicationSettings settings tlsSettings' mkApp action = do
+  callingThread <- myThreadId
+  app <- mkApp
+  let wrappedApp request respond =
+        app request respond `catch` \ e -> do
+          when
+            (defaultShouldDisplayException e)
+            (throwTo callingThread e)
+          throwIO e
+  withTLSApplicationSettings settings tlsSettings' (return wrappedApp) action
+
+data Waiter a
+  = Waiter {
+    notify  :: a -> IO (),
+    waitFor :: IO a
+  }
+
+mkWaiter :: IO (Waiter a)
+mkWaiter = do
+  mvar <- newEmptyMVar
+  return Waiter {
+    notify = putMVar mvar,
+    waitFor = readMVar mvar
+  }
+
+-- | Like 'openFreePort' but closes the socket before exiting.
+withFreePort :: ((Port, Socket) -> IO a) -> IO a
+withFreePort = bracket openFreePort (close . snd)
diff --git a/wai-middleware-delegate.cabal b/wai-middleware-delegate.cabal
new file mode 100644
--- /dev/null
+++ b/wai-middleware-delegate.cabal
@@ -0,0 +1,77 @@
+name:               wai-middleware-delegate
+version:            0.1.0.0
+synopsis:           WAI middleware that delegates handling of requests.
+description:        WAI middleware to intercept requests that match a predicate and
+                    respond to them using other WAI Applications or proxied hosts. [WAI]
+                    <http://hackage.haskell.org/package/wai>
+license:            BSD3
+license-file:       LICENSE
+extra-source-files: README.md
+author:             Tim Emiola
+maintainer:         tim.emiola@gmail.com
+category:           Web
+homepage:           https://github.com/adetokunbo/wai-middleware-delegate
+bug-reports:        https://github.com/adetokunbo/wai-middleware-delegate/issues
+build-type:         Simple
+extra-source-files: ChangeLog.md
+cabal-version:      >= 2.0
+
+source-repository head
+  type:     git
+  location: https://github.com/adetokunbo/wai-middleware-delegate.git
+
+library
+  exposed-modules:  Network.Wai.Middleware.Delegate
+  hs-source-dirs:   src
+  build-depends:    base              >= 4.10 && < 5
+                  , async             >= 2.2.1 && < 2.3
+                  , blaze-builder     >= 0.4.1.0 && < 0.5
+                  , bytestring        >= 0.10.8.2 && < 0.11
+                  , case-insensitive  >= 1.2.0.11 && < 1.3
+                  , conduit           >= 1.3.0.3 && < 1.4
+                  , conduit-extra     >= 1.3.0 && < 1.4
+                  , data-default      >= 0.7.1.1 && < 0.8
+                  , http-client       >= 0.5.13.1 && < 0.6
+                  , http-conduit      >= 2.3.2 && < 2.4
+                  , http-types        >= 0.12.1 && < 0.12.2
+                  , streaming-commons >= 0.2.1.0 && < 0.2.2
+                  , text              >= 1.2.3.0 && < 1.3
+                  , wai               >= 3.2 && < 3.3
+                  , wai-conduit       >= 3.0.0.4 && < 3.1
+  default-language: Haskell2010
+
+test-suite integration-test
+  type:             exitcode-stdio-1.0
+  main-is:          IntegrationTest.hs
+  other-modules:    Test.Fetch
+                    Test.HttpReply
+                    Test.TestRequests
+                    Test.WithExtras
+  hs-source-dirs:   test
+  build-depends:    base
+                  , async
+                  , blaze-builder
+                  , bytestring
+                  , bytestring-lexing
+                  , case-insensitive
+                  , conduit
+                  , conduit-extra
+                  , connection              >= 0.2
+                  , data-default
+                  , hspec                   >= 2.1
+                  , http-client
+                  , http-client-tls
+                  , http-conduit
+                  , http-types
+                  , network
+                  , random                  >= 1.1
+                  , resourcet
+                  , text
+                  , vault
+                  , wai
+                  , wai-conduit
+                  , wai-middleware-delegate
+                  , warp
+                  , warp-tls
+  default-language: Haskell2010
+  ghc-options:      -Wall -fwarn-tabs -threaded
