{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} module Main where import System.IO import Control.Monad (when) import Data.Default (Default (..)) import Data.Foldable (for_) 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 System.Environment (lookupEnv) import Network.Wai.Middleware.Delegate (ProxySettings (..), delegateToProxy) import Test.Fetch (fetch) import Test.Hspec import Test.HttpReply import Test.TestRequests (RequestBuilder (..), buildRequest, testNotProxiedRequests, testOverRedirectedRequests, testRequests) import Test.WithExtras (defaultTlsSettings, testWithTLSApplication) defaultTestSettings :: ProxySettings defaultTestSettings = def { proxyHost = "httpbin.org", proxyTimeout = 2 } redirectTestSettings :: ProxySettings redirectTestSettings = defaultTestSettings { proxyRedirectCount = 2 } main :: IO () main = do hSetBuffering stdin NoBuffering hSetBuffering stdout NoBuffering dumpDebug' <- lookupEnv "DEBUG" let dumpDebug = maybe False (const True) dumpDebug' hspec $ do -- TOOD: reenable when the public /redirect link on http-bin is working ok -- insecureRedirectTest dumpDebug insecureProxyTest dumpDebug insecureNotProxiedTest dumpDebug secureProxyTest dumpDebug secureNotProxiedTest 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) testWithInsecureRedirects :: (Port -> IO ()) -> IO () testWithInsecureRedirects = testWithApplication (defaultTestDelegate redirectTestSettings) testWithSecureProxy :: (Port -> IO ()) -> IO () testWithSecureProxy withPort = do tls <- defaultTlsSettings testWithTLSApplication tls (defaultTestDelegate defaultTestSettings) withPort 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 insecureNotProxiedTest :: Bool -> Spec insecureNotProxiedTest debug = let scheme = "HTTP" desc = "Proxy on " ++ scheme ++ " should fail" assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug in around testWithInsecureProxy $ describe desc $ do for_ testNotProxiedRequests $ \(title, modifier) -> it (scheme ++ " " ++ title) $ \port -> assertNeq port $ modifier def insecureRedirectTest :: Bool -> Spec insecureRedirectTest debug = let scheme = "HTTP" desc = "Proxy over " ++ scheme ++ " with too many redirects differs" assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug in around testWithInsecureRedirects $ describe desc $ do for_ testOverRedirectedRequests $ \(title, modifier) -> it (scheme ++ " " ++ title) $ \port -> assertNeq port $ modifier def insecureProxyTest :: Bool -> Spec insecureProxyTest debug = let scheme = "HTTP" desc = "Simple " ++ scheme ++ " proxying:" assertEq = onDirectAndProxy assertHttpRepliesAreEq debug in around testWithInsecureProxy $ describe desc $ do for_ testRequests $ \(title, modifier) -> it (scheme ++ " " ++ title) $ \port -> assertEq port $ modifier def secureNotProxiedTest :: Bool -> Spec secureNotProxiedTest debug = let scheme = "HTTPS" desc = "Proxy on " ++ scheme ++ " should fail" assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug def' = def { rbSecure = True } in around testWithSecureProxy $ describe desc $ do 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 def' = def { rbSecure = True } in around testWithSecureProxy $ describe desc $ do for_ testRequests $ \(title, modifier) -> it (scheme ++ " " ++ title) $ \port -> assertEq port $ modifier def'