on master help sign in
add withApplication and openFreePort
commita1ee04082e515e34364115bc9102bd58d5aafa1e authorSönke Hahn <soenkehahn@gmail.com> dateWed 2 Mar 2016, 13:51 (11 years ago) parent0981091
4 files changed+122
warp/Network/Wai/Handler/Warp.hs+3 warp/Network/Wai/Handler/Warp/WithApplication.hs+87 warp/test/WithApplicationSpec.hs+29 warp/warp.cabal+3
warp/Network/Wai/Handler/Warp.hs+3
@@ -90,6 +90,8 @@ module Network.Wai.Handler.Warp (
9090 , pauseTimeout
9191 , FileInfo(..)
9292 , getFileInfo
93+ , withApplication
94+ , openFreePort
9395 -- * Version
9496 , warpVersion
9597 ) where
@@ -109,6 +111,7 @@ import Network.Wai.Handler.Warp.Run
109111import Network.Wai.Handler.Warp.Settings
110112import Network.Wai.Handler.Warp.Timeout
111113import Network.Wai.Handler.Warp.Types hiding (getFileInfo)
114+import Network.Wai.Handler.Warp.WithApplication
112115
113116-- | Port to listen on. Default value: 3000
114117--
warp/Network/Wai/Handler/Warp/WithApplication.hsnew file+87
@@ -0,0 +1,87 @@
1+
2+module Network.Wai.Handler.Warp.WithApplication (
3+ withApplication,
4+ openFreePort,
5+) where
6+
7+import Control.Concurrent
8+import Control.Exception
9+import Network.Socket
10+import Network.Wai
11+import Network.Wai.Handler.Warp.Run
12+import Network.Wai.Handler.Warp.Settings
13+import Network.Wai.Handler.Warp.Types
14+
15+data App
16+ = App {
17+ appThread :: ThreadId,
18+ appKilled :: Waiter (),
19+ appExceptionMVar :: MVar (Maybe SomeException),
20+ appPort :: Int
21+ }
22+
23+-- | Runs the given 'Application' on a free port. Passes the port to the given
24+-- operation and executes it, while the 'Application' is running. Shuts down the
25+-- server before returning.
26+--
27+-- Handy for e.g. testing 'Application's over a real network port.
28+withApplication :: IO Application -> (Port -> IO a) -> IO a
29+withApplication mkApp action = do
30+ app <- mkApp
31+ bracket (acquire app) free (\ runningApp -> action (appPort runningApp))
32+ where
33+ acquire :: Application -> IO App
34+ acquire app = do
35+ start <- mkWaiter
36+ killed <- mkWaiter
37+ exceptionMVar_ <- newMVar Nothing
38+ thread <- forkIO $ do
39+ (port, sock) <- openFreePort
40+ let settings =
41+ defaultSettings{
42+ settingsBeforeMainLoop = notify start port
43+ }
44+ runSettingsSocket settings sock (handleApp exceptionMVar_ app)
45+ `finally` notify killed ()
46+ port <- waitFor start
47+ return $ App thread killed exceptionMVar_ port
48+
49+ free :: App -> IO ()
50+ free runningApp = do
51+ killThread $ appThread runningApp
52+ waitFor $ appKilled runningApp
53+ exception <- readMVar (appExceptionMVar runningApp)
54+ case exception of
55+ Nothing -> return ()
56+ Just e -> throwIO e
57+
58+handleApp :: MVar (Maybe SomeException) -> Application -> Application
59+handleApp mvar app request respond = do
60+ catch (app request respond) $ \ e -> do
61+ modifyMVar_ mvar $ \ _ ->
62+ return (Just e)
63+ throwIO e
64+
65+data Waiter a
66+ = Waiter {
67+ notify :: a -> IO (),
68+ waitFor :: IO a
69+ }
70+
71+mkWaiter :: IO (Waiter a)
72+mkWaiter = do
73+ mvar <- newEmptyMVar
74+ return $ Waiter {
75+ notify = putMVar mvar,
76+ waitFor = readMVar mvar
77+ }
78+
79+-- | Opens a socket on a free port and returns both port and socket.
80+openFreePort :: IO (Port, Socket)
81+openFreePort = do
82+ s <- socket AF_INET Stream defaultProtocol
83+ localhost <- inet_addr "127.0.0.1"
84+ bind s (SockAddrInet aNY_PORT localhost)
85+ listen s 1
86+ port <- socketPort s
87+ return (fromIntegral port, s)
warp/test/WithApplicationSpec.hsnew file+29
@@ -0,0 +1,29 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module WithApplicationSpec where
4+
5+import Control.Exception
6+import Network.HTTP.Types
7+import Network.Wai
8+import System.IO
9+import System.IO.Silently
10+import System.Process
11+import Test.Hspec
12+
13+import Network.Wai.Handler.Warp.WithApplication
14+
15+spec :: Spec
16+spec = do
17+ describe "withApplication" $ do
18+ it "runs a wai Application while executing the given action" $ do
19+ let mkApp = return $ \ _request respond -> respond $ responseLBS ok200 [] "foo"
20+ withApplication mkApp $ \ port -> do
21+ output <- readProcess "curl" ["-s", "localhost:" ++ show port] ""
22+ output `shouldBe` "foo"
23+
24+ it "propagates exceptions from the server to the executing thread" $
25+ hSilence [stderr] $ do
26+ let mkApp = return $ \ _request _respond -> throwIO $ ErrorCall "foo"
27+ (withApplication mkApp $ \ port -> do
28+ readProcess "curl" ["-s", "localhost:" ++ show port] "")
29+ `shouldThrow` (errorCall "foo")
warp/warp.cabal+3
@@ -93,6 +93,7 @@ Library
9393 Network.Wai.Handler.Warp.Timeout
9494 Network.Wai.Handler.Warp.Types
9595 Network.Wai.Handler.Warp.Windows
96+ Network.Wai.Handler.Warp.WithApplication
9697 Paths_warp
9798 Ghc-Options: -Wall
9899
@@ -129,6 +130,7 @@ Test-Suite spec
129130 ResponseSpec
130131 RunSpec
131132 SendFileSpec
133+ WithApplicationSpec
132134 HTTP
133135 Hs-Source-Dirs: test, .
134136 Type: exitcode-stdio-1.0
@@ -157,6 +159,7 @@ Test-Suite spec
157159 , time
158160 , text
159161 , streaming-commons >= 0.1.10
162+ , silently
160163 , async
161164 , vault
162165 , stm >= 2.3