on 25ffdaa help sign in
86 files changed+2784−1748
since splitsnapshots
unifiedsplit
.azure/azure-linux-template.yml+28−11 .azure/azure-osx-template.yml+12−3 .azure/azure-windows-template.yml+3−5 .gitignore−1 auto-update/ChangeLog.md−10 auto-update/Control/Debounce.hs+53−19 auto-update/Control/Debounce/Internal.hs−98 auto-update/auto-update.cabal+9−13 auto-update/test/Control/AutoUpdateSpec.hs+25−26 auto-update/test/Control/DebounceSpec.hs−120 auto-update/test/Control/ReaperSpec.hs+27−29 stack-lts-11.yaml+6−6 stack-lts-12.yaml+6−7 stack-lts-6.yaml+31 stack-lts-9.yaml+27 stack-nightly.yaml−20 stack.yaml+7−7 time-manager/LICENSE−20 time-manager/time-manager.cabal−19 wai-app-static/ChangeLog.md−10 wai-app-static/Network/Wai/Application/Static.hs+8−10 wai-app-static/WaiAppStatic/CmdLine.hs−1 wai-app-static/WaiAppStatic/Listing.hs+1−1 wai-app-static/WaiAppStatic/Storage/Embedded/TH.hs+1 wai-app-static/WaiAppStatic/Types.hs−2 wai-app-static/test/WaiAppStaticTest.hs+9−9 wai-app-static/wai-app-static.cabal+3−3 wai-conduit/Network/Wai/Conduit.hs+6−5 wai-extra/ChangeLog.md−27 wai-extra/Network/Wai/EventSource.hs+1−19 wai-extra/Network/Wai/EventSource/EventStream.hs+1−1 wai-extra/Network/Wai/Middleware/Gzip.hs−3 wai-extra/Network/Wai/Middleware/RequestLogger.hs+10−60 wai-extra/Network/Wai/Middleware/RequestLogger/JSON.hs+1−34 wai-extra/Network/Wai/Middleware/Rewrite.hs+1−1 wai-extra/Network/Wai/Parse.hs+4−11 wai-extra/Network/Wai/Test.hs+14−22 wai-extra/example/Main.hs−11 wai-extra/test/Network/Wai/ParseSpec.hs+5−14 wai-extra/test/Network/Wai/TestSpec.hs+11−41 wai-extra/wai-extra.cabal+4−6 wai-http2-extra/Network/Wai/Middleware/Push/Referer.hs+2−4 wai-http2-extra/wai-http2-extra.cabal+2−9 wai-websockets/server.lhs+1−7 wai-websockets/wai-websockets.cabal−1 wai/ChangeLog.md−6 wai/Network/Wai.hs−1 wai/wai.cabal+2−1 warp-tls/ChangeLog.md+1−44 warp-tls/Network/Wai/Handler/WarpTLS.hs+15−27 warp-tls/warp-tls.cabal+6−9 warp/ChangeLog.md−72 warp/Network/Wai/Handler/Warp.hs+2−52 warp/Network/Wai/Handler/Warp/FdCache.hs+22−18 warp/Network/Wai/Handler/Warp/File.hs+4−7 warp/Network/Wai/Handler/Warp/FileInfoCache.hs+22−19 warp/Network/Wai/Handler/Warp/HTTP2.hs+54−70 warp/Network/Wai/Handler/Warp/HTTP2/EncodeFrame.hs+34 warp/Network/Wai/Handler/Warp/HTTP2/File.hs+150−23 warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hs+130 warp/Network/Wai/Handler/Warp/HTTP2/Manager.hs+95 warp/Network/Wai/Handler/Warp/HTTP2/PushPromise.hs−36 warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+398 warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+17−17 warp/Network/Wai/Handler/Warp/HTTP2/Response.hs−127 warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+536 warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+265−13 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+328 warp/Network/Wai/Handler/Warp/HashMap.hs+15−24 warp/Network/Wai/Handler/Warp/Header.hs−2 warp/Network/Wai/Handler/Warp/Internal.hs+2−3 warp/Network/Wai/Handler/Warp/MultiMap.hs+44−55 warp/Network/Wai/Handler/Warp/Recv.hs+4−25 warp/Network/Wai/Handler/Warp/Request.hs+28−26 warp/Network/Wai/Handler/Warp/Response.hs+19−22 warp/Network/Wai/Handler/Warp/Run.hs+64−108 warp/Network/Wai/Handler/Warp/Settings.hs+10−54 time-manager/System/TimeManager.hs → warp/Network/Wai/Handler/Warp/Timeout.hs+3−2 warp/Network/Wai/Handler/Warp/Types.hs+30−25 warp/test/FdCacheSpec.hs+1−1 warp/test/FileSpec.hs+1−1 warp/test/RequestSpec.hs+7−11 warp/test/ResponseHeaderSpec.hs+35−2 warp/test/ResponseSpec.hs+15−11 warp/test/RunSpec.hs+62−56 warp/warp.cabal+44−52
.azure/azure-linux-template.yml+28−11
@@ -1,10 +1,19 @@
11jobs:
22- job: ${{ parameters.name }}
3− timeoutInMinutes: 180
43 pool:
54 vmImage: ${{ parameters.vmImage }}
65 strategy:
76 matrix:
7+ stack-def:
8+ BUILD: stack
9+ stack-lts-6:
10+ BUILD: stack
11+ STACK_YAML: stack-lts-6.yaml
12+ ARGS: "--resolver lts-6"
13+ stack-lts-9:
14+ BUILD: stack
15+ STACK_YAML: stack-lts-9.yaml
16+ ARGS: "--resolver lts-9"
817 stack-lts-11:
918 BUILD: stack
1019 STACK_YAML: stack-lts-11.yaml
@@ -13,13 +22,17 @@ jobs:
1322 BUILD: stack
1423 STACK_YAML: stack-lts-12.yaml
1524 ARGS: "--resolver lts-12"
16− stack-lts-14:
25+ stack-lts-13:
1726 BUILD: stack
18− ARGS: "--resolver lts-14"
19− stack-nightly:
20− BUILD: stack
21− STACK_YAML: stack-nightly.yaml
22− ARGS: "--resolver nightly"
27+ ARGS: "--resolver lts-13"
28+ cabal-7.10.3:
29+ BUILD: cabal
30+ GHCVER: 7.10.3
31+ CABALVER: 1.22
32+ cabal-8.0.2:
33+ BUILD: cabal
34+ GHCVER: 8.0.2
35+ CABALVER: 1.24
2336 cabal-8.2.2:
2437 BUILD: cabal
2538 GHCVER: 8.4.4
@@ -28,9 +41,9 @@ jobs:
2841 BUILD: cabal
2942 GHCVER: 8.4.4
3043 CABALVER: 2.2
31− cabal-8.6.5:
44+ cabal-8.6.4:
3245 BUILD: cabal
33− GHCVER: 8.6.5
46+ GHCVER: 8.6.4
3447 CABALVER: 2.4
3548 maxParallel: 6
3649 steps:
@@ -70,7 +83,7 @@ jobs:
7083 for dir in $PACKAGES
7184 do
7285 cd $dir
73− cabal check || [ "$CABALVER" == "1.22" ] # https://github.com/yesodweb/wai/pull/754#issuecomment-504866279
86+ cabal check || [ "$CABALVER" == "1.16" ]
7487 cabal sdist
7588 PKGVER=$(cabal info . | awk '{print $2;exit}')
7689 SRC_TGZ=$PKGVER.tar.gz
@@ -79,7 +92,11 @@ jobs:
7992 cd "$PKGVER"
8093 cabal configure --enable-tests --ghc-options -O0
8194 cabal build
82− cabal test --show-details=streaming
95+ if [ "$CABALVER" = "1.16" ] || [ "$CABALVER" = "1.18" ]; then
96+ cabal test
97+ else
98+ cabal test --show-details=streaming
99+ fi
83100 cd $ORIGDIR
84101 done
85102 ;;
.azure/azure-osx-template.yml+12−3
@@ -1,10 +1,19 @@
11jobs:
22- job: ${{ parameters.name }}
3− timeoutInMinutes: 180
43 pool:
54 vmImage: ${{ parameters.vmImage }}
65 strategy:
76 matrix:
7+ stack-def:
8+ BUILD: stack
9+ stack-lts-6:
10+ BUILD: stack
11+ STACK_YAML: stack-lts-6.yaml
12+ ARGS: "--resolver lts-6"
13+ stack-lts-9:
14+ BUILD: stack
15+ STACK_YAML: stack-lts-9.yaml
16+ ARGS: "--resolver lts-9"
817 stack-lts-11:
918 BUILD: stack
1019 STACK_YAML: stack-lts-11.yaml
@@ -13,9 +22,9 @@ jobs:
1322 BUILD: stack
1423 STACK_YAML: stack-lts-12.yaml
1524 ARGS: "--resolver lts-12"
16− stack-lts-14:
25+ stack-lts-13:
1726 BUILD: stack
18− ARGS: "--resolver lts-14"
27+ ARGS: "--resolver lts-13"
1928 maxParallel: 3
2029 steps:
2130 - script: |
.azure/azure-windows-template.yml+3−5
@@ -1,13 +1,12 @@
11jobs:
22- job: ${{ parameters.name }}
3− timeoutInMinutes: 180
43 pool:
54 vmImage: ${{ parameters.vmImage }}
65 strategy:
76 matrix:
8− stack-lts-14:
7+ stack-lts-13:
98 BUILD: stack
10− ARGS: "--resolver lts-14"
9+ ARGS: "--resolver lts-13"
1110 maxParallel: 3
1211 steps:
1312 - bash: |
@@ -16,8 +15,7 @@ jobs:
1615 unzip -o /usr/bin/stack.zip -d /usr/bin/
1716 stack setup $ARGS > /dev/null
1817 stack --install-ghc $ARGS test --bench --only-dependencies
19− stack $ARGS test --bench --no-run-benchmarks --no-run-tests
20− stack $ARGS test --bench --no-run-benchmarks --jobs 1 # non-parallel test execution
18+ stack $ARGS test --bench --no-run-benchmarks
2119 env:
2220 OS_NAME: ${{ parameters.os }}
2321 displayName: 'Installation ${{parameters.os}} & Test'
.gitignore−1
@@ -9,4 +9,3 @@ tarballs/
99cabal.sandbox.config
1010.stack-work/
1111*~
12−stack*.yaml.lock
auto-update/ChangeLog.md−10
@@ -1,15 +1,5 @@
11# ChangeLog for auto-update
22
3−## 0.1.6
4−
5−* Add control of activation on leading vs. trailing edges for Control.Debounce
6− [#756](https://github.com/yesodweb/wai/pull/756)
7−
8−## 0.1.5
9−
10−* Using the Strict and StrictData language extensions for GHC >8.
11− [#752](https://github.com/yesodweb/wai/pull/752)
12−
133## 0.1.4.1
144
155* [#693](https://github.com/yesodweb/wai/pull/693):
auto-update/Control/Debounce.hs+53−19
@@ -11,7 +11,6 @@
1111-- printString <- 'mkDebounce' 'defaultDebounceSettings'
1212-- { 'debounceAction' = putStrLn "Running action"
1313-- , 'debounceFreq' = 5000000 -- 5 seconds
14−-- , 'debounceEdge' = 'DI.trailingEdge' -- Trigger on the trailing edge
1514-- }
1615-- @
1716--
@@ -26,35 +25,70 @@
2625-- @since 0.1.2
2726module Control.Debounce
2827 ( -- * Type
29− DI.DebounceSettings
28+ DebounceSettings
3029 , defaultDebounceSettings
3130 -- * Accessors
32− , DI.debounceFreq
33− , DI.debounceAction
34− , DI.debounceEdge
35− , DI.leadingEdge
36− , DI.trailingEdge
31+ , debounceFreq
32+ , debounceAction
3733 -- * Creation
3834 , mkDebounce
3935 ) where
4036
41−import Control.Concurrent (newEmptyMVar, threadDelay)
42−import qualified Control.Debounce.Internal as DI
37+import Control.Concurrent (forkIO, threadDelay)
38+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, tryPutMVar)
39+import Control.Exception (SomeException, handle, mask_)
40+import Control.Monad (forever, void)
41+
42+-- | Settings to control how debouncing should work.
43+--
44+-- This should be constructed using 'defaultDebounceSettings' and record
45+-- update syntax, e.g.:
46+--
47+-- @
48+-- let settings = 'defaultDebounceSettings' { 'debounceAction' = flushLog }
49+-- @
50+--
51+-- @since 0.1.2
52+data DebounceSettings = DebounceSettings
53+ { debounceFreq :: Int
54+ -- ^ Microseconds lag required between subsequence calls to the debounced
55+ -- action.
56+ --
57+ -- Default: 1 second (1000000)
58+ --
59+ -- @since 0.1.2
60+ , debounceAction :: IO ()
61+ -- ^ Action to be performed.
62+ --
63+ -- Note: all exceptions thrown by this action will be silently discarded.
64+ --
65+ -- Default: does nothing.
66+ --
67+ -- @since 0.1.2
68+ }
4369
4470-- | Default value for creating a 'DebounceSettings'.
4571--
4672-- @since 0.1.2
47−defaultDebounceSettings :: DI.DebounceSettings
48−defaultDebounceSettings = DI.DebounceSettings
49− { DI.debounceFreq = 1000000
50− , DI.debounceAction = return ()
51− , DI.debounceEdge = DI.leadingEdge
73+defaultDebounceSettings :: DebounceSettings
74+defaultDebounceSettings = DebounceSettings
75+ { debounceFreq = 1000000
76+ , debounceAction = return ()
5277 }
5378
54−-- | Generate an action which will trigger the debounced action to be performed.
79+-- | Generate an action which will trigger the debounced action to be
80+-- performed. The action will either be performed immediately, or after the
81+-- current cooldown period has expired.
5582--
5683-- @since 0.1.2
57−mkDebounce :: DI.DebounceSettings -> IO (IO ())
58−mkDebounce settings = do
59− baton <- newEmptyMVar
60− DI.mkDebounceInternal baton threadDelay settings
84+mkDebounce :: DebounceSettings -> IO (IO ())
85+mkDebounce (DebounceSettings freq action) = do
86+ baton <- newEmptyMVar
87+ mask_ $ void $ forkIO $ forever $ do
88+ takeMVar baton
89+ ignoreExc action
90+ threadDelay freq
91+ return $ void $ tryPutMVar baton ()
92+
93+ignoreExc :: IO () -> IO ()
94+ignoreExc = handle $ \(_ :: SomeException) -> return ()
auto-update/Control/Debounce/Internal.hsdeleted−98
@@ -1,98 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−
3−-- | Unstable API which exposes internals for testing.
4−module Control.Debounce.Internal (
5− DebounceSettings(..)
6− , DebounceEdge(..)
7− , leadingEdge
8− , trailingEdge
9− , mkDebounceInternal
10− ) where
11−
12−import Control.Concurrent (forkIO)
13−import Control.Concurrent.MVar (takeMVar, tryPutMVar, tryTakeMVar, MVar)
14−import Control.Exception (SomeException, handle, mask_)
15−import Control.Monad (forever, void)
16−
17−-- | Settings to control how debouncing should work.
18−--
19−-- This should be constructed using 'defaultDebounceSettings' and record
20−-- update syntax, e.g.:
21−--
22−-- @
23−-- let settings = 'defaultDebounceSettings' { 'debounceAction' = flushLog }
24−-- @
25−--
26−-- @since 0.1.2
27−data DebounceSettings = DebounceSettings
28− { debounceFreq :: Int
29− -- ^ Length of the debounce timeout period in microseconds.
30− --
31− -- Default: 1 second (1000000)
32− --
33− -- @since 0.1.2
34− , debounceAction :: IO ()
35− -- ^ Action to be performed.
36− --
37− -- Note: all exceptions thrown by this action will be silently discarded.
38− --
39− -- Default: does nothing.
40− --
41− -- @since 0.1.2
42− , debounceEdge :: DebounceEdge
43− -- ^ Whether to perform the action on the leading edge or trailing edge of
44− -- the timeout.
45− --
46− -- Default: 'trailingEdge'.
47− --
48− -- @since 0.1.6
49− }
50−
51−-- | Setting to control whether the action happens at the leading and/or trailing
52−-- edge of the timeout.
53−--
54−-- @since 0.1.6
55−data DebounceEdge =
56− Leading
57− -- ^ Perform the action immediately, and then begin a cooldown period.
58− -- If the trigger happens again during the cooldown, wait until the end of the cooldown
59− -- and then perform the action again, then enter a new cooldown period.
60− | Trailing
61− -- ^ Start a cooldown period and perform the action when the period ends. If another trigger
62− -- happens during the cooldown, it has no effect.
63− deriving (Show, Eq)
64−
65−
66−-- | Perform the action immediately, and then begin a cooldown period.
67−-- If the trigger happens again during the cooldown, wait until the end of the cooldown
68−-- and then perform the action again, then enter a new cooldown period.
69−--
70−-- @since 0.1.6
71−leadingEdge :: DebounceEdge
72−leadingEdge = Leading
73−
74−-- | Start a cooldown period and perform the action when the period ends. If another trigger
75−-- happens during the cooldown, it has no effect.
76−--
77−-- @since 0.1.6
78−trailingEdge :: DebounceEdge
79−trailingEdge = Trailing
80−
81−mkDebounceInternal :: MVar () -> (Int -> IO ()) -> DebounceSettings -> IO (IO ())
82−mkDebounceInternal baton delayFn (DebounceSettings freq action edge) = do
83− mask_ $ void $ forkIO $ forever $ do
84− takeMVar baton
85− case edge of
86− Leading -> do
87− ignoreExc action
88− delayFn freq
89− Trailing -> do
90− delayFn freq
91− -- Empty the baton of any other activations during the interval
92− void $ tryTakeMVar baton
93− ignoreExc action
94−
95− return $ void $ tryPutMVar baton ()
96−
97−ignoreExc :: IO () -> IO ()
98−ignoreExc = handle $ \(_ :: SomeException) -> return ()
auto-update/auto-update.cabal+9−13
@@ -1,5 +1,5 @@
11name: auto-update
2−version: 0.1.6
2+version: 0.1.4.1
33synopsis: Efficiently run periodic, on-demand actions
44description: API docs and the README are available at <http://www.stackage.org/package/auto-update>.
55homepage: https://github.com/yesodweb/wai
@@ -17,22 +17,18 @@ library
1717 ghc-options: -Wall
1818 exposed-modules: Control.AutoUpdate
1919 Control.Debounce
20− Control.Debounce.Internal
2120 Control.Reaper
2221 other-modules: Control.AutoUpdate.Util
2322 build-depends: base >= 4 && < 5
2423 default-language: Haskell2010
25− if impl(ghc >= 8)
26− default-extensions: Strict StrictData
2724
2825-- Test suite is currently not robust enough, gives too many false negatives.
2926
30−test-suite spec
31− main-is: Spec.hs
32− other-modules: Control.AutoUpdateSpec
33− Control.DebounceSpec
34− Control.ReaperSpec
35− hs-source-dirs: test
36− type: exitcode-stdio-1.0
37− build-depends: base, auto-update, exceptions, hspec, retry, HUnit
38− default-language: Haskell2010
27+-- test-suite spec
28+-- main-is: Spec.hs
29+-- other-modules: Control.AutoUpdateSpec
30+-- Control.ReaperSpec
31+-- hs-source-dirs: test
32+-- type: exitcode-stdio-1.0
33+-- build-depends: base, auto-update, hspec
34+-- default-language: Haskell2010
auto-update/test/Control/AutoUpdateSpec.hs+25−26
@@ -1,35 +1,34 @@
11module Control.AutoUpdateSpec (spec) where
22
3−import Control.AutoUpdate
4−import Control.Concurrent (threadDelay)
5−import Control.Monad (replicateM_, forM_)
6−import Data.IORef
73import Test.Hspec
84import Test.Hspec.QuickCheck
5+import Data.IORef
6+import Control.Concurrent (threadDelay)
7+import Control.Monad (replicateM_, forM_)
8+import Control.AutoUpdate
99
1010spec :: Spec
11−spec = return ()
12− -- do
13− -- prop "incrementer" $ \st' -> do
14− -- let st = abs st' `mod` 10000
15− -- ref <- newIORef 0
16− -- next <- mkAutoUpdate defaultUpdateSettings
17− -- { updateAction = atomicModifyIORef ref $ \i ->
18− -- let i' = succ i in i' `seq` (i', i')
19− -- , updateSpawnThreshold = st
20− -- , updateFreq = 10000
21− -- }
11+spec = do
12+ prop "incrementer" $ \st' -> do
13+ let st = abs st' `mod` 10000
14+ ref <- newIORef 0
15+ next <- mkAutoUpdate defaultUpdateSettings
16+ { updateAction = atomicModifyIORef ref $ \i ->
17+ let i' = succ i in i' `seq` (i', i')
18+ , updateSpawnThreshold = st
19+ , updateFreq = 10000
20+ }
2221
23− -- forM_ [1..st + 1] $ \i -> do
24− -- j <- next
25− -- j `shouldBe` i
22+ forM_ [1..st + 1] $ \i -> do
23+ j <- next
24+ j `shouldBe` i
2625
27− -- replicateM_ 50 $ do
28− -- i <- next
29− -- i `shouldBe` st + 2
26+ replicateM_ 50 $ do
27+ i <- next
28+ i `shouldBe` st + 2
3029
31− -- threadDelay 60000
32− -- last1 <- readIORef ref
33− -- threadDelay 20000
34− -- last2 <- readIORef ref
35− -- last2 `shouldBe` last1
30+ threadDelay 60000
31+ last1 <- readIORef ref
32+ threadDelay 20000
33+ last2 <- readIORef ref
34+ last2 `shouldBe` last1
auto-update/test/Control/DebounceSpec.hsdeleted−120
@@ -1,120 +0,0 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−module Control.DebounceSpec (spec) where
3−
4−import Control.Concurrent
5−import Control.Debounce
6−import qualified Control.Debounce.Internal as DI
7−import Control.Monad
8−import Control.Monad.Catch
9−import Control.Retry
10−import Data.IORef
11−import Test.HUnit.Lang
12−import Test.Hspec
13−
14−spec :: Spec
15−spec = describe "mkDebounce" $ do
16− describe "Leading edge" $ do
17− it "works for a single event" $ do
18− (ref, debounced, baton, returnFromWait) <- getDebounce leadingEdge
19−
20− debounced
21− waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
22−
23− returnFromWait
24− pause
25− readIORef ref >>= (`shouldBe` 1)
26−
27− -- Try another round
28− debounced
29− waitUntil 5 $ readIORef ref >>= (`shouldBe` 2)
30−
31− returnFromWait
32− pause
33− readIORef ref >>= (`shouldBe` 2)
34−
35− it "works for multiple events" $ do
36− (ref, debounced, baton, returnFromWait) <- getDebounce leadingEdge
37−
38− debounced
39− waitForBatonToBeTaken baton
40− debounced
41− pause
42− waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
43−
44− returnFromWait
45− pause
46− readIORef ref >>= (`shouldBe` 2)
47−
48− describe "Trailing edge" $ do
49− it "works for a single event" $ do
50− (ref, debounced, baton, returnFromWait) <- getDebounce trailingEdge
51−
52− debounced
53− pause
54− waitUntil 5 $ readIORef ref >>= (`shouldBe` 0)
55−
56− returnFromWait
57− waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
58−
59− -- Try another round
60− debounced
61− pause
62− waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
63−
64− returnFromWait
65− waitUntil 5 $ readIORef ref >>= (`shouldBe` 2)
66−
67− it "works for multiple events" $ do
68− (ref, debounced, baton, returnFromWait) <- getDebounce trailingEdge
69−
70− debounced
71− waitForBatonToBeTaken baton
72− debounced
73− pause
74− waitUntil 5 $ readIORef ref >>= (`shouldBe` 0)
75−
76− returnFromWait
77− waitUntil 5 $ readIORef ref >>= (`shouldBe` 1)
78−
79−
80−-- | Make a controllable delay function
81−getWaitAction :: IO (p -> IO (), IO ())
82−getWaitAction = do
83− waitVar <- newEmptyMVar
84− let waitAction _ = takeMVar waitVar
85− let returnFromWait = putMVar waitVar ()
86− return (waitAction, returnFromWait)
87−
88−-- | Get a debounce system with access to the internals for testing
89−getDebounce :: DI.DebounceEdge -> IO (IORef Int, IO (), MVar (), IO ())
90−getDebounce edge = do
91− ref :: IORef Int <- newIORef 0
92− let action = modifyIORef ref (+ 1)
93−
94− (waitAction, returnFromWait) <- getWaitAction
95−
96− baton <- newEmptyMVar
97−
98− debounced <- DI.mkDebounceInternal baton waitAction defaultDebounceSettings {
99− debounceFreq = 5000000 -- unused
100− , debounceAction = action
101− , debounceEdge = edge
102− }
103−
104− return (ref, debounced, baton, returnFromWait)
105−
106−-- | Pause briefly (100ms)
107−pause :: IO ()
108−pause = threadDelay 100000
109−
110−waitForBatonToBeTaken :: MVar () -> IO ()
111−waitForBatonToBeTaken baton = waitUntil 5 $ tryReadMVar baton >>= (`shouldBe` Nothing)
112−
113−-- | Wait up to n seconds for an action to complete without throwing an HUnitFailure
114−waitUntil :: Int -> IO a -> IO ()
115−waitUntil n action = recovering policy [handler] (\_status -> void action)
116− where policy = constantDelay 1000 `mappend` limitRetries (n * 1000) -- 1ms * n * 1000 tries = n seconds
117− handler _status = Handler (\(HUnitFailure {}) -> return True)
118−
119−main :: IO ()
120−main = hspec spec
auto-update/test/Control/ReaperSpec.hs+27−29
@@ -1,39 +1,37 @@
11module Control.ReaperSpec (spec) where
22
3−import Control.Concurrent
43import Control.Reaper
5−import Data.IORef
4+import Control.Concurrent
65import Test.Hspec
76import Test.Hspec.QuickCheck
7+import Data.IORef
88
9+type Item = (Int, IORef Int)
910
10−spec :: Spec
11−spec = return ()
12−-- prop "works" $ \is -> do
13−-- reaper <- mkReaper defaultReaperSettings
14−-- { reaperAction = action
15−-- , reaperDelay = 1000
16−-- }
17−
18−-- let mkTestCase i = do
19−-- ref <- newIORef 0
20−-- let expected = (abs i `mod` 10) + 1
21−-- reaperAdd reaper (expected, ref)
22−-- return (expected, ref)
23−-- testCases <- mapM mkTestCase is
11+action = mkListAction $ \(i, ref) -> do
12+ modifyIORef ref succ
13+ return $ if i > 1
14+ then Just (pred i, ref)
15+ else Nothing
2416
25−-- let test (expected, ref) = do
26−-- actual <- readIORef ref
27−-- actual `shouldBe` (expected :: Int)
28−-- threadDelay 100000
29−-- mapM_ test testCases
30−-- [] <- reaperRead reaper
31−-- return ()
17+spec :: Spec
18+spec = prop "works" $ \is -> do
19+ reaper <- mkReaper defaultReaperSettings
20+ { reaperAction = action
21+ , reaperDelay = 1000
22+ }
3223
33−-- type Item = (Int, IORef Int)
24+ let mkTestCase i = do
25+ ref <- newIORef 0
26+ let expected = (abs i `mod` 10) + 1
27+ reaperAdd reaper (expected, ref)
28+ return (expected, ref)
29+ testCases <- mapM mkTestCase is
3430
35−-- action = mkListAction $ \(i, ref) -> do
36−-- modifyIORef ref succ
37−-- return $ if i > 1
38−-- then Just (pred i, ref)
39−-- else Nothing
31+ let test (expected, ref) = do
32+ actual <- readIORef ref
33+ actual `shouldBe` (expected :: Int)
34+ threadDelay 100000
35+ mapM_ test testCases
36+ [] <- reaperRead reaper
37+ return ()
stack-lts-11.yaml+6−6
@@ -9,19 +9,19 @@ packages:
99- ./wai-app-static
1010- ./wai-websockets
1111- ./wai-conduit
12−- ./time-manager
1312flags:
1413 wai-extra:
1514 build-example: true
1615extra-deps:
1716- streaming-commons-0.2.1.0@sha256:163af02944f374f316e35257c1afbffefd2c2852a85a151d1ea0ad22152cf28d,4990
18−- http2-2.0.0
19−- network-byte-order-0.1.1.0
20−- tls-1.5.3
21−- tls-session-manager-0.0.4
22−- x509-1.7.5
2317nix:
2418 enable: false
2519 packages:
2620 - fcgi
2721 - zlib
22+image:
23+ containers:
24+ - base: "fpco/pid1"
25+ name: "yesodweb/warp"
26+ executables:
27+ - warp
stack-lts-12.yaml+6−7
@@ -9,7 +9,6 @@ packages:
99- ./wai-app-static
1010- ./wai-websockets
1111- ./wai-conduit
12−- ./time-manager
1312flags:
1413 wai-extra:
1514 build-example: true
@@ -18,9 +17,9 @@ nix:
1817 packages:
1918 - fcgi
2019 - zlib
21−extra-deps:
22−- http2-2.0.0
23−- network-byte-order-0.1.1.0
24−- tls-1.5.3
25−- tls-session-manager-0.0.4
26−- x509-1.7.5
20+image:
21+ containers:
22+ - base: "fpco/pid1"
23+ name: "yesodweb/warp"
24+ executables:
25+ - warp
stack-lts-6.yamlnew file+31
@@ -0,0 +1,31 @@
1+resolver: lts-6.35
2+packages:
3+- ./auto-update
4+- ./mime-types
5+- ./wai
6+- ./wai-extra
7+- ./warp
8+- ./warp-tls
9+- ./wai-app-static
10+- ./wai-websockets
11+- ./wai-conduit
12+extra-deps:
13+- bsb-http-chunked-0.0.0.2@sha256:0571ef33ec5530104503d32eaf24f2d7c221f350e5fc475a6dd8e84311bc0ca1,1792
14+- streaming-commons-0.2.1.0@sha256:163af02944f374f316e35257c1afbffefd2c2852a85a151d1ea0ad22152cf28d,4990
15+- tls-session-manager-0.0.0.2@sha256:e3d3f8a026196df46746a19d3d7d31211b2fca3aefed9074e951b2dcc1b8833c,872
16+- wai-logger-2.3.2@sha256:7f5dbc72c0753bd3e0e73f8f542d9d159d1d70f8ff3a3ec16d0b74b500534fc6,1572
17+- gauge-0.2.3@sha256:ce41cb66ebf730a5050181db0338b1a4b3f76ac71331bf5070089bcd4352ff46,3777
18+- psqueues-0.2.7.0@sha256:189970837d0aae01f48cd2a8aac6ce314191939e118cc0488aee1fb12da1956a,4757
19+- basement-0.0.7@rev:0
20+- http-client-0.5.13.1@rev:0
21+nix:
22+ enable: false
23+ packages:
24+ - fcgi
25+ - zlib
26+image:
27+ containers:
28+ - base: "fpco/pid1"
29+ name: "yesodweb/warp"
30+ executables:
31+ - warp
stack-lts-9.yamlnew file+27
@@ -0,0 +1,27 @@
1+resolver: lts-9.21
2+packages:
3+- ./auto-update
4+- ./mime-types
5+- ./wai
6+- ./wai-extra
7+- ./warp
8+- ./warp-tls
9+- ./wai-app-static
10+- ./wai-websockets
11+- ./wai-conduit
12+extra-deps:
13+- bsb-http-chunked-0.0.0.2@sha256:0571ef33ec5530104503d32eaf24f2d7c221f350e5fc475a6dd8e84311bc0ca1,1792
14+- streaming-commons-0.2.1.0@sha256:163af02944f374f316e35257c1afbffefd2c2852a85a151d1ea0ad22152cf28d,4990
15+- wai-logger-2.3.2@sha256:7f5dbc72c0753bd3e0e73f8f542d9d159d1d70f8ff3a3ec16d0b74b500534fc6,1572
16+- http-client-0.5.13.1@rev:0
17+nix:
18+ enable: false
19+ packages:
20+ - fcgi
21+ - zlib
22+image:
23+ containers:
24+ - base: "fpco/pid1"
25+ name: "yesodweb/warp"
26+ executables:
27+ - warp
stack-nightly.yamldeleted−20
@@ -1,20 +0,0 @@
1−resolver: nightly-2020-01-08
2−packages:
3−- ./auto-update
4−- ./mime-types
5−- ./wai
6−- ./wai-extra
7−- ./warp
8−- ./warp-tls
9−- ./wai-app-static
10−- ./wai-websockets
11−- ./wai-conduit
12−- ./time-manager
13−flags:
14− wai-extra:
15− build-example: true
16−nix:
17− enable: false
18− packages:
19− - fcgi
20− - zlib
stack.yaml+7−7
@@ -1,4 +1,4 @@
1−resolver: lts-14.22
1+resolver: lts-13.14
22packages:
33- ./auto-update
44- ./mime-types
@@ -9,7 +9,6 @@ packages:
99- ./wai-app-static
1010- ./wai-websockets
1111- ./wai-conduit
12−- ./time-manager
1312flags:
1413 wai-extra:
1514 build-example: true
@@ -18,8 +17,9 @@ nix:
1817 packages:
1918 - fcgi
2019 - zlib
21−extra-deps:
22−- http2-2.0.0
23−- network-byte-order-0.1.1.0
24−- tls-1.5.3
25−- tls-session-manager-0.0.4
20+image:
21+ containers:
22+ - base: "fpco/pid1"
23+ name: "yesodweb/warp"
24+ executables:
25+ - warp
time-manager/LICENSEdeleted−20
@@ -1,20 +0,0 @@
1−Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
2−
3−Permission is hereby granted, free of charge, to any person obtaining
4−a copy of this software and associated documentation files (the
5−"Software"), to deal in the Software without restriction, including
6−without limitation the rights to use, copy, modify, merge, publish,
7−distribute, sublicense, and/or sell copies of the Software, and to
8−permit persons to whom the Software is furnished to do so, subject to
9−the following conditions:
10−
11−The above copyright notice and this permission notice shall be
12−included in all copies or substantial portions of the Software.
13−
14−THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15−EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16−MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17−NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18−LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19−OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20−WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
time-manager/time-manager.cabaldeleted−19
@@ -1,19 +0,0 @@
1−Name: time-manager
2−Version: 0.0.0
3−Synopsis: Scalable timer
4−License: MIT
5−License-file: LICENSE
6−Author: Michael Snoyman and Kazu Yamamoto
7−Maintainer: kazu@iij.ad.jp
8−Homepage: http://github.com/yesodweb/wai
9−Category: System
10−Build-Type: Simple
11−Cabal-Version: >=1.8
12−Stability: Stable
13−Description: Scalable timer functions provided by a timer manager.
14−
15−Library
16− Build-Depends: base >= 4.8 && < 5
17− , auto-update
18− Exposed-modules: System.TimeManager
19− Ghc-Options: -Wall
wai-app-static/ChangeLog.md−10
@@ -1,13 +1,3 @@
1−# wai-app-static changelog
2−
3−## 3.1.7.1
4−
5−* Update the test suite too
6−
7−## 3.1.7
8−
9−* Use 302 instead of 301 redirect, to avoid caching the presence of an index.html file
10−
111## 3.1.6.3
122
133* The executable warp obeys `-h` option properly for host
wai-app-static/Network/Wai/Application/Static.hs+8−10
@@ -164,13 +164,11 @@ serveFile StaticSettings {..} req file
164164 -- Didn't match, but we have a hash value. Send the file contents
165165 -- with an ETag header.
166166 --
167− -- RFC7232 (HTTP 1.1):
168− -- > A recipient MUST ignore If-Modified-Since if the request contains an
169− -- > If-None-Match header field; the condition in If-None-Match is
170− -- > considered to be a more accurate replacement for the condition in
171− -- > If-Modified-Since, and the two are only combined for the sake of
172− -- > interoperating with older intermediaries that might not implement
173− -- > If-None-Match.
167+ -- Note: It would be arguably better to next check
168+ -- if-modified-since and return a 304 if that indicates a match as
169+ -- well. However, the circumstances under which such a situation
170+ -- could arise would be very anomolous, and should likely warrant a
171+ -- new file being sent anyway.
174172 (Just hash, _) -> respond [("ETag", hash)]
175173
176174 -- No hash value available, fall back to last modified support.
@@ -245,7 +243,7 @@ staticAppPieces ss rawPieces req sendResponse = liftIO $ do
245243 response :: StaticResponse -> IO W.ResponseReceived
246244 response (FileResponse file ch) = do
247245 mimetype <- ssGetMimeType ss file
248− -- let filesize = fileGetSize file
246+ let filesize = fileGetSize file
249247 let headers = ("Content-Type", mimetype)
250248 -- Let Warp provide the content-length, since it takes
251249 -- range requests into account
@@ -269,13 +267,13 @@ staticAppPieces ss rawPieces req sendResponse = liftIO $ do
269267 Just hash -> replace "etag" (Just hash) (W.queryString req)
270268 Nothing -> remove "etag" (W.queryString req)
271269
272− sendResponse $ W.responseLBS H.status302
270+ sendResponse $ W.responseLBS H.status301
273271 [ ("Content-Type", "text/plain")
274272 , ("Location", S8.append loc $ H.renderQuery True qString)
275273 ] "Redirect"
276274
277275 response (RawRedirect path) =
278− sendResponse $ W.responseLBS H.status302
276+ sendResponse $ W.responseLBS H.status301
279277 [ ("Content-Type", "text/plain")
280278 , ("Location", path)
281279 ] "Redirect"
wai-app-static/WaiAppStatic/CmdLine.hs−1
@@ -39,7 +39,6 @@ data Args = Args
3939 }
4040
4141#if MIN_VERSION_optparse_applicative(0, 10, 0)
42−option' :: Mod OptionFields Int -> Parser Int
4342option' = option auto
4443#else
4544option' = option
wai-app-static/WaiAppStatic/Listing.hs+1−1
@@ -64,7 +64,7 @@ defaultListing pieces (Folder contents) = do
6464
6565 -- Add a link to the root of the tree
6666 showFolder' :: Bool -> Pieces -> H.Html
67− showFolder' hasTrailingSlash pieces' = showFolder hasTrailingSlash (unsafeToPiece "root" : pieces')
67+ showFolder' hasTrailingSlash pieces = showFolder hasTrailingSlash (unsafeToPiece "root" : pieces)
6868
6969 showFolder :: Bool -> Pieces -> H.Html
7070 showFolder _ [] = "/" -- won't happen
wai-app-static/WaiAppStatic/Storage/Embedded/TH.hs+1
@@ -7,6 +7,7 @@ module WaiAppStatic.Storage.Embedded.TH(
77
88import Data.ByteString.Builder.Extra (byteStringInsert)
99import Codec.Compression.GZip (compress)
10+import Control.Applicative
1011import Data.ByteString.Unsafe (unsafePackAddressLen)
1112import Data.Either (lefts, rights)
1213import GHC.Exts (Int(..))
wai-app-static/WaiAppStatic/Types.hs−2
@@ -59,8 +59,6 @@ toPieces :: [Text] -> Maybe Pieces
5959toPieces = mapM toPiece
6060
6161-- | Request coming from a user. Corresponds to @pathInfo@.
62−--
63−-- The root path is the empty list.
6462type Pieces = [Piece]
6563
6664-- | Values for the max-age component of the cache-control response header.
wai-app-static/test/WaiAppStaticTest.hs+9−9
@@ -57,22 +57,22 @@ spec = do
5757 assertStatus 200 =<<
5858 request (setRawPathInfo defRequest path)
5959
60− it "404 for non-existent files" $ webApp $
60+ it "404 for non-existant files" $ webApp $
6161 assertStatus 404 =<<
6262 request (setRawPathInfo defRequest "doesNotExist")
6363
64− it "302 redirect when multiple slashes" $ webApp $ do
64+ it "301 redirect when multiple slashes" $ webApp $ do
6565 req <- request (setRawPathInfo defRequest "a//b/c")
66− assertStatus 302 req
66+ assertStatus 301 req
6767 assertHeader "Location" "../../a/b/c" req
6868
6969 let absoluteApp = flip runSession $ staticApp $ (defaultWebAppSettings "test") {
7070 ssMkRedirect = \_ u -> S8.append "http://www.example.com" u
7171 }
72− it "302 redirect when multiple slashes" $ absoluteApp $
72+ it "301 redirect when multiple slashes" $ absoluteApp $
7373 flip mapM_ ["/a//b/c", "a//b/c"] $ \path -> do
7474 req <- request (setRawPathInfo defRequest path)
75− assertStatus 302 req
75+ assertStatus 301 req
7676 assertHeader "Location" "http://www.example.com/a/b/c" req
7777
7878 describe "webApp when requesting a static asset" $ do
@@ -131,10 +131,10 @@ spec = do
131131 assertStatus 304 req
132132 assertNoHeader "Cache-Control" req
133133
134− context "302 redirect to add a trailing slash on directories if missing" $ do
134+ context "301 redirect to add a trailing slash on directories if missing" $ do
135135 it "works at the root" $ fileServerApp $ do
136136 req <- request (setRawPathInfo defRequest "/a")
137− assertStatus 302 req
137+ assertStatus 301 req
138138 assertHeader "Location" "/a/" req
139139
140140 it "works when an index.html is delivered" $ do
@@ -144,7 +144,7 @@ spec = do
144144 inTempDirectory $ fileServerAppWithSettings settings $ do
145145 liftIO $ touch "foo/index.html"
146146 req <- request (setRawPathInfo defRequest "/foo")
147− assertStatus 302 req
147+ assertStatus 301 req
148148 assertHeader "Location" "/foo/" req
149149
150150 let urlMapApp = flip runSession $ \req send ->
@@ -158,7 +158,7 @@ spec = do
158158 "urlMapApp: only works at subPath"
159159 it "works with subpath at the root of the file server" $ urlMapApp $ do
160160 req <- request (setRawPathInfo defRequest "/subPath")
161− assertStatus 302 req
161+ assertStatus 301 req
162162 assertHeader "Location" "/subPath/" req
163163
164164 context "with defaultWebAppSettings" $ do
wai-app-static/wai-app-static.cabal+3−3
@@ -1,5 +1,5 @@
11name: wai-app-static
2−version: 3.1.7.1
2+version: 3.1.6.3
33license: MIT
44license-file: LICENSE
55author: Michael Snoyman <michael@snoyman.com>
@@ -49,7 +49,7 @@ library
4949 , filepath
5050 , wai-extra >= 3.0 && < 3.1
5151 , optparse-applicative >= 0.7
52− , warp >= 3.0.11 && < 3.4
52+ , warp >= 3.0.11 && < 3.3
5353
5454 exposed-modules: Network.Wai.Application.Static
5555 WaiAppStatic.Storage.Filesystem
@@ -102,7 +102,7 @@ test-suite runtests
102102 , temporary
103103 , mockery
104104 -- , containers
105− ghc-options: -Wall
105+ ghc-options: -Wall
106106
107107source-repository head
108108 type: git
wai-conduit/Network/Wai/Conduit.hs+6−5
@@ -17,16 +17,17 @@ import qualified Data.ByteString as S
1717import Control.Monad (unless)
1818import Network.HTTP.Types
1919import Data.ByteString.Builder (Builder)
20+import Data.IORef
2021import qualified Data.Conduit.List as CL
2122
2223-- | Stream the request body.
2324--
2425-- Since 3.0.0
25−sourceRequestBody :: MonadIO m => Request -> ConduitT () ByteString m ()
26+sourceRequestBody :: MonadIO m => Request -> Source m ByteString
2627sourceRequestBody req =
2728 loop
2829 where
29− go = liftIO (getRequestBodyChunk req)
30+ go = liftIO (requestBody req)
3031
3132 loop = do
3233 bs <- go
@@ -37,9 +38,9 @@ sourceRequestBody req =
3738-- | Create an HTTP response out of a @Source@.
3839--
3940-- Since 3.0.0
40−responseSource :: Status -> ResponseHeaders -> ConduitT () (Flush Builder) IO () -> Response
41+responseSource :: Status -> ResponseHeaders -> Source IO (Flush Builder) -> Response
4142responseSource s hs src = responseStream s hs $ \send flush ->
42− runConduit $ src .| CL.mapM_ (\mbuilder ->
43+ src $$ CL.mapM_ (\mbuilder ->
4344 case mbuilder of
4445 Chunk b -> send b
4546 Flush -> flush)
@@ -49,7 +50,7 @@ responseSource s hs src = responseStream s hs $ \send flush ->
4950--
5051-- Since 3.0.0
5152responseRawSource :: (MonadIO m, MonadIO n)
52− => (ConduitT () ByteString m () -> ConduitT ByteString Void n () -> IO ())
53+ => (Source m ByteString -> Sink ByteString n () -> IO ())
5354 -> Response
5455 -> Response
5556responseRawSource app =
wai-extra/ChangeLog.md−27
@@ -1,32 +1,5 @@
11# Changelog for wai-extra
22
3−## 3.0.29.1
4−
5−* Fix `Network.Wai.Test.request` always sending an empty request body [#794](https://github.com/yesodweb/wai/pull/794)
6−
7−## 3.0.29
8−
9−* Export `Network.Wai.EventSource.eventStreamAppRaw` [#786](https://github.com/yesodweb/wai/pull/786)
10−
11−## 3.0.28
12−
13−* Add `Network.Wai.EventSource.eventStreamAppRaw` [#767](https://github.com/yesodweb/wai/pull/767)
14−
15−## 3.0.27
16−
17−* Add custom request log formatter which includes response headers [#762](https://github.com/yesodweb/wai/pull/762)
18−
19−## 3.0.26.1
20−
21−* When available, supply the response size to custom loggers
22− [#757](https://github.com/yesodweb/wai/pull/757)
23−
24−## 3.0.26
25−
26−* Throw 413 for too large payload
27−* Throw 431 for too large headers
28− [#741](https://github.com/yesodweb/wai/pull/741)
29−
303## 3.0.25
314
325* Supporting `network` version 3.0.
wai-extra/Network/Wai/EventSource.hs+1−19
@@ -5,14 +5,11 @@
55 headers:
66
77 > [ ("X-Accel-Buffering", "no"), ("Cache-Control", "no-cache")]
8−
9− There is a small example using these functions in the @example@ directory.
108-}
119module Network.Wai.EventSource (
1210 ServerEvent(..),
1311 eventSourceAppChan,
14− eventSourceAppIO,
15− eventStreamAppRaw
12+ eventSourceAppIO
1613 ) where
1714
1815import Data.Function (fix)
@@ -42,18 +39,3 @@ eventSourceAppIO src _ sendResponse =
4239 case eventToBuilder se of
4340 Nothing -> return ()
4441 Just b -> sendChunk b >> flush >> loop
45−
46−-- | Make a new WAI EventSource application with a handler that emits events.
47−--
48−-- @since 3.0.28
49−eventStreamAppRaw :: ((ServerEvent -> IO()) -> IO () -> IO ()) -> Application
50−eventStreamAppRaw handler _ sendResponse =
51− sendResponse $ responseStream
52− status200
53− [(hContentType, "text/event-stream")]
54− $ \sendChunk flush -> handler (sendEvent sendChunk) flush
55− where
56− sendEvent sendChunk event =
57− case eventToBuilder event of
58− Nothing -> return ()
59− Just b -> sendChunk b
wai-extra/Network/Wai/EventSource/EventStream.hs+1−1
@@ -1,5 +1,5 @@
11{-# LANGUAGE CPP #-}
2−{- code adapted by Mathias Billman originally from Chris Smith https://github.com/cdsmith/gloss-web -}
2+{- code adapted by Mathias Billman originaly from Chris Smith https://github.com/cdsmith/gloss-web -}
33
44{-|
55 Internal module, usually you don't need to use it.
wai-extra/Network/Wai/Middleware/Gzip.hs−3
@@ -89,9 +89,6 @@ defaultCheckMime bs =
8989-- if gzip is supported.
9090--
9191-- File responses will be compressed according to the 'GzipFiles' setting.
92−--
93−-- Will only be applied based on the 'gzipCheckMime' setting. For default
94−-- behavior, see 'defaultCheckMime'.
9592gzip :: GzipSettings -> Middleware
9693gzip set app env sendResponse = app env $ \res ->
9794 case res of
wai-extra/Network/Wai/Middleware/RequestLogger.hs+10−60
@@ -14,7 +14,6 @@ module Network.Wai.Middleware.RequestLogger
1414 , OutputFormat (..)
1515 , OutputFormatter
1616 , OutputFormatterWithDetails
17− , OutputFormatterWithDetailsAndHeaders
1817 , Destination (..)
1918 , Callback
2019 , IPAddrSource (..)
@@ -50,42 +49,14 @@ import Network.Wai.Middleware.RequestLogger.Internal
5049import Network.Wai.Header (contentLength)
5150import Data.Text.Encoding (decodeUtf8')
5251
53−data OutputFormat
54− = Apache IPAddrSource
55− | Detailed Bool -- ^ use colors?
56− | CustomOutputFormat OutputFormatter
57− | CustomOutputFormatWithDetails OutputFormatterWithDetails
58− | CustomOutputFormatWithDetailsAndHeaders OutputFormatterWithDetailsAndHeaders
52+data OutputFormat = Apache IPAddrSource
53+ | Detailed Bool -- ^ use colors?
54+ | CustomOutputFormat OutputFormatter
55+ | CustomOutputFormatWithDetails OutputFormatterWithDetails
5956
6057type OutputFormatter = ZonedDate -> Request -> Status -> Maybe Integer -> LogStr
61−
62−type OutputFormatterWithDetails
63− = ZonedDate
64− -> Request
65− -> Status
66− -> Maybe Integer
67− -> NominalDiffTime
68− -> [S8.ByteString]
69− -> B.Builder
70− -> LogStr
71−
72−-- | Same as @OutputFormatterWithDetails@ but with response headers included
73−--
74−-- This is useful if you wish to include arbitrary application data in your
75−-- logs, e.g., an authenticated user ID, which you would set in a response
76−-- header in your application and retrieve in the log formatter.
77−--
78−-- @since 3.0.27
79−type OutputFormatterWithDetailsAndHeaders
80− = ZonedDate -- ^ When the log message was generated
81− -> Request -- ^ The WAI request
82− -> Status -- ^ HTTP status code
83− -> Maybe Integer -- ^ Response size
84− -> NominalDiffTime -- ^ Duration of the request
85− -> [S8.ByteString] -- ^ The request body
86− -> B.Builder -- ^ Raw response
87− -> [Header] -- ^ The response headers
88− -> LogStr
58+type OutputFormatterWithDetails =
59+ ZonedDate -> Request -> Status -> Maybe Integer -> NominalDiffTime -> [S8.ByteString] -> B.Builder -> LogStr
8960
9061data Destination = Handle Handle
9162 | Logger LoggerSet
@@ -137,9 +108,6 @@ mkRequestLogger RequestLoggerSettings{..} = do
137108 CustomOutputFormatWithDetails formatter -> do
138109 getdate <- getDateGetter flusher
139110 return $ customMiddlewareWithDetails callbackAndFlush getdate formatter
140− CustomOutputFormatWithDetailsAndHeaders formatter -> do
141− getdate <- getDateGetter flusher
142− return $ customMiddlewareWithDetailsAndHeaders callbackAndFlush getdate formatter
143111
144112apacheMiddleware :: ApacheLoggerActions -> Middleware
145113apacheMiddleware ala app req sendResponse = app req $ \res -> do
@@ -150,8 +118,8 @@ apacheMiddleware ala app req sendResponse = app req $ \res -> do
150118customMiddleware :: Callback -> IO ZonedDate -> OutputFormatter -> Middleware
151119customMiddleware cb getdate formatter app req sendResponse = app req $ \res -> do
152120 date <- liftIO getdate
153− let msize = contentLength (responseHeaders res)
154− liftIO $ cb $ formatter date req (responseStatus res) msize
121+ -- We use Nothing for the response size since we generally don't know it
122+ liftIO $ cb $ formatter date req (responseStatus res) Nothing
155123 sendResponse res
156124
157125customMiddlewareWithDetails :: Callback -> IO ZonedDate -> OutputFormatterWithDetails -> Middleware
@@ -161,33 +129,15 @@ customMiddlewareWithDetails cb getdate formatter app req sendResponse = do
161129 app req' $ \res -> do
162130 t1 <- getCurrentTime
163131 date <- liftIO getdate
164− let msize = contentLength (responseHeaders res)
132+ -- We use Nothing for the response size since we generally don't know it
165133 builderIO <- newIORef $ B.byteString ""
166134 res' <- recordChunks builderIO res
167135 rspRcv <- sendResponse res'
168136 _ <- liftIO . cb .
169− formatter date req' (responseStatus res') msize (t1 `diffUTCTime` t0) reqBody =<<
137+ formatter date req' (responseStatus res') Nothing (t1 `diffUTCTime` t0) reqBody =<<
170138 readIORef builderIO
171139 return rspRcv
172140
173−customMiddlewareWithDetailsAndHeaders :: Callback -> IO ZonedDate -> OutputFormatterWithDetailsAndHeaders -> Middleware
174−customMiddlewareWithDetailsAndHeaders cb getdate formatter app req sendResponse = do
175− (req', reqBody) <- getRequestBody req
176− t0 <- getCurrentTime
177− app req' $ \res -> do
178− t1 <- getCurrentTime
179− date <- liftIO getdate
180− let msize = contentLength (responseHeaders res)
181− builderIO <- newIORef $ B.byteString ""
182− res' <- recordChunks builderIO res
183− rspRcv <- sendResponse res'
184− _ <- do
185− rawResponse <- readIORef builderIO
186− let status = responseStatus res'
187− duration = t1 `diffUTCTime` t0
188− resHeaders = responseHeaders res'
189− liftIO . cb $ formatter date req' status msize duration reqBody rawResponse resHeaders
190− return rspRcv
191141-- | Production request logger middleware.
192142--
193143-- This uses the 'Apache' logging format, and takes IP addresses for clients from
wai-extra/Network/Wai/Middleware/RequestLogger/JSON.hs+1−34
@@ -1,10 +1,7 @@
11{-# LANGUAGE RecordWildCards #-}
22{-# LANGUAGE CPP #-}
33
4−module Network.Wai.Middleware.RequestLogger.JSON
5− ( formatAsJSON
6− , formatAsJSONWithHeaders
7− ) where
4+module Network.Wai.Middleware.RequestLogger.JSON (formatAsJSON) where
85
96import qualified Data.ByteString.Builder as BB (toLazyByteString)
107import Data.ByteString.Lazy (toStrict)
@@ -43,30 +40,6 @@ formatAsJSON date req status responseSize duration reqBody response =
4340 , "time" .= decodeUtf8With lenientDecode date
4441 ]) <> "\n"
4542
46−-- | Same as @formatAsJSON@ but with response headers included
47−--
48−-- This is useful for passing arbitrary data from your application out to the
49−-- WAI layer for it to be logged, but you may need to be careful to
50−-- subsequently redact any headers which may contain sensitive data.
51−--
52−-- @since 3.0.27
53−formatAsJSONWithHeaders :: OutputFormatterWithDetailsAndHeaders
54−formatAsJSONWithHeaders date req status resSize duration reqBody res resHeaders =
55− toLogStr (encode $
56− object
57− [ "request" .= requestToJSON duration req reqBody
58− , "response" .= object
59− [ "status" .= statusCode status
60− , "size" .= resSize
61− , "headers" .= responseHeadersToJSON resHeaders
62− , "body" .=
63− if statusCode status >= 400
64− then Just . decodeUtf8With lenientDecode . toStrict . BB.toLazyByteString $ res
65− else Nothing
66− ]
67− , "time" .= decodeUtf8With lenientDecode date
68− ]) <> "\n"
69−
7043word32ToHostAddress :: Word32 -> Text
7144word32ToHostAddress = T.intercalate "." . map (T.pack . show) . fromIPv4 . fromHostAddress
7245
@@ -117,12 +90,6 @@ requestHeadersToJSON = toJSON . map hToJ where
11790 hToJ ("Cookie", _) = toJSON ("Cookie" :: Text, "-RDCT-" :: Text)
11891 hToJ hd = headerToJSON hd
11992
120−responseHeadersToJSON :: [Header] -> Value
121−responseHeadersToJSON = toJSON . map hToJ where
122− -- Redact cookies
123− hToJ ("Set-Cookie", _) = toJSON ("Set-Cookie" :: Text, "-RDCT-" :: Text)
124− hToJ hd = headerToJSON hd
125−
12693headerToJSON :: Header -> Value
12794headerToJSON (headerName, header) = toJSON (decodeUtf8With lenientDecode . original $ headerName, decodeUtf8With lenientDecode header)
12895
wai-extra/Network/Wai/Middleware/Rewrite.hs+1−1
@@ -169,7 +169,7 @@ import Control.Applicative
169169--
170170-- as the following example demonstrates.
171171--
172−-- Old versions of the library could only handle path pieces, not queries.
172+-- Old versions of the libary could only handle path pieces, not queries.
173173-- This could have been supplied to 'rewritePure'.
174174--
175175-- @
wai-extra/Network/Wai/Parse.hs+4−11
@@ -48,7 +48,6 @@ module Network.Wai.Parse
4848#endif
4949 ) where
5050
51−import qualified Control.Exception as E
5251import qualified Data.ByteString as S
5352import qualified Data.ByteString.Lazy as L
5453import qualified Data.ByteString.Char8 as S8
@@ -68,7 +67,6 @@ import Control.Monad (when, unless, guard)
6867import Control.Monad.Trans.Resource (allocate, release, register, InternalState, runInternalState)
6968import Data.IORef
7069import Network.HTTP.Types (hContentType)
71−import Network.HTTP2( HTTP2Error (..), ErrorCodeId (..) )
7270import Data.CaseInsensitive (mk)
7371
7472import Prelude hiding (lines)
@@ -428,8 +426,6 @@ conduitRequestBodyEx o backend (Multipart bound) rbody add =
428426
429427
430428-- | Take one header or subheader line.
431−-- Since: 3.0.26
432−-- Throw 431 if headers too large.
433429takeLine :: Maybe Int -> Source -> IO (Maybe S.ByteString)
434430takeLine maxlen src =
435431 go ""
@@ -438,8 +434,7 @@ takeLine maxlen src =
438434 bs <- readSource src
439435 case maxlen of
440436 Just maxlen' -> when (S.length front > maxlen') $
441− E.throwIO $ ConnectionError (UnknownErrorCode 431)
442− "Request Header Fields Too Large"
437+ error "Header line length exceeds allowed maximum."
443438 Nothing -> return ()
444439 if S.null bs
445440 then close front
@@ -455,8 +450,7 @@ takeLine maxlen src =
455450 let res = front `S.append` x
456451 case maxlen of
457452 Just maxlen' -> when (S.length res > maxlen') $
458− E.throwIO $ ConnectionError (UnknownErrorCode 431)
459− "Request Header Fields Too Large"
453+ error "Header line length exceeds allowed maximum."
460454 Nothing -> return ()
461455 return $ Just $ killCR $ res
462456
@@ -645,9 +639,8 @@ wrapTillBound bound src max' = do
645639 cur <- atomicModifyIORef' sref $ \ cur ->
646640 let new = cur + fromIntegral (S.length bs) in (new, new)
647641 case max' of
648− Just max'' | cur > max'' ->
649− E.throwIO $ ConnectionError (UnknownErrorCode 413) "Payload Too Large"
650− _ -> return ()
642+ Just max'' | cur > max'' -> error "Maximum size exceeded"
643+ _ -> return ()
651644 if S.null bs
652645 then do
653646 writeIORef ref $ WTBDone False
wai-extra/Network/Wai/Test.hs+14−22
@@ -99,10 +99,6 @@ runSession session app = ST.evalStateT (runReaderT session app) initState
9999data SRequest = SRequest
100100 { simpleRequest :: Request
101101 , simpleRequestBody :: L.ByteString
102− -- ^ Request body that will override the one set in 'simpleRequest'.
103− --
104− -- This is usually simpler than setting the body as a stateful IO-action
105− -- in 'simpleRequest'.
106102 }
107103data SResponse = SResponse
108104 { simpleStatus :: H.Status
@@ -110,16 +106,8 @@ data SResponse = SResponse
110106 , simpleBody :: L.ByteString
111107 }
112108 deriving (Show, Eq)
113−
114109request :: Request -> Session SResponse
115−request req = do
116− app <- ask
117− req' <- addCookiesToRequest req
118− response <- liftIO $ do
119− ref <- newIORef $ error "runResponse gave no result"
120− ResponseReceived <- app req' (runResponse ref)
121− readIORef ref
122− extractSetCookieFromSResponse response
110+request = srequest . flip SRequest L.empty
123111
124112-- | Set whole path (request path + query string).
125113setPath :: Request -> S8.ByteString -> Request
@@ -178,18 +166,22 @@ extractSetCookieFromSResponse response = do
178166 (Map.fromList [(Cookie.setCookieName c, c) | c <- newClientCookies ]))
179167 return response
180168
181−-- | Similar to 'request', but allows setting the request body as a plain
182−-- 'L.ByteString'.
183169srequest :: SRequest -> Session SResponse
184170srequest (SRequest req bod) = do
171+ app <- ask
185172 refChunks <- liftIO $ newIORef $ L.toChunks bod
186− request $
187− req
188− { requestBody = atomicModifyIORef refChunks $ \bss ->
189− case bss of
190− [] -> ([], S.empty)
191− x:y -> (y, x)
192− }
173+ let req' = req
174+ { requestBody = atomicModifyIORef refChunks $ \bss ->
175+ case bss of
176+ [] -> ([], S.empty)
177+ x:y -> (y, x)
178+ }
179+ req'' <- addCookiesToRequest req'
180+ response <- liftIO $ do
181+ ref <- newIORef $ error "runResponse gave no result"
182+ ResponseReceived <- app req'' (runResponse ref)
183+ readIORef ref
184+ extractSetCookieFromSResponse response
193185
194186runResponse :: IORef SResponse -> Response -> IO ResponseReceived
195187runResponse ref res = do
wai-extra/example/Main.hs−11
@@ -37,17 +37,6 @@ eventIO = do
3737 Nothing
3838 [string8 . show $ time]
3939
40−eventRaw :: (ServerEvent -> IO ()) -> IO () -> IO ()
41−eventRaw = handle 0
42− where
43− handle counter emit flush = do
44− threadDelay 1000000
45− _ <- emit $ ServerEvent (Just $ string8 "raw")
46− Nothing
47− [string8 . show $ counter]
48− _ <- flush
49− handle (counter + 1) emit flush
50−
5140main :: IO ()
5241main = do
5342 chan <- newChan
wai-extra/test/Network/Wai/ParseSpec.hs+5−14
@@ -13,7 +13,6 @@ import qualified Data.ByteString.Lazy as L
1313import qualified Data.Text as TS
1414import qualified Data.Text.Encoding as TE
1515import Control.Monad.Trans.Resource (withInternalState, runResourceT)
16−import Network.HTTP2( HTTP2Error (..), ErrorCodeId (..) )
1716
1817import Network.Wai
1918import Network.Wai.Test
@@ -95,9 +94,6 @@ caseParseRequestBody = do
9594 let expectedfile3 = [("yaml", FileInfo "README" "application/octet-stream" "Photo blog using Hack.\n")]
9695 let expected3 = (expectedsmap3, expectedfile3)
9796
98− let unknownErrorException c (ConnectionError (UnknownErrorCode code) _) = c == code
99− unknownErrorException _ _ = False
100−
10197
10298 let def = defaultParseRequestBodyOptions
10399 it "parsing actual post multipart/form-data" $ do
@@ -123,21 +119,17 @@ caseParseRequestBody = do
123119
124120 it "exceeding file size" $ do
125121 SRequest req4 _bod4 <- toRequest'' ctype3 content3
126− (parseRequestBodyEx ( setMaxRequestFileSize 2 def ) lbsBackEnd req4)
127− `shouldThrow` unknownErrorException 413
122+ (parseRequestBodyEx ( setMaxRequestFileSize 2 def ) lbsBackEnd req4) `shouldThrow` anyErrorCall
128123
129124 it "exceeding total file size" $ do
130125 SRequest req4 _bod4 <- toRequest'' ctype3 content3
131− (parseRequestBodyEx ( setMaxRequestFilesSize 20 def ) lbsBackEnd req4)
132− `shouldThrow` unknownErrorException 413
126+ (parseRequestBodyEx ( setMaxRequestFilesSize 20 def ) lbsBackEnd req4) `shouldThrow` anyErrorCall
133127 SRequest req5 _bod5 <- toRequest'' ctype3 content5
134− (parseRequestBodyEx ( setMaxRequestFilesSize 20 def ) lbsBackEnd req5)
135− `shouldThrow` unknownErrorException 413
128+ (parseRequestBodyEx ( setMaxRequestFilesSize 20 def ) lbsBackEnd req5) `shouldThrow` anyErrorCall
136129
137130 it "exceeding max parm value size" $ do
138131 SRequest req4 _bod4 <- toRequest'' ctype2 content2
139− (parseRequestBodyEx ( setMaxRequestParmsSize 10 def ) lbsBackEnd req4)
140− `shouldThrow` unknownErrorException 413
132+ (parseRequestBodyEx ( setMaxRequestParmsSize 10 def ) lbsBackEnd req4) `shouldThrow` anyErrorCall
141133
142134 it "exceeding max header lines" $ do
143135 SRequest req4 _bod4 <- toRequest'' ctype2 content2
@@ -145,8 +137,7 @@ caseParseRequestBody = do
145137
146138 it "exceeding header line size" $ do
147139 SRequest req4 _bod4 <- toRequest'' ctype3 content4
148− (parseRequestBodyEx ( setMaxHeaderLineLength 8190 def ) lbsBackEnd req4)
149− `shouldThrow` unknownErrorException 431
140+ (parseRequestBodyEx ( setMaxHeaderLineLength 8190 def ) lbsBackEnd req4) `shouldThrow` anyErrorCall
150141
151142 it "Testing parseRequestBodyEx with application/x-www-form-urlencoded" $ do
152143 let content = "thisisalongparameterkey=andthisbeanevenlongerparametervaluehelloworldhowareyou"
wai-extra/test/Network/Wai/TestSpec.hs+11−41
@@ -3,8 +3,6 @@ module Network.Wai.TestSpec (main, spec) where
33
44import Control.Monad (void)
55
6−import qualified Data.IORef as IORef
7−
86import qualified Data.Text.Encoding as TE
97
108import Data.Time.Calendar (fromGregorian)
@@ -55,53 +53,25 @@ spec = do
5553 it "sets rawQueryString to empty string" $ do
5654 rawQueryString (setPath defaultRequest "/foo/bar/baz") `shouldBe` ""
5755
58− describe "srequest" $ do
59−
60− let echoApp req respond = do
61− reqBody <- L8.fromStrict <$> getRequestBodyChunk req
62− let reqHeaders = requestHeaders req
63− respond $
64− responseLBS
65− status200
66− reqHeaders
67− reqBody
68−
69− it "returns the response body of an echo app" $ do
70− sresp <- flip runSession echoApp $
71− srequest $ SRequest defaultRequest "request body"
72− simpleBody sresp `shouldBe` "request body"
73−
7456 describe "request" $ do
7557
76− let echoApp req respond = do
77− reqBody <- L8.fromStrict <$> getRequestBodyChunk req
78− let reqHeaders = requestHeaders req
58+ let simpleApp _req respond =
7959 respond $
8060 responseLBS
8161 status200
82− reqHeaders
83− reqBody
62+ [("foo", "bar")]
63+ "simple"
8464
85− it "returns the status code of an echo app on default request" $ do
86− sresp <- runSession (request defaultRequest) echoApp
65+ it "returns the status code of a simple app on default request" $ do
66+ sresp <- runSession (request defaultRequest) simpleApp
8767 simpleStatus sresp `shouldBe` status200
8868
89− it "returns the response body of an echo app" $ do
90− bodyRef <- IORef.newIORef "request body"
91− let getBodyChunk = IORef.atomicModifyIORef bodyRef $ \leftover -> ("", leftover)
92− sresp <- flip runSession echoApp $
93− request $
94− defaultRequest
95− { requestBody = getBodyChunk
96− }
97− simpleBody sresp `shouldBe` "request body"
98−
99− it "returns the response headers of an echo app" $ do
100− sresp <- flip runSession echoApp $
101− request $
102− defaultRequest
103− { requestHeaders = [("foo", "bar")]
104− }
69+ it "returns the response body of a simple app on default request" $ do
70+ sresp <- runSession (request defaultRequest) simpleApp
71+ simpleBody sresp `shouldBe` "simple"
72+
73+ it "returns the response headers of a simple app on default request" $ do
74+ sresp <- runSession (request defaultRequest) simpleApp
10575 simpleHeaders sresp `shouldBe` [("foo", "bar")]
10676
10777 let cookieApp req respond =
wai-extra/wai-extra.cabal+4−6
@@ -1,5 +1,5 @@
11Name: wai-extra
2−Version: 3.0.29.1
2+Version: 3.0.25
33Synopsis: Provides some basic WAI handlers and middleware.
44description:
55 Provides basic WAI handler and middleware functionality:
@@ -87,7 +87,7 @@ flag build-example
8787 default: False
8888
8989Library
90− Build-Depends: base >= 4.8 && < 5
90+ Build-Depends: base >= 4.6 && < 5
9191 , bytestring >= 0.10.4
9292 , wai >= 3.0.3.0 && < 3.3
9393 , old-locale >= 1.0.0.2 && < 1.1
@@ -99,8 +99,8 @@ Library
9999 , text >= 0.7
100100 , case-insensitive >= 0.2
101101 , data-default-class
102− , fast-logger >= 2.4.5
103− , wai-logger >= 2.3.2
102+ , fast-logger >= 2.4.5 && < 2.5
103+ , wai-logger >= 2.3.2 && < 2.4
104104 , ansi-terminal
105105 , resourcet >= 0.4.6 && < 1.3
106106 , void >= 0.5
@@ -115,7 +115,6 @@ Library
115115 , zlib
116116 , aeson
117117 , iproute
118− , http2
119118
120119 if os(windows)
121120 cpp-options: -DWINDOWS
@@ -203,7 +202,6 @@ test-suite spec
203202 , cookie
204203 , time
205204 , case-insensitive
206− , http2
207205 ghc-options: -Wall
208206 default-language: Haskell2010
209207
wai-http2-extra/Network/Wai/Middleware/Push/Referer.hs+2−4
@@ -120,12 +120,10 @@ pushOnReferer settings@Settings{..} app req sendResponse = do
120120 when (isNothing mauth
121121 || requestHeaderHost req == mauth) $ do
122122 when (path /= refPath) $ do -- just in case
123− let !path' = BS.copy path
124− !refPath' = BS.copy refPath
125− mpp <- makePushPromise refPath' path' file
123+ mpp <- makePushPromise refPath path file
126124 case mpp of
127125 Nothing -> return ()
128− Just pp -> reaperAdd reaper (refPath',pp)
126+ Just pp -> reaperAdd reaper (refPath,pp)
129127 ps -> do
130128 let !h2d = defaultHTTP2Data { http2dataPushPromise = ps}
131129 setHTTP2Data req (Just h2d)
wai-http2-extra/wai-http2-extra.cabal+2−9
@@ -1,5 +1,5 @@
11Name: wai-http2-extra
2−Version: 0.1.2
2+Version: 0.1.0
33Synopsis: WAI utilities for HTTP/2
44License: MIT
55License-file: LICENSE
@@ -8,7 +8,7 @@ Maintainer: kazu@iij.ad.jp
88Homepage: http://github.com/yesodweb/wai
99Category: Web
1010Build-Type: Simple
11−Cabal-Version: >=1.10
11+Cabal-Version: >=1.8
1212Stability: Stable
1313Description: WAI utilities for HTTP/2
1414
@@ -24,9 +24,6 @@ Library
2424 Exposed-modules: Network.Wai.Middleware.Push.Referer
2525 Other-modules: Network.Wai.Middleware.Push.Referer.LimitMultiMap
2626 Ghc-Options: -Wall
27− if impl(ghc >= 8)
28− default-extensions: Strict StrictData
29− default-language: Haskell2010
3027
3128Test-Suite doctest
3229 Type: exitcode-stdio-1.0
@@ -35,10 +32,6 @@ Test-Suite doctest
3532 Main-Is: doctests.hs
3633 Build-Depends: base
3734 , doctest >= 0.10.1
38− if impl(ghc >= 8)
39− default-extensions: Strict StrictData
40− default-language: Haskell2010
41−
4235Source-Repository head
4336 Type: git
4437 Location: git://github.com/yesodweb/wai.git
wai-websockets/server.lhs+1−7
@@ -8,8 +8,6 @@ this example, keep the [reference](http://jaspervdj.be/websockets/reference)
88nearby to check out the functions we use.
99
1010> {-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
11−> {-# LANGUAGE CPP #-}
12−> {-# OPTIONS_GHC -Wno-unused-top-binds #-} -- for numClients
1311> import Data.Char (isPunctuation, isSpace)
1412> import Data.Monoid (mappend)
1513> import Data.Text (Text)
@@ -101,13 +99,9 @@ stays alive on some browsers.
10199
102100> application state pending = do
103101> conn <- WS.acceptRequest pending
104−#if __GLASGOW_HASKELL__ >= 806
105−> WS.withPingThread conn 30 (return ()) $ do
106−#else
107102> WS.forkPingThread conn 30
108−#endif
109103
110−When a client is successfully connected, we read the first message. This should
104+When a client is succesfully connected, we read the first message. This should
111105be in the format of "Hi! I am Jasper", where Jasper is the requested username.
112106
113107> msg <- WS.receiveData conn
wai-websockets/wai-websockets.cabal−1
@@ -45,7 +45,6 @@ Executable wai-websockets-example
4545 , text
4646 , file-embed
4747 , http-types
48− other-modules: Network.Wai.Handler.WebSockets
4948 else
5049 buildable: False
5150
wai/ChangeLog.md−6
@@ -1,9 +1,3 @@
1−# ChangeLog for wai
2−
3−## 3.2.2.1
4−
5−* Fix missing reexport of `getRequestBodyChunk` [#753](https://github.com/yesodweb/wai/issues/753)
6−
71## 3.2.2
82
93* Deprecate `requestBody` in favor of the more clearly named `getRequestBodyChunk`. [#726](https://github.com/yesodweb/wai/pull/726)
wai/Network/Wai.hs−1
@@ -53,7 +53,6 @@ module Network.Wai
5353 , remoteHost
5454 , pathInfo
5555 , queryString
56− , getRequestBodyChunk
5756 , requestBody
5857 , vault
5958 , requestBodyLength
wai/wai.cabal+2−1
@@ -1,5 +1,5 @@
11Name: wai
2−Version: 3.2.2.1
2+Version: 3.2.2
33Synopsis: Web Application Interface.
44Description: Provides a common protocol for communication between web applications and web servers.
55 .
@@ -25,6 +25,7 @@ Library
2525 , network >= 2.2.1.5
2626 , http-types >= 0.7
2727 , text >= 0.7
28+ , transformers >= 0.0
2829 , vault >= 0.3 && < 0.4
2930 Exposed-modules: Network.Wai
3031 Network.Wai.Internal
warp-tls/ChangeLog.md+1−44
@@ -1,50 +1,7 @@
1−## 3.2.11
2−
3−* Ignoring an exception from shutdown (gracefulClose).
4−
5−## 3.2.10
6−
7−* Passing client certificate, if any, to Warp
8− [#783](https://github.com/yesodweb/wai/pull/783)
9−
10−## 3.2.9
11−
12−* Cooperating setGracefulCloseTimeout1 and setGracefulCloseTimeout2 of Warp.
13− [#782](https://github.com/yesodweb/wai/pull/782)
14−
15−## 3.2.8
16−
17−* Using gracefullClose of network 3.1.1 or later if available.
18−
19−## 3.2.7
20−
21−* Relaxing version constraint.
22−
23−## 3.2.6
24−
25−* Using the Strict and StrictData language extensions for GHC >8.
26− [#752](https://github.com/yesodweb/wai/pull/752)
27−
28−## 3.2.5
29−
30−* When tls 1.5.0 is available, TLS 1.3 is automatically supported.
31−
32−## 3.2.4.3
33−
34−* Using warp >= 3.2.17.
35−
36−## 3.2.4.2
1+## 3.2.4.1
372
383* Ignore socket errors while sending `close_notify` [#640](https://github.com/yesodweb/wai/issues/640)
394
40−## 3.2.4
41−
42−* Using tls-session-manager.
43−
44−## 3.2.3
45−
46−* Stop using obsoleted APIs of network.
47−
485## 3.2.2
496
507* New settting parameter: tlsServerDHEParams [#556](https://github.com/yesodweb/wai/pull/556)
warp-tls/Network/Wai/Handler/WarpTLS.hs+15−27
@@ -53,9 +53,6 @@ import qualified Data.IORef as I
5353import Data.Streaming.Network (bindPortTCP, safeRecv)
5454import Data.Typeable (Typeable)
5555import Network.Socket (Socket, close, withSocketsDo, SockAddr, accept)
56−#if MIN_VERSION_network(3,1,1)
57−import Network.Socket (gracefulClose)
58−#endif
5956import Network.Socket.ByteString (sendAll)
6057import qualified Network.TLS as TLS
6158import qualified Crypto.PubKey.DH as DH
@@ -281,7 +278,7 @@ runTLSSocket' :: TLSSettings -> Settings -> TLS.Credential -> TLS.SessionManager
281278runTLSSocket' tlsset@TLSSettings{..} set credential mgr sock app =
282279 runSettingsConnectionMakerSecure set get app
283280 where
284− get = getter tlsset set sock params
281+ get = getter tlsset sock params
285282 params = def { -- TLS.ServerParams
286283 TLS.serverWantClientCert = tlsWantClientCert
287284 , TLS.serverCACertificates = []
@@ -322,55 +319,49 @@ alpn xs
322319
323320----------------------------------------------------------------
324321
325−getter :: TLS.TLSParams params => TLSSettings -> Settings -> Socket -> params -> IO (IO (Connection, Transport), SockAddr)
326−getter tlsset@TLSSettings{..} set sock params = do
322+getter :: TLS.TLSParams params => TLSSettings -> Socket -> params -> IO (IO (Connection, Transport), SockAddr)
323+getter tlsset@TLSSettings{..} sock params = do
327324#if WINDOWS
328325 (s, sa) <- windowsThreadBlockHack $ accept sock
329326#else
330327 (s, sa) <- accept sock
331328#endif
332329 setSocketCloseOnExec s
333− return (mkConn tlsset set s params, sa)
330+ return (mkConn tlsset s params, sa)
334331
335−mkConn :: TLS.TLSParams params => TLSSettings -> Settings -> Socket -> params -> IO (Connection, Transport)
336−mkConn tlsset set s params = switch `onException` close s
332+mkConn :: TLS.TLSParams params => TLSSettings -> Socket -> params -> IO (Connection, Transport)
333+mkConn tlsset s params = switch `onException` close s
337334 where
338335 switch = do
339336 firstBS <- safeRecv s 4096
340337 if not (S.null firstBS) && S.head firstBS == 0x16 then
341− httpOverTls tlsset set s firstBS params
338+ httpOverTls tlsset s firstBS params
342339 else
343− plainHTTP tlsset set s firstBS
340+ plainHTTP tlsset s firstBS
344341
345342----------------------------------------------------------------
346343
347−httpOverTls :: TLS.TLSParams params => TLSSettings -> Settings -> Socket -> S.ByteString -> params -> IO (Connection, Transport)
348−httpOverTls TLSSettings{..} _set s bs0 params = do
344+httpOverTls :: TLS.TLSParams params => TLSSettings -> Socket -> S.ByteString -> params -> IO (Connection, Transport)
345+httpOverTls TLSSettings{..} s bs0 params = do
349346 recvN <- makePlainReceiveN s bs0
350347 ctx <- TLS.contextNew (backend recvN) params
351348 TLS.contextHookSetLogging ctx tlsLogging
352349 TLS.handshake ctx
353− h2 <- (== Just "h2") <$> TLS.getNegotiatedProtocol ctx
354− isH2 <- I.newIORef h2
355350 writeBuf <- allocateBuffer bufferSize
356351 -- Creating a cache for leftover input data.
357352 ref <- I.newIORef ""
358353 tls <- getTLSinfo ctx
359− return (conn ctx writeBuf ref isH2, tls)
354+ return (conn ctx writeBuf ref, tls)
360355 where
361356 backend recvN = TLS.Backend {
362357 TLS.backendFlush = return ()
363−#if MIN_VERSION_network(3,1,1)
364− , TLS.backendClose = gracefulClose s 5000 `E.catch` \(SomeException _) -> return ()
365−#else
366358 , TLS.backendClose = close s
367−#endif
368359 , TLS.backendSend = sendAll' s
369360 , TLS.backendRecv = recvN
370361 }
371362 sendAll' sock bs = sendAll sock bs `E.catch` \(SomeException _) ->
372363 throwIO ConnectionClosedByPeer
373− conn ctx writeBuf ref isH2 = Connection {
364+ conn ctx writeBuf ref = Connection {
374365 connSendMany = TLS.sendData ctx . L.fromChunks
375366 , connSendAll = sendall
376367 , connSendFile = sendfile
@@ -380,7 +371,6 @@ httpOverTls TLSSettings{..} _set s bs0 params = do
380371 , connRecvBuf = recvBuf ref
381372 , connWriteBuffer = writeBuf
382373 , connBufferSize = bufferSize
383− , connHTTP2 = isH2
384374 }
385375 where
386376 sendall = TLS.sendData ctx . L.fromChunks . return
@@ -468,13 +458,11 @@ getTLSinfo ctx = do
468458#if MIN_VERSION_tls(1,5,0)
469459 TLS.TLS13 -> (3,4)
470460#endif
471− clientCert <- TLS.getClientCertificateChain ctx
472461 return TLS {
473462 tlsMajorVersion = major
474463 , tlsMinorVersion = minor
475464 , tlsNegotiatedProtocol = proto
476465 , tlsChiperID = TLS.cipherID infoCipher
477− , tlsClientCertificate = clientCert
478466 }
479467
480468tryIO :: IO a -> IO (Either IOException a)
@@ -482,10 +470,10 @@ tryIO = try
482470
483471----------------------------------------------------------------
484472
485−plainHTTP :: TLSSettings -> Settings -> Socket -> S.ByteString -> IO (Connection, Transport)
486−plainHTTP TLSSettings{..} set s bs0 = case onInsecure of
473+plainHTTP :: TLSSettings -> Socket -> S.ByteString -> IO (Connection, Transport)
474+plainHTTP TLSSettings{..} s bs0 = case onInsecure of
487475 AllowInsecure -> do
488− conn' <- socketConnection set s
476+ conn' <- socketConnection s
489477 cachedRef <- I.newIORef bs0
490478 let conn'' = conn'
491479 { connRecv = recvPlain cachedRef (connRecv conn')
warp-tls/warp-tls.cabal+6−9
@@ -1,5 +1,5 @@
11Name: warp-tls
2−Version: 3.2.11
2+Version: 3.2.4.3
33Synopsis: HTTP over TLS support for Warp via the TLS package
44License: MIT
55License-file: LICENSE
@@ -8,7 +8,7 @@ Maintainer: michael@snoyman.com
88Homepage: http://github.com/yesodweb/wai
99Category: Web, Yesod
1010Build-Type: Simple
11−Cabal-Version: >= 1.10
11+Cabal-Version: >=1.6
1212Stability: Stable
1313description: SSLv1 and SSLv2 are obsoleted by IETF.
1414 We should use TLS 1.2 (or TLS 1.1 or TLS 1.0 if necessary).
@@ -18,23 +18,20 @@ description: SSLv1 and SSLv2 are obsoleted by IETF.
1818extra-source-files: ChangeLog.md README.md
1919
2020Library
21− Build-Depends: base >= 4.10 && < 5
21+ Build-Depends: base >= 4.8 && < 5
2222 , bytestring >= 0.9
2323 , wai >= 3.2 && < 3.3
24− , warp >= 3.3.6 && < 3.4
24+ , warp >= 3.2.17 && < 3.3
2525 , data-default-class >= 0.0.1
26− , tls >= 1.5.3
26+ , tls >= 1.3.10
2727 , cryptonite >= 0.12
2828 , network >= 2.2.1
2929 , streaming-commons
30− , tls-session-manager >= 0.0.4
30+ , tls-session-manager >= 0.0.0.1
3131 Exposed-modules: Network.Wai.Handler.WarpTLS
3232 ghc-options: -Wall
3333 if os(windows)
3434 Cpp-Options: -DWINDOWS
35− if impl(ghc >= 8)
36− Default-Extensions: Strict StrictData
37− Default-Language: Haskell2010
3835
3936
4037source-repository head
warp/ChangeLog.md−72
@@ -1,80 +1,8 @@
1−## 3.3.9
2−
3−* Don't insert Last-Modified: if exists.
4− [#791](https://github.com/yesodweb/wai/pull/791)
5−
6−## 3.3.8
7−
8−* Maximum header size is configurable.
9− [#781](https://github.com/yesodweb/wai/pull/781)
10−* Ignoring an exception from shutdown (gracefulClose).
11−
12−## 3.3.7
13−
14−* InvalidArgument (Bad file descriptor) is ignored in `receive`.
15− [#787](https://github.com/yesodweb/wai/pull/787)
16−
17−## 3.3.6
18−
19−* Fixing a bug of thread killed in the case of event source with
20− HTTP/2 (fixing #692 and #785)
21−* New APIs: clientCertificate to get client's certificate
22− [#783](https://github.com/yesodweb/wai/pull/783)
23−
24−## 3.3.5
25−
26−* New APIs: setGracefulCloseTimeout1 and setGracefulCloseTimeout2.
27− For HTTP/1.x, connections are closed immediately by default.
28− gracefullClose is used for HTTP/2 by default.
29− [#782](https://github.com/yesodweb/wai/pull/782)
30−
31−## 3.3.4
32−
33−* Setting isSecure of HTTP/2 correctly.
34−
35−## 3.3.3
36−
37−* Calling setOnException in HTTP/2.
38− [#771](https://github.com/yesodweb/wai/pull/771)
39−
40−## 3.3.2
41−
42−* Fixing a bug of HTTP/2 without fd cache.
43−
44−## 3.3.1
45−
46−* Using gracefullClose of network 3.1.1 or later if available.
47−* If the first line of an HTTP request is really invalid,
48− don't send an error response
49−
50−## 3.3.0
51−
52−* Switching from the original implementation to HTTP/2 server library.
53− [#754](https://github.com/yesodweb/wai/pull/754)
54−* Breaking change: The type of `http2dataTrailers` is now
55− `HTTP2Data -> TrailersMaker`.
56−
57−## 3.2.28
58−
59−* Using the Strict and StrictData language extensions for GHC >8.
60− [#752](https://github.com/yesodweb/wai/pull/752)
61−* System.TimeManager is now in a separate package: time-manager.
62− [#750](https://github.com/yesodweb/wai/pull/750)
63−* Fixing a bug of ALPN.
64−* Introducing the half closed state for HTTP/2.
65− [#717](https://github.com/yesodweb/wai/pull/717)
66−
671## 3.2.27
682
693* Internally, use `lookupEnv` instead of `getEnvironment` to get the
704 value of the `PORT` environment variable
715 [#736](https://github.com/yesodweb/wai/pull/736)
72−* Throw 413 for too large payload
73−* Throw 431 for too large headers
74− [#741](https://github.com/yesodweb/wai/pull/741)
75−* Use exception response handler in HTTP/2 & improve connection preservation
76− in HTTP/1.x if uncaught exceptions are thrown in an `Application`.
77− [#738](https://github.com/yesodweb/wai/pull/738)
786
797## 3.2.26
808
warp/Network/Wai/Handler/Warp.hs+2−52
@@ -72,9 +72,6 @@ module Network.Wai.Handler.Warp (
7272 , setLogger
7373 , setServerPushLogger
7474 , setGracefulShutdownTimeout
75− , setGracefulCloseTimeout1
76− , setGracefulCloseTimeout2
77− , setMaxTotalHeaderLength
7875 -- ** Getters
7976 , getPort
8077 , getHost
@@ -82,8 +79,6 @@ module Network.Wai.Handler.Warp (
8279 , getOnClose
8380 , getOnException
8481 , getGracefulShutdownTimeout
85− , getGracefulCloseTimeout1
86− , getGracefulCloseTimeout2
8782 -- ** Exception handler
8883 , defaultOnException
8984 , defaultShouldDisplayException
@@ -98,7 +93,6 @@ module Network.Wai.Handler.Warp (
9893 , pauseTimeout
9994 , FileInfo(..)
10095 , getFileInfo
101− , clientCertificate
10296 , withApplication
10397 , withApplicationSettings
10498 , testWithApplication
@@ -127,11 +121,9 @@ module Network.Wai.Handler.Warp (
127121import Control.Exception (SomeException, throwIO)
128122import Data.Streaming.Network (HostPreference)
129123import qualified Data.Vault.Lazy as Vault
130−import Data.X509
131124import qualified Network.HTTP.Types as H
132125import Network.Socket (SockAddr)
133126import Network.Wai (Request, Response, vault)
134−import System.TimeManager
135127
136128import Network.Wai.Handler.Warp.FileInfoCache
137129import Network.Wai.Handler.Warp.HTTP2.Request (getHTTP2Data, setHTTP2Data, modifyHTTP2Data)
@@ -141,6 +133,7 @@ import Network.Wai.Handler.Warp.Request
141133import Network.Wai.Handler.Warp.Response (warpVersion)
142134import Network.Wai.Handler.Warp.Run
143135import Network.Wai.Handler.Warp.Settings
136+import Network.Wai.Handler.Warp.Timeout
144137import Network.Wai.Handler.Warp.Types hiding (getFileInfo)
145138import Network.Wai.Handler.Warp.WithApplication
146139
@@ -385,7 +378,7 @@ setProxyProtocolRequired y = y { settingsProxyProtocol = ProxyProtocolRequired }
385378--
386379-- WARNING: This is contrary to the PROXY protocol specification and
387380-- using it can indicate a security problem with your
388−-- architecture if the web server is directly accessible
381+-- architecture if the web server is directly accessable
389382-- to the public, since it would allow easy IP address
390383-- spoofing. However, it can be useful in some cases,
391384-- such as if a load balancer health check uses regular
@@ -437,13 +430,6 @@ setGracefulShutdownTimeout :: Maybe Int
437430 -> Settings -> Settings
438431setGracefulShutdownTimeout time y = y { settingsGracefulShutdownTimeout = time }
439432
440−-- | Set the maximum header size that Warp will tolerate when using HTTP/1.x.
441−--
442−-- Since 3.3.8
443−setMaxTotalHeaderLength :: Int -> Settings -> Settings
444−setMaxTotalHeaderLength maxTotalHeaderLength settings = settings
445− { settingsMaxTotalHeaderLength = maxTotalHeaderLength }
446−
447433-- | Explicitly pause the slowloris timeout.
448434--
449435-- This is useful for cases where you partially consume a request body. For
@@ -471,39 +457,3 @@ pauseTimeout = fromMaybe (return ()) . Vault.lookup pauseTimeoutKey . vault
471457-- Since 3.1.10
472458getFileInfo :: Request -> FilePath -> IO FileInfo
473459getFileInfo = fromMaybe (\_ -> throwIO (userError "getFileInfo")) . Vault.lookup getFileInfoKey . vault
474−
475−-- | A timeout to limit the time (in milliseconds) waiting for
476−-- FIN for HTTP/1.x. 0 means uses immediate close.
477−-- Default: 0.
478−--
479−-- Since 3.3.5
480−setGracefulCloseTimeout1 :: Int -> Settings -> Settings
481−setGracefulCloseTimeout1 x y = y { settingsGracefulCloseTimeout1 = x }
482−
483−-- | A timeout to limit the time (in milliseconds) waiting for
484−-- FIN for HTTP/1.x. 0 means uses immediate close.
485−--
486−-- Since 3.3.5
487−getGracefulCloseTimeout1 :: Settings -> Int
488−getGracefulCloseTimeout1 = settingsGracefulCloseTimeout1
489−
490−-- | A timeout to limit the time (in milliseconds) waiting for
491−-- FIN for HTTP/2. 0 means uses immediate close.
492−-- Default: 2000.
493−--
494−-- Since 3.3.5
495−setGracefulCloseTimeout2 :: Int -> Settings -> Settings
496−setGracefulCloseTimeout2 x y = y { settingsGracefulCloseTimeout2 = x }
497−
498−-- | A timeout to limit the time (in milliseconds) waiting for
499−-- FIN for HTTP/2. 0 means uses immediate close.
500−--
501−-- Since 3.3.5
502−getGracefulCloseTimeout2 :: Settings -> Int
503−getGracefulCloseTimeout2 = settingsGracefulCloseTimeout2
504−
505−-- | Getting information of client certificate.
506−--
507−-- Since 3.3.5
508−clientCertificate :: Request -> Maybe CertificateChain
509−clientCertificate = join . Vault.lookup getClientCertificateKey . vault
warp/Network/Wai/Handler/Warp/FdCache.hs+22−18
@@ -17,24 +17,26 @@ module Network.Wai.Handler.Warp.FdCache (
1717import Control.Exception (bracket)
1818import Control.Reaper
1919import Data.IORef
20−import Network.Wai.Handler.Warp.MultiMap as MM
20+import Network.Wai.Handler.Warp.MultiMap
2121import System.Posix.IO (openFd, OpenFileFlags(..), defaultFileFlags, OpenMode(ReadOnly), closeFd, FdOption(CloseOnExec), setFdOption)
2222#endif
2323import System.Posix.Types (Fd)
2424
2525----------------------------------------------------------------
2626
27+type Hash = Int
28+
2729-- | An action to activate a Fd cache entry.
2830type Refresh = IO ()
2931
30−getFdNothing :: FilePath -> IO (Maybe Fd, Refresh)
31−getFdNothing _ = return (Nothing, return ())
32+getFdNothing :: Hash -> FilePath -> IO (Maybe Fd, Refresh)
33+getFdNothing _ _ = return (Nothing, return ())
3234
3335----------------------------------------------------------------
3436
3537-- | Creating 'MutableFdCache' and executing the action in the second
3638-- argument. The first argument is a cache duration in second.
37−withFdCache :: Int -> ((FilePath -> IO (Maybe Fd, Refresh)) -> IO a) -> IO a
39+withFdCache :: Int -> ((Hash -> FilePath -> IO (Maybe Fd, Refresh)) -> IO a) -> IO a
3840#ifdef WINDOWS
3941withFdCache _ action = action getFdNothing
4042#else
@@ -63,7 +65,7 @@ inactive (MutableStatus ref) = writeIORef ref Inactive
6365
6466----------------------------------------------------------------
6567
66−data FdEntry = FdEntry !Fd !MutableStatus
68+data FdEntry = FdEntry !FilePath !Fd !MutableStatus
6769
6870openFile :: FilePath -> IO Fd
6971openFile path = do
@@ -75,23 +77,25 @@ closeFile :: Fd -> IO ()
7577closeFile = closeFd
7678
7779newFdEntry :: FilePath -> IO FdEntry
78−newFdEntry path = FdEntry <$> openFile path <*> newActiveStatus
80+newFdEntry path = FdEntry path <$> openFile path <*> newActiveStatus
7981
8082setFileCloseOnExec :: Fd -> IO ()
8183setFileCloseOnExec fd = setFdOption fd CloseOnExec True
8284
8385----------------------------------------------------------------
8486
85−type FdCache = MultiMap FdEntry
87+type FdCache = MMap FdEntry
8688
8789-- | Mutable Fd cacher.
88−newtype MutableFdCache = MutableFdCache (Reaper FdCache (FilePath,FdEntry))
90+newtype MutableFdCache = MutableFdCache (Reaper FdCache (Hash, FdEntry))
8991
9092fdCache :: MutableFdCache -> IO FdCache
9193fdCache (MutableFdCache reaper) = reaperRead reaper
9294
93−look :: MutableFdCache -> FilePath -> IO (Maybe FdEntry)
94−look mfc path = MM.lookup path <$> fdCache mfc
95+look :: MutableFdCache -> FilePath -> Hash -> IO (Maybe FdEntry)
96+look mfc path key = searchWith key check <$> fdCache mfc
97+ where
98+ check (FdEntry path' _ _) = path == path'
9599
96100----------------------------------------------------------------
97101
@@ -112,7 +116,7 @@ clean old = do
112116 new <- pruneWith old prune
113117 return $ merge new
114118 where
115− prune (_,FdEntry fd mst) = status mst >>= act
119+ prune (FdEntry _ fd mst) = status mst >>= act
116120 where
117121 act Active = inactive mst >> return True
118122 act Inactive = closeFd fd >> return False
@@ -122,21 +126,21 @@ clean old = do
122126terminate :: MutableFdCache -> IO ()
123127terminate (MutableFdCache reaper) = do
124128 !t <- reaperStop reaper
125− mapM_ (closeIt . snd) $ toList t
129+ mapM_ closeIt $ toList t
126130 where
127− closeIt (FdEntry fd _) = closeFd fd
131+ closeIt (FdEntry _ fd _) = closeFd fd
128132
129133----------------------------------------------------------------
130134
131135-- | Getting 'Fd' and 'Refresh' from the mutable Fd cacher.
132−getFd :: MutableFdCache -> FilePath -> IO (Maybe Fd, Refresh)
133−getFd mfc@(MutableFdCache reaper) path = look mfc path >>= get
136+getFd :: MutableFdCache -> Hash -> FilePath -> IO (Maybe Fd, Refresh)
137+getFd mfc@(MutableFdCache reaper) h path = look mfc path h >>= get
134138 where
135139 get Nothing = do
136− ent@(FdEntry fd mst) <- newFdEntry path
137− reaperAdd reaper (path,ent)
140+ ent@(FdEntry _ fd mst) <- newFdEntry path
141+ reaperAdd reaper (h, ent)
138142 return (Just fd, refresh mst)
139− get (Just (FdEntry fd mst)) = do
143+ get (Just (FdEntry _ fd mst)) = do
140144 refresh mst
141145 return (Just fd, refresh mst)
142146#endif
warp/Network/Wai/Handler/Warp/File.hs+4−7
@@ -34,15 +34,12 @@ data RspFileInfo = WithoutBody H.Status
3434----------------------------------------------------------------
3535
3636conditionalRequest :: I.FileInfo
37− -> H.ResponseHeaders
38− -> IndexedHeader -- ^ Response
39− -> IndexedHeader -- ^ Request
37+ -> H.ResponseHeaders -> IndexedHeader
4038 -> RspFileInfo
41−conditionalRequest finfo hs0 rspidx reqidx = case condition of
39+conditionalRequest finfo hs0 reqidx = case condition of
4240 nobody@(WithoutBody _) -> nobody
43− WithBody s _ off len -> let !hs1 = addContentHeaders hs0 off len size
44− !hasLM = isJust $ rspidx ! fromEnum ResLastModified
45− !hs = [ (H.hLastModified,date) | not hasLM ] ++ hs1
41+ WithBody s _ off len -> let !hs = (H.hLastModified,date) :
42+ addContentHeaders hs0 off len size
4643 in WithBody s hs off len
4744 where
4845 !mtime = I.fileInfoTime finfo
warp/Network/Wai/Handler/Warp/FileInfoCache.hs+22−19
@@ -2,6 +2,7 @@
22
33module Network.Wai.Handler.Warp.FileInfoCache (
44 FileInfo(..)
5+ , Hash
56 , withFileInfoCache
67 , getInfo -- test purpose only
78 ) where
@@ -17,6 +18,8 @@ import Network.Wai.Handler.Warp.Imports
1718
1819----------------------------------------------------------------
1920
21+type Hash = Int
22+
2023-- | File information.
2124data FileInfo = FileInfo {
2225 fileInfoName :: !FilePath
@@ -26,8 +29,8 @@ data FileInfo = FileInfo {
2629 } deriving (Eq, Show)
2730
2831data Entry = Negative | Positive FileInfo
29−type Cache = HashMap Entry
30−type FileInfoCache = Reaper Cache (FilePath,Entry)
32+type Cache = HashMap FilePath Entry
33+type FileInfoCache = Reaper Cache (Int,FilePath,Entry)
3134
3235----------------------------------------------------------------
3336
@@ -51,29 +54,29 @@ getInfo path = do
5154 else
5255 throwIO (userError "FileInfoCache:getInfo")
5356
54−getInfoNaive :: FilePath -> IO FileInfo
55−getInfoNaive = getInfo
57+getInfoNaive :: Hash -> FilePath -> IO FileInfo
58+getInfoNaive _ = getInfo
5659
5760----------------------------------------------------------------
5861
59−getAndRegisterInfo :: FileInfoCache -> FilePath -> IO FileInfo
60−getAndRegisterInfo reaper@Reaper{..} path = do
62+getAndRegisterInfo :: FileInfoCache -> Hash -> FilePath -> IO FileInfo
63+getAndRegisterInfo reaper@Reaper{..} h path = do
6164 cache <- reaperRead
62− case M.lookup path cache of
65+ case M.lookup h path cache of
6366 Just Negative -> throwIO (userError "FileInfoCache:getAndRegisterInfo")
6467 Just (Positive x) -> return x
65− Nothing -> positive reaper path
66− `E.onException` negative reaper path
68+ Nothing -> positive reaper h path
69+ `E.onException` negative reaper h path
6770
68−positive :: FileInfoCache -> FilePath -> IO FileInfo
69−positive Reaper{..} path = do
71+positive :: FileInfoCache -> Hash -> FilePath -> IO FileInfo
72+positive Reaper{..} h path = do
7073 info <- getInfo path
71− reaperAdd (path, Positive info)
74+ reaperAdd (h, path, Positive info)
7275 return info
7376
74−negative :: FileInfoCache -> FilePath -> IO FileInfo
75−negative Reaper{..} path = do
76− reaperAdd (path, Negative)
77+negative :: FileInfoCache -> Hash -> FilePath -> IO FileInfo
78+negative Reaper{..} h path = do
79+ reaperAdd (h, path,Negative)
7780 throwIO (userError "FileInfoCache:negative")
7881
7982----------------------------------------------------------------
@@ -82,7 +85,7 @@ negative Reaper{..} path = do
8285-- and executing the action in the second argument.
8386-- The first argument is a cache duration in second.
8487withFileInfoCache :: Int
85− -> ((FilePath -> IO FileInfo) -> IO a)
88+ -> ((Hash -> FilePath -> IO FileInfo) -> IO a)
8689 -> IO a
8790withFileInfoCache 0 action = action getInfoNaive
8891withFileInfoCache duration action =
@@ -90,14 +93,14 @@ withFileInfoCache duration action =
9093 terminate
9194 (action . getAndRegisterInfo)
9295
93−initialize :: Int -> IO FileInfoCache
96+initialize :: Hash -> IO FileInfoCache
9497initialize duration = mkReaper settings
9598 where
9699 settings = defaultReaperSettings {
97100 reaperAction = override
98101 , reaperDelay = duration
99− , reaperCons = \(path,v) -> M.insert path v
100− , reaperNull = M.isEmpty
102+ , reaperCons = \(h,k,v) -> M.insert h k v
103+ , reaperNull = M.null
101104 , reaperEmpty = M.empty
102105 }
103106
warp/Network/Wai/Handler/Warp/HTTP2.hs+54−70
@@ -1,85 +1,69 @@
1−{-# LANGUAGE RecordWildCards #-}
2−{-# LANGUAGE BangPatterns #-}
31{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE PatternGuards #-}
5−{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE RecordWildCards #-}
63
7−module Network.Wai.Handler.Warp.HTTP2 (http2) where
4+module Network.Wai.Handler.Warp.HTTP2 (isHTTP2, http2) where
85
9−import qualified Data.IORef as I
6+import Control.Concurrent (forkIO, killThread)
107import qualified Control.Exception as E
11−import qualified Network.HTTP2.Server as H2
8+import Network.HTTP2
129import Network.Socket (SockAddr)
1310import Network.Wai
14−import Network.Wai.Internal (ResponseReceived(..))
15−import qualified System.TimeManager as T
1611
17−import Network.Wai.Handler.Warp.HTTP2.File
18−import Network.Wai.Handler.Warp.HTTP2.PushPromise
12+import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
13+import Network.Wai.Handler.Warp.HTTP2.Manager
14+import Network.Wai.Handler.Warp.HTTP2.Receiver
1915import Network.Wai.Handler.Warp.HTTP2.Request
20−import Network.Wai.Handler.Warp.HTTP2.Response
16+import Network.Wai.Handler.Warp.HTTP2.Sender
17+import Network.Wai.Handler.Warp.HTTP2.Types
18+import Network.Wai.Handler.Warp.HTTP2.Worker
2119import Network.Wai.Handler.Warp.Imports
22−import qualified Network.Wai.Handler.Warp.Settings as S
20+import qualified Network.Wai.Handler.Warp.Settings as S (Settings)
2321import Network.Wai.Handler.Warp.Types
2422
2523----------------------------------------------------------------
2624
27−http2 :: Connection -> Transport -> InternalInfo -> SockAddr -> S.Settings -> (BufSize -> IO ByteString) -> (ByteString -> IO ()) -> Application -> IO ()
28−http2 conn transport ii addr settings readN send app =
29− H2.run conf http2server
25+http2 :: Connection -> InternalInfo1 -> SockAddr -> Transport -> S.Settings -> (BufSize -> IO ByteString) -> Application -> IO ()
26+http2 conn ii1 addr transport settings readN app = do
27+ checkTLS
28+ ok <- checkPreface
29+ when ok $ do
30+ ctx <- newContext
31+ -- Workers, worker manager and timer manager
32+ mgr <- start settings
33+ let responder = response settings ctx mgr
34+ action = worker ctx settings app responder
35+ setAction mgr action
36+ -- The number of workers is 3.
37+ -- This was carefully chosen based on a lot of benchmarks.
38+ -- If it is 1, we cannot avoid head-of-line blocking.
39+ -- If it is large, huge memory is consumed and many
40+ -- context switches happen.
41+ replicateM_ 3 $ spawnAction mgr
42+ -- Receiver
43+ let mkreq = mkRequest ii1 settings addr
44+ tid <- forkIO $ frameReceiver ctx mkreq readN
45+ -- Sender
46+ -- frameSender is the main thread because it ensures to send
47+ -- a goway frame.
48+ frameSender ctx conn settings mgr `E.finally` do
49+ clearContext ctx
50+ stop mgr
51+ killThread tid
3052 where
31− conf = H2.Config {
32− confWriteBuffer = connWriteBuffer conn
33− , confBufferSize = connBufferSize conn
34− , confSendAll = send
35− , confReadN = readN
36− , confPositionReadMaker = pReadMaker ii
37− }
38−
39− http2server h2req aux response = do
40− req <- toWAIRequest h2req aux
41− ref <- I.newIORef Nothing
42− eResponseReceived <- E.try $ app req $ \rsp -> do
43− h2rsp <- fromResponse settings ii req rsp
44− pps <- fromPushPromises ii req
45− I.writeIORef ref $ Just (h2rsp, pps)
46− _ <- response h2rsp pps
47− return ResponseReceived
48− case eResponseReceived of
49− Right ResponseReceived -> do
50− Just (h2rsp, pps) <- I.readIORef ref
51− logResponse h2rsp req
52− mapM_ (logPushPromise req) pps
53− Left e@(E.SomeException _)
54− -- killed by the local worker manager
55− | Just E.ThreadKilled <- E.fromException e -> return ()
56− -- killed by the local timeout manager
57− | Just T.TimeoutThread <- E.fromException e -> return ()
58− | otherwise -> do
59− S.settingsOnException settings (Just req) e
60− let ersp = S.settingsOnExceptionResponse settings e
61− h2rsp' <- fromResponse settings ii req ersp
62− _ <- response h2rsp' []
63− logResponse h2rsp' req
64− return ()
53+ checkTLS = case transport of
54+ TCP -> return () -- direct
55+ tls -> unless (tls12orLater tls) $ goaway conn InadequateSecurity "Weak TLS"
56+ tls12orLater tls = tlsMajorVersion tls == 3 && tlsMinorVersion tls >= 3
57+ checkPreface = do
58+ preface <- readN connectionPrefaceLength
59+ if connectionPreface /= preface then do
60+ goaway conn ProtocolError "Preface mismatch"
61+ return False
62+ else
63+ return True
6564
66− toWAIRequest h2req aux = toRequest ii settings addr hdr bdylen bdy th transport
67− where
68− !hdr = H2.requestHeaders h2req
69− !bdy = H2.getRequestBodyChunk h2req
70− !bdylen = H2.requestBodySize h2req
71− !th = H2.auxTimeHandle aux
72−
73− logResponse h2rsp req = logger req st msiz
74− where
75− !logger = S.settingsLogger settings
76− !st = H2.responseStatus h2rsp
77− !msiz = fromIntegral <$> H2.responseBodySize h2rsp
78−
79− logPushPromise req pp = logger req path siz
80− where
81− !logger = S.settingsServerPushLogger settings
82− !path = H2.promiseRequestPath pp
83− !siz = case H2.responseBodySize $ H2.promiseResponse pp of
84− Nothing -> 0
85− Just s -> fromIntegral s
65+-- connClose must not be called here since Run:fork calls it
66+goaway :: Connection -> ErrorCodeId -> ByteString -> IO ()
67+goaway Connection{..} etype debugmsg = connSendAll bytestream
68+ where
69+ bytestream = goawayFrame 0 etype debugmsg
warp/Network/Wai/Handler/Warp/HTTP2/EncodeFrame.hsnew file+34
@@ -0,0 +1,34 @@
1+module Network.Wai.Handler.Warp.HTTP2.EncodeFrame where
2+
3+import Network.HTTP2
4+
5+import Network.Wai.Handler.Warp.Imports
6+
7+----------------------------------------------------------------
8+
9+goawayFrame :: StreamId -> ErrorCodeId -> ByteString -> ByteString
10+goawayFrame sid etype debugmsg = encodeFrame einfo frame
11+ where
12+ einfo = encodeInfo id 0
13+ frame = GoAwayFrame sid etype debugmsg
14+
15+resetFrame :: ErrorCodeId -> StreamId -> ByteString
16+resetFrame etype sid = encodeFrame einfo frame
17+ where
18+ einfo = encodeInfo id sid
19+ frame = RSTStreamFrame etype
20+
21+settingsFrame :: (FrameFlags -> FrameFlags) -> SettingsList -> ByteString
22+settingsFrame func alist = encodeFrame einfo $ SettingsFrame alist
23+ where
24+ einfo = encodeInfo func 0
25+
26+pingFrame :: ByteString -> ByteString
27+pingFrame bs = encodeFrame einfo $ PingFrame bs
28+ where
29+ einfo = encodeInfo setAck 0
30+
31+windowUpdateFrame :: StreamId -> WindowSize -> ByteString
32+windowUpdateFrame sid winsiz = encodeFrame einfo $ WindowUpdateFrame winsiz
33+ where
34+ einfo = encodeInfo id sid
warp/Network/Wai/Handler/Warp/HTTP2/File.hs+150−23
@@ -1,30 +1,157 @@
11{-# LANGUAGE CPP #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE BangPatterns #-}
24
3−module Network.Wai.Handler.Warp.HTTP2.File where
5+module Network.Wai.Handler.Warp.HTTP2.File (
6+ RspFileInfo(..)
7+ , conditionalRequest
8+ , addContentHeadersForFilePart
9+ , H.parseByteRanges
10+ ) where
411
5−import Network.HTTP2.Server
12+import qualified Data.ByteString.Char8 as C8 (pack)
13+import Network.HPACK
14+import Network.HPACK.Token
15+import Network.HTTP.Date
16+import qualified Network.HTTP.Types as H
17+import Network.Wai
618
7−import Network.Wai.Handler.Warp.Types
19+import qualified Network.Wai.Handler.Warp.FileInfoCache as I
20+import Network.Wai.Handler.Warp.Imports
21+import Network.Wai.Handler.Warp.PackInt
822
9−#ifdef WINDOWS
10−pReadMaker :: InternalInfo -> PositionReadMaker
11−pReadMaker _ = defaultPositionReadMaker
12−#else
13−import Network.Wai.Handler.Warp.FdCache
14−import Network.Wai.Handler.Warp.SendFile (positionRead)
23+-- $setup
24+-- >>> import Test.QuickCheck
1525
16−pReadMaker :: InternalInfo -> PositionReadMaker
17−pReadMaker ii path = do
18− (mfd, refresh) <- getFd ii path
19− case mfd of
20− Just fd -> return (pread fd, Refresher refresh)
21− Nothing -> do
22− fd <- openFile path
23− return (pread fd, Closer $ closeFile fd)
26+----------------------------------------------------------------
27+
28+data RspFileInfo = WithoutBody !H.Status
29+ | WithBody !H.Status !TokenHeaderList !Integer !Integer
30+ deriving (Eq,Show)
31+
32+----------------------------------------------------------------
33+
34+conditionalRequest :: I.FileInfo
35+ -> TokenHeaderList -- Response
36+ -> ValueTable -- Request
37+ -> RspFileInfo
38+conditionalRequest (I.FileInfo _ size mtime date) ths0 reqtbl = case condition of
39+ nobody@(WithoutBody _) -> nobody
40+ WithBody s _ off len -> let !hs = (tokenLastModified,date) :
41+ addContentHeaders ths0 off len size
42+ in WithBody s hs off len
43+ where
44+ !mcondition = ifmodified reqtbl size mtime
45+ <|> ifunmodified reqtbl size mtime
46+ <|> ifrange reqtbl size mtime
47+ !condition = fromMaybe (unconditional reqtbl size) mcondition
48+
49+----------------------------------------------------------------
50+
51+{-# INLINE ifModifiedSince #-}
52+ifModifiedSince :: ValueTable -> Maybe HTTPDate
53+ifModifiedSince reqtbl = getHeaderValue tokenIfModifiedSince reqtbl >>= parseHTTPDate
54+
55+{-# INLINE ifUnmodifiedSince #-}
56+ifUnmodifiedSince :: ValueTable -> Maybe HTTPDate
57+ifUnmodifiedSince reqtbl = getHeaderValue tokenIfUnmodifiedSince reqtbl >>= parseHTTPDate
58+
59+{-# INLINE ifRange #-}
60+ifRange :: ValueTable -> Maybe HTTPDate
61+ifRange reqtbl = getHeaderValue tokenIfRange reqtbl >>= parseHTTPDate
62+
63+----------------------------------------------------------------
64+
65+{-# INLINE ifmodified #-}
66+ifmodified :: ValueTable -> Integer -> HTTPDate -> Maybe RspFileInfo
67+ifmodified reqtbl size mtime = do
68+ date <- ifModifiedSince reqtbl
69+ return $ if date /= mtime
70+ then unconditional reqtbl size
71+ else WithoutBody H.notModified304
72+
73+{-# INLINE ifunmodified #-}
74+ifunmodified :: ValueTable -> Integer -> HTTPDate -> Maybe RspFileInfo
75+ifunmodified reqtbl size mtime = do
76+ date <- ifUnmodifiedSince reqtbl
77+ return $ if date == mtime
78+ then unconditional reqtbl size
79+ else WithoutBody H.preconditionFailed412
80+
81+{-# INLINE ifrange #-}
82+ifrange :: ValueTable -> Integer -> HTTPDate -> Maybe RspFileInfo
83+ifrange reqtbl size mtime = do
84+ date <- ifRange reqtbl
85+ rng <- getHeaderValue tokenRange reqtbl
86+ return $ if date == mtime
87+ then parseRange rng size
88+ else WithBody H.ok200 [] 0 size
89+
90+{-# INLINE unconditional #-}
91+unconditional :: ValueTable -> Integer -> RspFileInfo
92+unconditional reqtbl size = case getHeaderValue tokenRange reqtbl of
93+ Nothing -> WithBody H.ok200 [] 0 size
94+ Just rng -> parseRange rng size
95+
96+----------------------------------------------------------------
97+
98+{-# INLINE parseRange #-}
99+parseRange :: ByteString -> Integer -> RspFileInfo
100+parseRange rng size = case H.parseByteRanges rng of
101+ Nothing -> WithoutBody H.requestedRangeNotSatisfiable416
102+ Just [] -> WithoutBody H.requestedRangeNotSatisfiable416
103+ Just (r:_) -> let (!beg, !end) = checkRange r size
104+ !len = end - beg + 1
105+ s = if beg == 0 && end == size - 1 then
106+ H.ok200
107+ else
108+ H.partialContent206
109+ in WithBody s [] beg len
110+
111+{-# INLINE checkRange #-}
112+checkRange :: H.ByteRange -> Integer -> (Integer, Integer)
113+checkRange (H.ByteRangeFrom beg) size = (beg, size - 1)
114+checkRange (H.ByteRangeFromTo beg end) size = (beg, min (size - 1) end)
115+checkRange (H.ByteRangeSuffix count) size = (max 0 (size - count), size - 1)
116+
117+----------------------------------------------------------------
118+
119+{-# INLINE contentRangeHeader #-}
120+-- | @contentRangeHeader beg end total@ constructs a Content-Range 'H.Header'
121+-- for the range specified.
122+contentRangeHeader :: Integer -> Integer -> Integer -> TokenHeader
123+contentRangeHeader beg end total = (tokenContentRange, range)
124+ where
125+ range = C8.pack
126+ -- building with ShowS
127+ $ 'b' : 'y': 't' : 'e' : 's' : ' '
128+ : (if beg > end then ('*':) else
129+ showInt beg
130+ . ('-' :)
131+ . showInt end)
132+ ( '/'
133+ : showInt total "")
134+
135+{-# INLINE addContentHeaders #-}
136+addContentHeaders :: TokenHeaderList -> Integer -> Integer -> Integer -> TokenHeaderList
137+addContentHeaders ths off len size
138+ | len == size = ths'
139+ | otherwise = let !ctrng = contentRangeHeader off (off + len - 1) size
140+ in ctrng:ths'
141+ where
142+ !lengthBS = packIntegral len
143+ !ths' = (tokenContentLength, lengthBS) : (tokenAcceptRanges,"bytes") : ths
144+
145+{-# INLINE addContentHeadersForFilePart #-}
146+-- |
147+--
148+-- >>> addContentHeadersForFilePart [] (FilePart 2 10 16)
149+-- [(Token {ix = 20, shouldBeIndexed = True, isPseudo = False, tokenKey = "Content-Range"},"bytes 2-11/16"),(Token {ix = 18, shouldBeIndexed = False, isPseudo = False, tokenKey = "Content-Length"},"10"),(Token {ix = 8, shouldBeIndexed = True, isPseudo = False, tokenKey = "Accept-Ranges"},"bytes")]
150+-- >>> addContentHeadersForFilePart [] (FilePart 0 16 16)
151+-- [(Token {ix = 18, shouldBeIndexed = False, isPseudo = False, tokenKey = "Content-Length"},"16"),(Token {ix = 8, shouldBeIndexed = True, isPseudo = False, tokenKey = "Accept-Ranges"},"bytes")]
152+addContentHeadersForFilePart :: TokenHeaderList -> FilePart -> TokenHeaderList
153+addContentHeadersForFilePart hs part = addContentHeaders hs off len size
24154 where
25− pread :: Fd -> PositionRead
26− pread fd off bytes buf = fromIntegral <$> positionRead fd buf bytes' off'
27− where
28− bytes' = fromIntegral bytes
29− off' = fromIntegral off
30−#endif
155+ off = filePartOffset part
156+ len = filePartByteCount part
157+ size = filePartFileSize part
warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hsnew file+130
@@ -0,0 +1,130 @@
1+{-# LANGUAGE OverloadedStrings, BangPatterns #-}
2+{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}
3+
4+module Network.Wai.Handler.Warp.HTTP2.HPACK (
5+ hpackEncodeHeader
6+ , hpackEncodeHeaderLoop
7+ , hpackDecodeHeader
8+ , just
9+ , fixHeaders
10+ , addHeader -- testing
11+ , deleteUnnecessaryHeaders
12+ ) where
13+
14+import qualified Control.Exception as E
15+import Network.HPACK hiding (Buffer)
16+import Network.HPACK.Token
17+import Network.HTTP2
18+
19+import Network.Wai.Handler.Warp.HTTP2.Types
20+import Network.Wai.Handler.Warp.Imports
21+import Network.Wai.Handler.Warp.PackInt
22+import qualified Network.Wai.Handler.Warp.Settings as S
23+import Network.Wai.Handler.Warp.Types
24+
25+-- $setup
26+-- >>> :set -XOverloadedStrings
27+
28+----------------------------------------------------------------
29+
30+{-# INLINE addHeader #-}
31+addHeader :: Token -> ByteString -> ValueTable -> TokenHeaderList -> TokenHeaderList
32+addHeader t "" _ ths
33+ | t == tokenServer = filter ((/= tokenServer) . fst) ths
34+addHeader t v tbl ths = case getHeaderValue t tbl of
35+ Nothing -> (t,v) : ths
36+ _ -> ths
37+
38+fixHeaders :: Context
39+ -> Rspn
40+ -> InternalInfo
41+ -> S.Settings
42+ -> IO TokenHeaderList
43+fixHeaders Context{..} rspn ii settings = do
44+ date <- getDate ii
45+ let !s = rspnStatus rspn
46+ !status = packStatus s
47+ !defServer = S.settingsServerName settings
48+ (!ths0,tbl) = rspnHeaders rspn
49+ !ths1 = addHeader tokenServer defServer tbl ths0
50+ !ths2 = addHeader tokenDate date tbl ths1
51+ !ths3 = (tokenStatus, status) : ths2
52+ !ths4 = deleteUnnecessaryHeaders ths3 tbl
53+ return ths4
54+
55+deleteUnnecessaryHeaders :: TokenHeaderList -> ValueTable -> TokenHeaderList
56+deleteUnnecessaryHeaders htl0 tbl0 = loop headersToBeRemoved htl0
57+ where
58+ loop [] thl = thl
59+ loop (t:ts) thl = case getHeaderValue t tbl0 of
60+ Nothing -> thl
61+ _ -> let !thl' = filter ((/= t) . fst) thl
62+ in loop ts thl'
63+
64+headersToBeRemoved :: [Token]
65+headersToBeRemoved = [ tokenConnection
66+ , tokenTransferEncoding
67+ -- Keep-Alive:
68+ -- Proxy-Connection:
69+ -- Upgrade:
70+ ]
71+
72+----------------------------------------------------------------
73+
74+strategy :: EncodeStrategy
75+strategy = EncodeStrategy { compressionAlgo = Linear, useHuffman = False }
76+
77+-- Set-Cookie: contains only one cookie value.
78+-- So, we don't need to split it.
79+hpackEncodeHeader :: Context -> Buffer -> BufSize
80+ -> TokenHeaderList
81+ -> IO (TokenHeaderList, Int)
82+hpackEncodeHeader Context{..} buf siz ths =
83+ encodeTokenHeader buf siz strategy True encodeDynamicTable ths
84+
85+hpackEncodeHeaderLoop :: Context -> Buffer -> BufSize
86+ -> TokenHeaderList
87+ -> IO (TokenHeaderList, Int)
88+hpackEncodeHeaderLoop Context{..} buf siz hs =
89+ encodeTokenHeader buf siz strategy False encodeDynamicTable hs
90+
91+----------------------------------------------------------------
92+
93+hpackDecodeHeader :: HeaderBlockFragment -> Context -> IO (TokenHeaderList, ValueTable)
94+hpackDecodeHeader hdrblk Context{..} = do
95+ tbl@(_,vt) <- decodeTokenHeader decodeDynamicTable hdrblk `E.catch` handl
96+ unless (checkRequestHeader vt) $
97+ E.throwIO $ ConnectionError ProtocolError "the header key is illegal"
98+ return tbl
99+ where
100+ handl IllegalHeaderName =
101+ E.throwIO $ ConnectionError ProtocolError "the header key is illegal"
102+ handl _ =
103+ E.throwIO $ ConnectionError CompressionError "cannot decompress the header"
104+
105+{-# INLINE checkRequestHeader #-}
106+checkRequestHeader :: ValueTable -> Bool
107+checkRequestHeader reqvt
108+ | just mMethod (== "CONNECT") = isNothing mPath && isNothing mScheme
109+ | isJust mStatus = False
110+ | isNothing mMethod = False
111+ | isNothing mScheme = False
112+ | isNothing mPath = False
113+ | mPath == Just "" = False
114+ | isJust mConnection = False
115+ | just mTE (/= "trailers") = False
116+ | otherwise = True
117+ where
118+ mStatus = getHeaderValue tokenStatus reqvt
119+ mScheme = getHeaderValue tokenScheme reqvt
120+ mPath = getHeaderValue tokenPath reqvt
121+ mMethod = getHeaderValue tokenMethod reqvt
122+ mConnection = getHeaderValue tokenConnection reqvt
123+ mTE = getHeaderValue tokenTE reqvt
124+
125+{-# INLINE just #-}
126+just :: Maybe a -> (a -> Bool) -> Bool
127+just Nothing _ = False
128+just (Just x) p
129+ | p x = True
130+ | otherwise = False
warp/Network/Wai/Handler/Warp/HTTP2/Manager.hsnew file+95
@@ -0,0 +1,95 @@
1+{-# LANGUAGE CPP, BangPatterns #-}
2+
3+-- | A thread pool manager.
4+-- The manager has responsibility to spawn and kill
5+-- worker threads.
6+module Network.Wai.Handler.Warp.HTTP2.Manager (
7+ Manager
8+ , start
9+ , setAction
10+ , stop
11+ , spawnAction
12+ , addMyId
13+ , deleteMyId
14+ ) where
15+
16+import Control.Concurrent
17+import Control.Concurrent.STM
18+import Data.Foldable
19+import Data.IORef
20+import Data.Set (Set)
21+import qualified Data.Set as Set
22+
23+import Network.Wai.Handler.Warp.Imports
24+import Network.Wai.Handler.Warp.Settings
25+import qualified Network.Wai.Handler.Warp.Timeout as T
26+
27+----------------------------------------------------------------
28+
29+type Action = T.Manager -> IO ()
30+
31+data Command = Stop | Spawn | Add ThreadId | Delete ThreadId
32+
33+data Manager = Manager (TQueue Command) (IORef Action)
34+
35+-- | Starting a thread pool manager.
36+-- Its action is initially set to 'return ()' and should be set
37+-- by 'setAction'. This allows that the action can include
38+-- the manager itself.
39+start :: Settings -> IO Manager
40+start set = do
41+ q <- newTQueueIO
42+ ref <- newIORef (\_ -> return ())
43+ timmgr <- T.initialize $ settingsTimeout set * 1000000
44+ void $ forkIO $ go q Set.empty ref timmgr
45+ return $ Manager q ref
46+ where
47+ go q !tset0 ref timmgr = do
48+ x <- atomically $ readTQueue q
49+ case x of
50+ Stop -> kill tset0 >> T.killManager timmgr
51+ Spawn -> next tset0
52+ Add newtid -> let !tset = add newtid tset0
53+ in go q tset ref timmgr
54+ Delete oldtid -> let !tset = del oldtid tset0
55+ in go q tset ref timmgr
56+ where
57+ next tset = do
58+ action <- readIORef ref
59+ newtid <- forkIO (action timmgr)
60+ let !tset' = add newtid tset
61+ go q tset' ref timmgr
62+
63+setAction :: Manager -> Action -> IO ()
64+setAction (Manager _ ref) action = writeIORef ref action
65+
66+stop :: Manager -> IO ()
67+stop (Manager q _) = atomically $ writeTQueue q Stop
68+
69+spawnAction :: Manager -> IO ()
70+spawnAction (Manager q _) = atomically $ writeTQueue q Spawn
71+
72+addMyId :: Manager -> IO ()
73+addMyId (Manager q _) = do
74+ tid <- myThreadId
75+ atomically $ writeTQueue q $ Add tid
76+
77+deleteMyId :: Manager -> IO ()
78+deleteMyId (Manager q _) = do
79+ tid <- myThreadId
80+ atomically $ writeTQueue q $ Delete tid
81+
82+----------------------------------------------------------------
83+
84+add :: ThreadId -> Set ThreadId -> Set ThreadId
85+add tid set = set'
86+ where
87+ !set' = Set.insert tid set
88+
89+del :: ThreadId -> Set ThreadId -> Set ThreadId
90+del tid set = set'
91+ where
92+ !set' = Set.delete tid set
93+
94+kill :: Set ThreadId -> IO ()
95+kill set = traverse_ killThread set
warp/Network/Wai/Handler/Warp/HTTP2/PushPromise.hsdeleted−36
@@ -1,36 +0,0 @@
1−{-# LANGUAGE BangPatterns #-}
2−{-# LANGUAGE RecordWildCards #-}
3−{-# LANGUAGE ScopedTypeVariables #-}
4−
5−module Network.Wai.Handler.Warp.HTTP2.PushPromise where
6−
7−import qualified Control.Exception as E
8−import qualified Network.HTTP.Types as H
9−import qualified Network.HTTP2.Server as H2
10−
11−import Network.Wai
12−import Network.Wai.Handler.Warp.FileInfoCache
13−import Network.Wai.Handler.Warp.HTTP2.Request (getHTTP2Data)
14−import Network.Wai.Handler.Warp.HTTP2.Types
15−import Network.Wai.Handler.Warp.Imports
16−import Network.Wai.Handler.Warp.Types
17−
18−fromPushPromises :: InternalInfo -> Request -> IO [H2.PushPromise]
19−fromPushPromises ii req = do
20− mh2data <- getHTTP2Data req
21− let pp = case mh2data of
22− Nothing -> []
23− Just h2data -> http2dataPushPromise h2data
24− catMaybes <$> mapM (fromPushPromise ii) pp
25−
26−fromPushPromise :: InternalInfo -> PushPromise -> IO (Maybe H2.PushPromise)
27−fromPushPromise ii (PushPromise path file rsphdr w) = do
28− efinfo <- E.try $ getFileInfo ii file
29− case efinfo of
30− Left (_ex :: E.IOException) -> return Nothing
31− Right finfo -> do
32− let !siz = fromIntegral $ fileInfoSize finfo
33− !fileSpec = H2.FileSpec file 0 siz
34− !rsp = H2.responseFile H.ok200 rsphdr fileSpec
35− !pp = H2.pushPromise path rsp w
36− return $ Just pp
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hsnew file+398
@@ -0,0 +1,398 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE NamedFieldPuns #-}
3+{-# LANGUAGE BangPatterns #-}
4+{-# LANGUAGE CPP #-}
5+{-# LANGUAGE PatternGuards #-}
6+
7+module Network.Wai.Handler.Warp.HTTP2.Receiver (frameReceiver) where
8+
9+import Control.Concurrent
10+import Control.Concurrent.STM
11+import qualified Control.Exception as E
12+import qualified Data.ByteString as BS
13+import qualified Data.ByteString.Char8 as C8
14+import Data.IORef
15+import Network.HPACK
16+import Network.HPACK.Token
17+import Network.HTTP2
18+import Network.HTTP2.Priority (toPrecedence, delete, prepare)
19+
20+import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
21+import Network.Wai.Handler.Warp.HTTP2.HPACK
22+import Network.Wai.Handler.Warp.HTTP2.Request
23+import Network.Wai.Handler.Warp.HTTP2.Types
24+import Network.Wai.Handler.Warp.Imports hiding (delete, insert, readInt)
25+import Network.Wai.Handler.Warp.ReadInt
26+import Network.Wai.Handler.Warp.Types
27+
28+----------------------------------------------------------------
29+
30+frameReceiver :: Context -> MkReq -> (BufSize -> IO ByteString) -> IO ()
31+frameReceiver ctx mkreq recvN = loop 0 `E.catch` sendGoaway
32+ where
33+ Context{ http2settings
34+ , streamTable
35+ , concurrency
36+ , continued
37+ , clientStreamId
38+ , inputQ
39+ , controlQ
40+ } = ctx
41+ sendGoaway e
42+ | Just (ConnectionError err msg) <- E.fromException e = do
43+ csid <- readIORef clientStreamId
44+ let !frame = goawayFrame csid err msg
45+ enqueueControl controlQ $ CGoaway frame
46+ | otherwise = return ()
47+
48+ sendReset err sid = do
49+ let !frame = resetFrame err sid
50+ enqueueControl controlQ $ CFrame frame
51+
52+ loop :: Int -> IO ()
53+ loop !n
54+ | n == 6 = do
55+ yield
56+ loop 0
57+ | otherwise = do
58+ hd <- recvN frameHeaderLength
59+ if BS.null hd then
60+ enqueueControl controlQ CFinish
61+ else do
62+ cont <- processStreamGuardingError $ decodeFrameHeader hd
63+ when cont $ loop (n + 1)
64+
65+ processStreamGuardingError (fid, FrameHeader{streamId})
66+ | isResponse streamId &&
67+ (fid `notElem` [FramePriority,FrameRSTStream,FrameWindowUpdate]) =
68+ E.throwIO $ ConnectionError ProtocolError "stream id should be odd"
69+ processStreamGuardingError (FrameUnknown _, FrameHeader{payloadLength}) = do
70+ mx <- readIORef continued
71+ case mx of
72+ Nothing -> do
73+ -- ignoring unknown frame
74+ consume payloadLength
75+ return True
76+ Just _ -> E.throwIO $ ConnectionError ProtocolError "unknown frame"
77+ processStreamGuardingError (FramePushPromise, _) =
78+ E.throwIO $ ConnectionError ProtocolError "push promise is not allowed"
79+ processStreamGuardingError typhdr@(ftyp, header@FrameHeader{payloadLength}) = do
80+ settings <- readIORef http2settings
81+ case checkFrameHeader settings typhdr of
82+ Left h2err -> case h2err of
83+ StreamError err sid -> do
84+ sendReset err sid
85+ consume payloadLength
86+ return True
87+ connErr -> E.throwIO connErr
88+ Right _ -> do
89+ ex <- E.try $ controlOrStream ftyp header
90+ case ex of
91+ Left (StreamError err sid) -> do
92+ sendReset err sid
93+ return True
94+ Left connErr -> E.throw connErr
95+ Right cont -> return cont
96+
97+ controlOrStream ftyp header@FrameHeader{streamId, payloadLength}
98+ | isControl streamId = do
99+ pl <- recvN payloadLength
100+ control ftyp header pl ctx
101+ | otherwise = do
102+ checkContinued
103+ !mstrm <- getStream
104+ pl <- recvN payloadLength
105+ case mstrm of
106+ Nothing -> do
107+ -- for h2spec only
108+ when (ftyp == FramePriority) $ do
109+ PriorityFrame newpri <- guardIt $ decodePriorityFrame header pl
110+ checkPriority newpri streamId
111+ return True -- just ignore this frame
112+ Just strm@Stream{streamState,streamPrecedence} -> do
113+ state <- readIORef streamState
114+ state' <- stream ftyp header pl ctx state strm
115+ case state' of
116+ Open (NoBody tbl@(_,reqvt) pri) -> do
117+ resetContinued
118+ let mcl = readInt <$> getHeaderValue tokenContentLength reqvt
119+ when (just mcl (/= (0 :: Int))) $
120+ E.throwIO $ StreamError ProtocolError streamId
121+ writeIORef streamPrecedence $ toPrecedence pri
122+ writeIORef streamState HalfClosed
123+ (!req, !ii) <- mkreq tbl (Just 0, return "")
124+ atomically $ writeTQueue inputQ $ Input strm req reqvt ii
125+ Open (HasBody tbl@(_,reqvt) pri) -> do
126+ resetContinued
127+ q <- newTQueueIO
128+ let !mcl = readInt <$> getHeaderValue tokenContentLength reqvt
129+ writeIORef streamPrecedence $ toPrecedence pri
130+ bodyLength <- newIORef 0
131+ writeIORef streamState $ Open (Body q mcl bodyLength)
132+ readQ <- newReadBody q
133+ bodySource <- mkSource readQ
134+ (!req, !ii) <- mkreq tbl (mcl, readSource bodySource)
135+ atomically $ writeTQueue inputQ $ Input strm req reqvt ii
136+ s@(Open Continued{}) -> do
137+ setContinued
138+ writeIORef streamState s
139+ s -> do -- Idle, Open Body, HalfClosed, Closed
140+ resetContinued
141+ writeIORef streamState s
142+ return True
143+ where
144+ setContinued = writeIORef continued (Just streamId)
145+ resetContinued = writeIORef continued Nothing
146+ checkContinued = do
147+ mx <- readIORef continued
148+ case mx of
149+ Nothing -> return ()
150+ Just sid
151+ | sid == streamId && ftyp == FrameContinuation -> return ()
152+ | otherwise -> E.throwIO $ ConnectionError ProtocolError "continuation frame must follow"
153+ getStream = do
154+ mstrm0 <- search streamTable streamId
155+ case mstrm0 of
156+ js@(Just strm0) -> do
157+ when (ftyp == FrameHeaders) $ do
158+ st <- readIORef $ streamState strm0
159+ when (isHalfClosed st) $ E.throwIO $ ConnectionError StreamClosed "header must not be sent to half closed"
160+ -- Priority made an idele stream
161+ when (isIdle st) $ opened ctx strm0
162+ return js
163+ Nothing
164+ | isResponse streamId -> return Nothing
165+ | otherwise -> do
166+ csid <- readIORef clientStreamId
167+ if streamId <= csid then -- consider the stream closed
168+ if ftyp `elem` [FrameWindowUpdate, FrameRSTStream, FramePriority] then
169+ return Nothing -- will be ignored
170+ else
171+ E.throwIO $ ConnectionError ProtocolError "stream identifier must not decrease"
172+ else do -- consider the stream idle
173+ when (ftyp `notElem` [FrameHeaders,FramePriority]) $
174+ E.throwIO $ ConnectionError ProtocolError $ "this frame is not allowed in an idel stream: " `BS.append` C8.pack (show ftyp)
175+ when (ftyp == FrameHeaders) $ do
176+ writeIORef clientStreamId streamId
177+ cnt <- readIORef concurrency
178+ -- Checking the limitation of concurrency
179+ when (cnt >= maxConcurrency) $
180+ E.throwIO $ StreamError RefusedStream streamId
181+ ws <- initialWindowSize <$> readIORef http2settings
182+ newstrm <- newStream streamId (fromIntegral ws)
183+ when (ftyp == FrameHeaders) $ opened ctx newstrm
184+ insert streamTable streamId newstrm
185+ return $ Just newstrm
186+
187+ consume = void . recvN
188+
189+maxConcurrency :: Int
190+maxConcurrency = recommendedConcurrency
191+
192+initialFrame :: ByteString
193+initialFrame = settingsFrame id [(SettingsMaxConcurrentStreams,maxConcurrency)]
194+
195+----------------------------------------------------------------
196+
197+control :: FrameTypeId -> FrameHeader -> ByteString -> Context -> IO Bool
198+control FrameSettings header@FrameHeader{flags} bs Context{http2settings, controlQ, firstSettings, streamTable} = do
199+ SettingsFrame alist <- guardIt $ decodeSettingsFrame header bs
200+ case checkSettingsList alist of
201+ Just x -> E.throwIO x
202+ Nothing -> return ()
203+ -- HTTP/2 Setting from a browser
204+ unless (testAck flags) $ do
205+ oldws <- initialWindowSize <$> readIORef http2settings
206+ modifyIORef' http2settings $ \old -> updateSettings old alist
207+ newws <- initialWindowSize <$> readIORef http2settings
208+ let diff = newws - oldws
209+ when (diff /= 0) $ updateAllStreamWindow (+ diff) streamTable
210+ let !frame = settingsFrame setAck []
211+ sent <- readIORef firstSettings
212+ let !setframe
213+ | sent = CSettings frame alist
214+ | otherwise = CSettings0 initialFrame frame alist
215+ unless sent $ writeIORef firstSettings True
216+ enqueueControl controlQ setframe
217+ return True
218+
219+control FramePing FrameHeader{flags} bs Context{controlQ} =
220+ if testAck flags then
221+ return True -- just ignore
222+ else do
223+ let !frame = pingFrame bs
224+ enqueueControl controlQ $ CFrame frame
225+ return True
226+
227+control FrameGoAway _ _ Context{controlQ} = do
228+ enqueueControl controlQ CFinish
229+ return False
230+
231+control FrameWindowUpdate header bs Context{connectionWindow} = do
232+ WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
233+ !w <- atomically $ do
234+ w0 <- readTVar connectionWindow
235+ let !w1 = w0 + n
236+ writeTVar connectionWindow w1
237+ return w1
238+ when (isWindowOverflow w) $ E.throwIO $ ConnectionError FlowControlError "control window should be less than 2^31"
239+ return True
240+
241+control _ _ _ _ =
242+ -- must not reach here
243+ return False
244+
245+----------------------------------------------------------------
246+
247+{-# INLINE guardIt #-}
248+guardIt :: Either HTTP2Error a -> IO a
249+guardIt x = case x of
250+ Left err -> E.throwIO err
251+ Right frame -> return frame
252+
253+
254+{-# INLINE checkPriority #-}
255+checkPriority :: Priority -> StreamId -> IO ()
256+checkPriority p me
257+ | dep == me = E.throwIO $ StreamError ProtocolError me
258+ | otherwise = return ()
259+ where
260+ dep = streamDependency p
261+
262+stream :: FrameTypeId -> FrameHeader -> ByteString -> Context -> StreamState -> Stream -> IO StreamState
263+stream FrameHeaders header@FrameHeader{flags} bs ctx (Open JustOpened) Stream{streamNumber} = do
264+ HeadersFrame mp frag <- guardIt $ decodeHeadersFrame header bs
265+ pri <- case mp of
266+ Nothing -> return defaultPriority
267+ Just p -> do
268+ checkPriority p streamNumber
269+ return p
270+ let !endOfStream = testEndStream flags
271+ !endOfHeader = testEndHeader flags
272+ if endOfHeader then do
273+ tbl <- hpackDecodeHeader frag ctx -- fixme
274+ return $ if endOfStream then
275+ Open (NoBody tbl pri)
276+ else
277+ Open (HasBody tbl pri)
278+ else do
279+ let !siz = BS.length frag
280+ return $ Open $ Continued [frag] siz 1 endOfStream pri
281+
282+stream FrameHeaders header@FrameHeader{flags} bs _ (Open (Body q _ _)) _ = do
283+ -- trailer is not supported.
284+ -- let's read and ignore it.
285+ HeadersFrame _ _ <- guardIt $ decodeHeadersFrame header bs
286+ let !endOfStream = testEndStream flags
287+ if endOfStream then do
288+ atomically $ writeTQueue q ""
289+ return HalfClosed
290+ else
291+ -- we don't support continuation here.
292+ E.throwIO $ ConnectionError ProtocolError "continuation in trailer is not supported"
293+
294+stream FrameData
295+ header@FrameHeader{flags,payloadLength,streamId}
296+ bs
297+ Context{controlQ} s@(Open (Body q mcl bodyLength))
298+ Stream{streamNumber} = do
299+ DataFrame body <- guardIt $ decodeDataFrame header bs
300+ let !endOfStream = testEndStream flags
301+ len0 <- readIORef bodyLength
302+ let !len = len0 + payloadLength
303+ writeIORef bodyLength len
304+ when (payloadLength /= 0) $ do
305+ let !frame1 = windowUpdateFrame 0 payloadLength
306+ !frame2 = windowUpdateFrame streamNumber payloadLength
307+ !frame = frame1 `BS.append` frame2
308+ enqueueControl controlQ $ CFrame frame
309+ atomically $ writeTQueue q body
310+ if endOfStream then do
311+ case mcl of
312+ Nothing -> return ()
313+ Just cl -> when (cl /= len) $ E.throwIO $ StreamError ProtocolError streamId
314+ atomically $ writeTQueue q ""
315+ return HalfClosed
316+ else
317+ return s
318+
319+stream FrameContinuation FrameHeader{flags} frag ctx (Open (Continued rfrags siz n endOfStream pri)) _ = do
320+ let !endOfHeader = testEndHeader flags
321+ !rfrags' = frag : rfrags
322+ !siz' = siz + BS.length frag
323+ !n' = n + 1
324+ when (siz' > 51200) $ -- fixme: hard coding: 50K
325+ E.throwIO $ ConnectionError EnhanceYourCalm "Header is too big"
326+ when (n' > 10) $ -- fixme: hard coding
327+ E.throwIO $ ConnectionError EnhanceYourCalm "Header is too fragmented"
328+ if endOfHeader then do
329+ let !hdrblk = BS.concat $ reverse rfrags'
330+ tbl <- hpackDecodeHeader hdrblk ctx
331+ return $ if endOfStream then
332+ Open (NoBody tbl pri)
333+ else
334+ Open (HasBody tbl pri)
335+ else
336+ return $ Open $ Continued rfrags' siz' n' endOfStream pri
337+
338+stream FrameWindowUpdate header@FrameHeader{streamId} bs _ s Stream{streamWindow} = do
339+ WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
340+ !w <- atomically $ do
341+ w0 <- readTVar streamWindow
342+ let !w1 = w0 + n
343+ writeTVar streamWindow w1
344+ return w1
345+ when (isWindowOverflow w) $
346+ E.throwIO $ StreamError FlowControlError streamId
347+ return s
348+
349+stream FrameRSTStream header bs ctx _ strm = do
350+ RSTStreamFrame e <- guardIt $ decoderstStreamFrame header bs
351+ let !cc = Reset e
352+ closed ctx strm cc
353+ return $ Closed cc -- will be written to streamState again
354+
355+stream FramePriority header bs Context{outputQ,priorityTreeSize} s Stream{streamNumber,streamPrecedence} = do
356+ PriorityFrame newpri <- guardIt $ decodePriorityFrame header bs
357+ checkPriority newpri streamNumber
358+ oldpre <- readIORef streamPrecedence
359+ let !newpre = toPrecedence newpri
360+ writeIORef streamPrecedence newpre
361+ if isIdle s then do
362+ n <- atomicModifyIORef' priorityTreeSize (\x -> (x+1,x+1))
363+ -- fixme hard coding
364+ when (n >= 20) $ E.throwIO $ ConnectionError EnhanceYourCalm "too many idle priority frames"
365+ prepare outputQ streamNumber newpri
366+ else do
367+ mx <- delete outputQ streamNumber oldpre
368+ case mx of
369+ Nothing -> return ()
370+ Just out -> enqueueOutput outputQ out
371+ return s
372+
373+-- this ordering is important
374+stream FrameContinuation _ _ _ _ _ = E.throwIO $ ConnectionError ProtocolError "continue frame cannot come here"
375+stream _ _ _ _ (Open Continued{}) _ = E.throwIO $ ConnectionError ProtocolError "an illegal frame follows header/continuation frames"
376+-- Ignore frames to streams we have just reset, per section 5.1.
377+stream _ _ _ _ st@(Closed (ResetByMe _)) _ = return st
378+stream FrameData FrameHeader{streamId} _ _ _ _ = E.throwIO $ StreamError StreamClosed streamId
379+stream _ FrameHeader{streamId} _ _ _ _ = E.throwIO $ StreamError ProtocolError streamId
380+
381+----------------------------------------------------------------
382+
383+{-# INLINE newReadBody #-}
384+newReadBody :: TQueue ByteString -> IO (IO ByteString)
385+newReadBody q = do
386+ ref <- newIORef False
387+ return $ readBody q ref
388+
389+{-# INLINE readBody #-}
390+readBody :: TQueue ByteString -> IORef Bool -> IO ByteString
391+readBody q ref = do
392+ eof <- readIORef ref
393+ if eof then
394+ return ""
395+ else do
396+ bs <- atomically $ readTQueue q
397+ when (bs == "") $ writeIORef ref True
398+ return bs
warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+17−17
@@ -1,7 +1,9 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE BangPatterns #-}
23
34module Network.Wai.Handler.Warp.HTTP2.Request (
4− toRequest
5+ mkRequest
6+ , MkReq
57 , getHTTP2Data
68 , setHTTP2Data
79 , modifyHTTP2Data
@@ -18,37 +20,35 @@ import Network.Socket (SockAddr)
1820import Network.Wai
1921import Network.Wai.Internal (Request(..))
2022import System.IO.Unsafe (unsafePerformIO)
21−import qualified System.TimeManager as T
2223
2324import Network.Wai.Handler.Warp.HTTP2.Types
25+import Network.Wai.Handler.Warp.HashMap (hashByteString)
2426import Network.Wai.Handler.Warp.Imports
25−import Network.Wai.Handler.Warp.Request (getFileInfoKey, pauseTimeoutKey, getClientCertificateKey)
27+import Network.Wai.Handler.Warp.Request (getFileInfoKey)
2628import qualified Network.Wai.Handler.Warp.Settings as S (Settings, settingsNoParsePath)
2729import Network.Wai.Handler.Warp.Types
2830
29−type ToReq = (TokenHeaderList,ValueTable) -> Maybe Int -> IO ByteString -> T.Handle -> Transport -> IO Request
31+type MkReq = (TokenHeaderList,ValueTable) -> (Maybe Int,IO ByteString) -> IO (Request,InternalInfo)
3032
31−----------------------------------------------------------------
32−
33−toRequest :: InternalInfo -> S.Settings -> SockAddr -> ToReq
34−toRequest ii settings addr ht bodylen body th transport = do
33+mkRequest :: InternalInfo1 -> S.Settings -> SockAddr -> MkReq
34+mkRequest ii1 settings addr (reqths,reqvt) (bodylen,body) = do
3535 ref <- newIORef Nothing
36− toRequest' ii settings addr ref ht bodylen body th transport
36+ mkRequest' ii1 settings addr ref (reqths,reqvt) (bodylen,body)
3737
38−toRequest' :: InternalInfo -> S.Settings -> SockAddr
38+mkRequest' :: InternalInfo1 -> S.Settings -> SockAddr
3939 -> IORef (Maybe HTTP2Data)
40− -> ToReq
41−toRequest' ii settings addr ref (reqths,reqvt) bodylen body th transport = return req
40+ -> MkReq
41+mkRequest' ii1 settings addr ref (reqths,reqvt) (bodylen,body) = return (req,ii)
4242 where
4343 !req = Request {
4444 requestMethod = colonMethod
45− , httpVersion = H.http20
45+ , httpVersion = http2ver
4646 , rawPathInfo = rawPath
4747 , pathInfo = H.decodePathSegments path
4848 , rawQueryString = query
4949 , queryString = H.parseQuery query
5050 , requestHeaders = headers
51− , isSecure = isTransportSecure transport
51+ , isSecure = True
5252 , remoteHost = addr
5353 , requestBody = body
5454 , vault = vaultValue
@@ -77,13 +77,13 @@ toRequest' ii settings addr ref (reqths,reqvt) bodylen body th transport = retur
7777 (unparsedPath,query) = C8.break (=='?') $ fromJust (mPath <|> mAuth)
7878 !path = H.extractPath unparsedPath
7979 !rawPath = if S.settingsNoParsePath settings then unparsedPath else path
80− -- fixme: pauseTimeout. th is not available here.
80+ !h = hashByteString rawPath
81+ -- timeout handler must be overwritten with worker's one.
82+ !ii = toInternalInfo ii1 h
8183 !vaultValue = Vault.insert getFileInfoKey (getFileInfo ii)
8284 $ Vault.insert getHTTP2DataKey (readIORef ref)
8385 $ Vault.insert setHTTP2DataKey (writeIORef ref)
8486 $ Vault.insert modifyHTTP2DataKey (modifyIORef' ref)
85− $ Vault.insert pauseTimeoutKey (T.pause th)
86− $ Vault.insert getClientCertificateKey (tlsClientCertificate transport)
8787 Vault.empty
8888
8989getHTTP2DataKey :: Vault.Key (IO (Maybe HTTP2Data))
warp/Network/Wai/Handler/Warp/HTTP2/Response.hsdeleted−127
@@ -1,127 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE RecordWildCards #-}
3−{-# LANGUAGE BangPatterns #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−
6−module Network.Wai.Handler.Warp.HTTP2.Response (
7− fromResponse
8− ) where
9−
10−import qualified Control.Exception as E
11−import qualified Data.ByteString.Builder as BB
12−import qualified Network.HTTP.Types as H
13−import qualified Network.HTTP2.Server as H2
14−import Network.Wai hiding (responseFile, responseBuilder, responseStream)
15−import Network.Wai.Internal (Response(..))
16−
17−import Network.Wai.Handler.Warp.File
18−import Network.Wai.Handler.Warp.HTTP2.Request (getHTTP2Data)
19−import Network.Wai.Handler.Warp.HTTP2.Types
20−import Network.Wai.Handler.Warp.Header
21−import qualified Network.Wai.Handler.Warp.Response as R
22−import qualified Network.Wai.Handler.Warp.Settings as S
23−import Network.Wai.Handler.Warp.Types
24−
25−----------------------------------------------------------------
26−
27−fromResponse :: S.Settings -> InternalInfo -> Request -> Response -> IO H2.Response
28−fromResponse settings ii req rsp = do
29− date <- getDate ii
30− h2rsp <- case rsp of
31− ResponseFile st rsphdr path mpart -> do
32− let rsphdr' = add date svr rsphdr
33− responseFile st rsphdr' isHead path mpart ii reqhdr
34− ResponseBuilder st rsphdr builder -> do
35− let rsphdr' = add date svr rsphdr
36− return $ responseBuilder st rsphdr' isHead builder
37− ResponseStream st rsphdr strmbdy -> do
38− let rsphdr' = add date svr rsphdr
39− return $ responseStream st rsphdr' isHead strmbdy
40− _ -> error "ResponseRaw is not supported in HTTP/2"
41− mh2data <- getHTTP2Data req
42− case mh2data of
43− Nothing -> return h2rsp
44− Just h2data -> do
45− let !trailers = http2dataTrailers h2data
46− return $ H2.setResponseTrailersMaker h2rsp trailers
47− where
48− !isHead = requestMethod req == H.methodHead
49− !reqhdr = requestHeaders req
50− !svr = S.settingsServerName settings
51− add date server rsphdr = (H.hDate, date) : (H.hServer, server) : rsphdr
52− -- fixme: not adding svr if already exists
53−
54−----------------------------------------------------------------
55−
56−responseFile :: H.Status -> H.ResponseHeaders -> Bool
57− -> FilePath -> Maybe FilePart -> InternalInfo -> H.RequestHeaders
58− -> IO H2.Response
59−responseFile st rsphdr _ _ _ _ _
60− | noBody st = return $ responseNoBody st rsphdr
61−
62−responseFile st rsphdr isHead path (Just fp) _ _ =
63− return $ responseFile2XX st rsphdr isHead fileSpec
64− where
65− !off' = fromIntegral $ filePartOffset fp
66− !bytes' = fromIntegral $ filePartByteCount fp
67− !fileSpec = H2.FileSpec path off' bytes'
68−
69−responseFile _ rsphdr isHead path Nothing ii reqhdr = do
70− efinfo <- E.try $ getFileInfo ii path
71− case efinfo of
72− Left (_ex :: E.IOException) -> return $ response404 rsphdr
73− Right finfo -> do
74− let reqidx = indexRequestHeader reqhdr
75− rspidx = indexResponseHeader rsphdr
76− case conditionalRequest finfo rsphdr rspidx reqidx of
77− WithoutBody s -> return $ responseNoBody s rsphdr
78− WithBody s rsphdr' off bytes -> do
79− let !off' = fromIntegral off
80− !bytes' = fromIntegral bytes
81− !fileSpec = H2.FileSpec path off' bytes'
82− return $ responseFile2XX s rsphdr' isHead fileSpec
83−
84−----------------------------------------------------------------
85−
86−responseFile2XX :: H.Status -> H.ResponseHeaders -> Bool -> H2.FileSpec -> H2.Response
87−responseFile2XX st rsphdr isHead fileSpec
88− | isHead = responseNoBody st rsphdr
89− | otherwise = H2.responseFile st rsphdr fileSpec
90−
91−----------------------------------------------------------------
92−
93−responseBuilder :: H.Status -> H.ResponseHeaders -> Bool
94− -> BB.Builder
95− -> H2.Response
96−responseBuilder st rsphdr isHead builder
97− | noBody st = responseNoBody st rsphdr
98− | isHead = responseNoBody st rsphdr
99− | otherwise = H2.responseBuilder st rsphdr builder
100−
101−----------------------------------------------------------------
102−
103−responseStream :: H.Status -> H.ResponseHeaders -> Bool
104− -> StreamingBody
105− -> H2.Response
106−responseStream st rsphdr isHead strmbdy
107− | noBody st = responseNoBody st rsphdr
108− | isHead = responseNoBody st rsphdr
109− | otherwise = H2.responseStreaming st rsphdr strmbdy
110−
111−----------------------------------------------------------------
112−
113−responseNoBody :: H.Status -> H.ResponseHeaders -> H2.Response
114−responseNoBody st rsphdr = H2.responseNoBody st rsphdr
115−
116−----------------------------------------------------------------
117−
118−response404 :: H.ResponseHeaders -> H2.Response
119−response404 rsphdr = H2.responseBuilder H.notFound404 rsphdr' body
120− where
121− !rsphdr' = R.replaceHeader H.hContentType "text/plain; charset=utf-8" rsphdr
122− !body = BB.byteString "File not found"
123−
124−----------------------------------------------------------------
125−
126−noBody :: H.Status -> Bool
127−noBody = not . R.hasBody
warp/Network/Wai/Handler/Warp/HTTP2/Sender.hsnew file+536
@@ -0,0 +1,536 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE BangPatterns, CPP #-}
3+{-# LANGUAGE NamedFieldPuns #-}
4+
5+module Network.Wai.Handler.Warp.HTTP2.Sender (frameSender) where
6+
7+import Control.Concurrent.STM
8+import qualified Control.Exception as E
9+import qualified Data.ByteString as BS
10+import Data.ByteString.Builder (Builder)
11+import qualified Data.ByteString.Builder.Extra as B
12+import Data.IORef
13+import Foreign.Ptr (Ptr, plusPtr)
14+import Foreign.Storable (poke)
15+import Network.HPACK (setLimitForEncoding, toHeaderTable)
16+import Network.HTTP2
17+import Network.HTTP2.Priority (isEmptySTM, dequeueSTM, Precedence)
18+import Network.Wai
19+
20+#ifdef WINDOWS
21+import qualified System.IO as IO
22+#else
23+import Network.Wai.Handler.Warp.FdCache
24+import Network.Wai.Handler.Warp.SendFile (positionRead)
25+import qualified Network.Wai.Handler.Warp.Timeout as T
26+#endif
27+
28+import Network.Wai.Handler.Warp.Buffer
29+import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
30+import Network.Wai.Handler.Warp.HTTP2.HPACK
31+import Network.Wai.Handler.Warp.HTTP2.Manager (Manager)
32+import Network.Wai.Handler.Warp.HTTP2.Types
33+import Network.Wai.Handler.Warp.Imports hiding (readInt)
34+import qualified Network.Wai.Handler.Warp.Settings as S
35+import Network.Wai.Handler.Warp.Types
36+
37+----------------------------------------------------------------
38+
39+data Leftover = LZero
40+ | LOne B.BufferWriter
41+ | LTwo ByteString B.BufferWriter
42+
43+----------------------------------------------------------------
44+
45+{-# INLINE getStreamWindowSize #-}
46+getStreamWindowSize :: Stream -> IO WindowSize
47+getStreamWindowSize Stream{streamWindow} = atomically $ readTVar streamWindow
48+
49+{-# INLINE waitStreamWindowSize #-}
50+waitStreamWindowSize :: Stream -> IO ()
51+waitStreamWindowSize Stream{streamWindow} = atomically $ do
52+ w <- readTVar streamWindow
53+ check (w > 0)
54+
55+{-# INLINE waitStreaming #-}
56+waitStreaming :: TBQueue a -> IO ()
57+waitStreaming tbq = atomically $ do
58+ isEmpty <- isEmptyTBQueue tbq
59+ check (not isEmpty)
60+
61+data Switch = C Control
62+ | O (StreamId,Precedence,Output)
63+ | Flush
64+
65+frameSender :: Context -> Connection -> S.Settings -> Manager -> IO ()
66+frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
67+ conn@Connection{connWriteBuffer,connBufferSize,connSendAll}
68+ settings mgr = loop 0 `E.catch` ignore
69+ where
70+ dequeue off = do
71+ isEmpty <- isEmptyTQueue controlQ
72+ if isEmpty then do
73+ w <- readTVar connectionWindow
74+ check (w > 0)
75+ emp <- isEmptySTM outputQ
76+ if emp then
77+ if off /= 0 then return Flush else retry
78+ else
79+ O <$> dequeueSTM outputQ
80+ else
81+ C <$> readTQueue controlQ
82+
83+ loop off = do
84+ x <- atomically $ dequeue off
85+ case x of
86+ C ctl -> do
87+ when (off /= 0) $ flushN off
88+ off' <- control ctl off
89+ when (off' >= 0) $ loop off'
90+ O (_,pre,out) -> do
91+ let strm = outputStream out
92+ writeIORef (streamPrecedence strm) pre
93+ off' <- outputOrEnqueueAgain out off
94+ case off' of
95+ 0 -> loop 0
96+ _ | off' > 15872 -> flushN off' >> loop 0 -- fixme: hard-coding
97+ | otherwise -> loop off'
98+ Flush -> flushN off >> loop 0
99+
100+ control CFinish _ = return (-1)
101+ control (CGoaway frame) _ = connSendAll frame >> return (-1)
102+ control (CFrame frame) _ = connSendAll frame >> return 0
103+ control (CSettings frame alist) _ = do
104+ connSendAll frame
105+ setLimit alist
106+ return 0
107+ control (CSettings0 frame1 frame2 alist) off = do -- off == 0, just in case
108+ let !buf = connWriteBuffer `plusPtr` off
109+ !off' = off + BS.length frame1 + BS.length frame2
110+ buf' <- copy buf frame1
111+ void $ copy buf' frame2
112+ setLimit alist
113+ return off'
114+
115+ {-# INLINE setLimit #-}
116+ setLimit alist = case lookup SettingsHeaderTableSize alist of
117+ Nothing -> return ()
118+ Just siz -> setLimitForEncoding siz encodeDynamicTable
119+
120+ output out@(Output strm _ _ tell getH2D (ONext curr)) off0 lim = do
121+ -- Data frame payload
122+ let !buf = connWriteBuffer `plusPtr` off0
123+ !siz = connBufferSize - off0
124+ Next datPayloadLen mnext <- curr buf siz lim
125+ off <- fillDataHeader strm off0 datPayloadLen mnext tell getH2D
126+ maybeEnqueueNext out mnext
127+ return off
128+
129+ output out@(Output strm rspn ii tell getH2D ORspn) off0 lim = do
130+ -- Header frame and Continuation frame
131+ let !sid = streamNumber strm
132+ !endOfStream = case rspn of
133+ RspnNobody _ _ -> True
134+ _ -> False
135+ ths <- fixHeaders ctx rspn ii settings
136+ kvlen <- headerContinue sid ths endOfStream off0
137+ off <- sendHeadersIfNecessary $ off0 + frameHeaderLength + kvlen
138+ case rspn of
139+ RspnNobody _ _ -> do
140+ closed ctx strm Finished
141+ return off
142+ RspnFile _ _ path mpart -> do
143+ -- Data frame payload
144+ let payloadOff = off + frameHeaderLength
145+ Next datPayloadLen mnext <-
146+ fillFileBodyGetNext conn ii payloadOff lim path mpart
147+ off' <- fillDataHeader strm off datPayloadLen mnext tell getH2D
148+ maybeEnqueueNext out mnext
149+ return off'
150+ RspnBuilder _ _ builder -> do
151+ -- Data frame payload
152+ let payloadOff = off + frameHeaderLength
153+ Next datPayloadLen mnext <-
154+ fillBuilderBodyGetNext conn ii payloadOff lim builder
155+ off' <- fillDataHeader strm off datPayloadLen mnext tell getH2D
156+ maybeEnqueueNext out mnext
157+ return off'
158+ RspnStreaming _ _ tbq -> do
159+ let payloadOff = off + frameHeaderLength
160+ Next datPayloadLen mnext <-
161+ fillStreamBodyGetNext conn payloadOff lim tbq strm
162+ off' <- fillDataHeader strm off datPayloadLen mnext tell getH2D
163+ maybeEnqueueNext out mnext
164+ return off'
165+
166+ output out@(Output strm _ _ _ _ (OPush ths pid)) off0 lim = do
167+ -- Creating a push promise header
168+ -- Frame id should be associated stream id from the client.
169+ let !sid = streamNumber strm
170+ len <- pushPromise pid sid ths off0
171+ off <- sendHeadersIfNecessary $ off0 + frameHeaderLength + len
172+ output out{ outputType = ORspn } off lim
173+
174+ output _ _ _ = undefined -- never reach
175+
176+ outputOrEnqueueAgain out off = E.handle resetStream $ do
177+ state <- readIORef $ streamState strm
178+ if isClosed state then
179+ return off
180+ else case out of
181+ Output _ _ _ wait _ OWait -> do
182+ -- Checking if all push are done.
183+ let out' = out {
184+ outputHook = return ()
185+ , outputType = ORspn
186+ }
187+ forkAndEnqueueWhenReady wait outputQ out' mgr
188+ return off
189+ _ -> case mtbq of
190+ Just tbq -> checkStreaming tbq
191+ _ -> checkStreamWindowSize
192+ where
193+ strm = outputStream out
194+ mtbq = outputMaybeTBQueue out
195+ checkStreaming tbq = do
196+ isEmpty <- atomically $ isEmptyTBQueue tbq
197+ if isEmpty then do
198+ forkAndEnqueueWhenReady (waitStreaming tbq) outputQ out mgr
199+ return off
200+ else
201+ checkStreamWindowSize
202+ checkStreamWindowSize = do
203+ sws <- getStreamWindowSize strm
204+ if sws == 0 then do
205+ forkAndEnqueueWhenReady (waitStreamWindowSize strm) outputQ out mgr
206+ return off
207+ else do
208+ cws <- atomically $ readTVar connectionWindow -- not 0
209+ let !lim = min cws sws
210+ output out off lim
211+ resetStream e = do
212+ closed ctx strm (ResetByMe e)
213+ let !rst = resetFrame InternalError $ streamNumber strm
214+ enqueueControl controlQ $ CFrame rst
215+ return off
216+
217+ {-# INLINE flushN #-}
218+ -- Flush the connection buffer to the socket, where the first 'n' bytes of
219+ -- the buffer are filled.
220+ flushN :: Int -> IO ()
221+ flushN n = bufferIO connWriteBuffer n connSendAll
222+
223+ headerContinue sid ths endOfStream off = do
224+ let !offkv = off + frameHeaderLength
225+ let !bufkv = connWriteBuffer `plusPtr` offkv
226+ !limkv = connBufferSize - offkv
227+ (hs,kvlen) <- hpackEncodeHeader ctx bufkv limkv ths
228+ let flag0 = case hs of
229+ [] -> setEndHeader defaultFlags
230+ _ -> defaultFlags
231+ flag = if endOfStream then setEndStream flag0 else flag0
232+ let buf = connWriteBuffer `plusPtr` off
233+ fillFrameHeader FrameHeaders kvlen sid flag buf
234+ continue sid kvlen hs
235+
236+ !bufHeaderPayload = connWriteBuffer `plusPtr` frameHeaderLength
237+ !headerPayloadLim = connBufferSize - frameHeaderLength
238+
239+ continue _ kvlen [] = return kvlen
240+ continue sid kvlen ths = do
241+ flushN $ kvlen + frameHeaderLength
242+ -- Now off is 0
243+ (ths', kvlen') <- hpackEncodeHeaderLoop ctx bufHeaderPayload headerPayloadLim ths
244+ when (ths == ths') $ E.throwIO $ ConnectionError CompressionError "cannot compress the header"
245+ let flag = case ths' of
246+ [] -> setEndHeader defaultFlags
247+ _ -> defaultFlags
248+ fillFrameHeader FrameContinuation kvlen' sid flag connWriteBuffer
249+ continue sid kvlen' ths'
250+
251+ {-# INLINE maybeEnqueueNext #-}
252+ -- Re-enqueue the stream in the output queue.
253+ maybeEnqueueNext :: Output -> Maybe DynaNext -> IO ()
254+ maybeEnqueueNext _ Nothing = return ()
255+ maybeEnqueueNext out (Just next) = enqueueOutput outputQ out'
256+ where
257+ !out' = out { outputType = ONext next }
258+
259+ {-# INLINE sendHeadersIfNecessary #-}
260+ -- Send headers if there is not room for a 1-byte data frame, and return
261+ -- the offset of the next frame's first header byte.
262+ sendHeadersIfNecessary off
263+ -- True if the connection buffer has room for a 1-byte data frame.
264+ | off + frameHeaderLength < connBufferSize = return off
265+ | otherwise = do
266+ flushN off
267+ return 0
268+
269+ fillDataHeader strm@Stream{streamWindow,streamNumber}
270+ off datPayloadLen mnext tell getH2D = do
271+ -- Data frame header
272+ mh2d <- getH2D
273+ let (!trailers,!noTrailers) = case http2dataTrailers <$> mh2d of
274+ Nothing -> ([], True)
275+ Just ts -> (ts, null ts)
276+ !buf = connWriteBuffer `plusPtr` off
277+ !off' = off + frameHeaderLength + datPayloadLen
278+ !noMoreBody = isNothing mnext
279+ flag | noMoreBody && noTrailers = setEndStream defaultFlags
280+ | otherwise = defaultFlags
281+ fillFrameHeader FrameData datPayloadLen streamNumber flag buf
282+ off'' <- handleEndOfBody noMoreBody off' noTrailers trailers
283+ atomically $ modifyTVar' connectionWindow (subtract datPayloadLen)
284+ atomically $ modifyTVar' streamWindow (subtract datPayloadLen)
285+ return off''
286+ where
287+ handleTrailers True off0 _ = return off0
288+ handleTrailers _ off0 trailers = do
289+ (ths,_) <- toHeaderTable trailers
290+ kvlen <- headerContinue streamNumber ths True off0
291+ sendHeadersIfNecessary $ off0 + frameHeaderLength + kvlen
292+ handleEndOfBody True off0 noTrailers trailers = do
293+ off1 <- handleTrailers noTrailers off0 trailers
294+ void tell
295+ closed ctx strm Finished
296+ return off1
297+ handleEndOfBody False off0 _ _ = return off0
298+
299+
300+ pushPromise pid sid ths off = do
301+ let !offsid = off + frameHeaderLength
302+ !bufsid = connWriteBuffer `plusPtr` offsid
303+ poke32 bufsid $ fromIntegral sid
304+ let !offkv = offsid + 4
305+ !bufkv = connWriteBuffer `plusPtr` offkv
306+ !limkv = connBufferSize - offkv
307+ (_,kvlen) <- hpackEncodeHeader ctx bufkv limkv ths
308+ let !flag = setEndHeader defaultFlags -- No EndStream flag
309+ !buf = connWriteBuffer `plusPtr` off
310+ !len = kvlen + 4
311+ fillFrameHeader FramePushPromise len pid flag buf
312+ return len
313+
314+ {-# INLINE fillFrameHeader #-}
315+ fillFrameHeader ftyp len sid flag buf = encodeFrameHeaderBuf ftyp hinfo buf
316+ where
317+ hinfo = FrameHeader len flag sid
318+
319+ {-# INLINE ignore #-}
320+ ignore :: E.SomeException -> IO ()
321+ ignore _ = return ()
322+
323+----------------------------------------------------------------
324+
325+{-
326+ResponseFile Status ResponseHeaders FilePath (Maybe FilePart)
327+ResponseBuilder Status ResponseHeaders Builder
328+ResponseStream Status ResponseHeaders StreamingBody
329+ResponseRaw (IO ByteString -> (ByteString -> IO ()) -> IO ()) Response
330+-}
331+
332+fillBuilderBodyGetNext :: Connection -> InternalInfo -> Int -> WindowSize -> Builder -> IO Next
333+fillBuilderBodyGetNext Connection{connWriteBuffer,connBufferSize}
334+ _ off lim bb = do
335+ let datBuf = connWriteBuffer `plusPtr` off
336+ room = min (connBufferSize - off) lim
337+ (len, signal) <- B.runBuilder bb datBuf room
338+ return $ nextForBuilder len signal
339+
340+fillFileBodyGetNext :: Connection -> InternalInfo -> Int -> WindowSize -> FilePath -> Maybe FilePart -> IO Next
341+#ifdef WINDOWS
342+fillFileBodyGetNext Connection{connWriteBuffer,connBufferSize}
343+ _ off lim path mpart = do
344+ let datBuf = connWriteBuffer `plusPtr` off
345+ room = min (connBufferSize - off) lim
346+ (start, bytes) <- fileStartEnd path mpart
347+ -- fixme: how to close Handle? GC does it at this moment.
348+ hdl <- IO.openBinaryFile path IO.ReadMode
349+ IO.hSeek hdl IO.AbsoluteSeek start
350+ len <- IO.hGetBufSome hdl datBuf (mini room bytes)
351+ let bytes' = bytes - fromIntegral len
352+ -- fixme: connWriteBuffer connBufferSize
353+ return $ nextForFile len hdl bytes' (return ())
354+#else
355+fillFileBodyGetNext Connection{connWriteBuffer,connBufferSize}
356+ ii off lim path mpart = do
357+ (mfd, refresh') <- getFd ii path
358+ (fd, refresh) <- case mfd of
359+ Nothing -> do
360+ fd' <- openFile path
361+ th <- T.register (timeoutManager ii) (closeFile fd')
362+ return (fd', T.tickle th)
363+ Just fd -> return (fd, refresh')
364+ let datBuf = connWriteBuffer `plusPtr` off
365+ room = min (connBufferSize - off) lim
366+ (start, bytes) <- fileStartEnd path mpart
367+ len <- positionRead fd datBuf (mini room bytes) start
368+ refresh
369+ let len' = fromIntegral len
370+ return $ nextForFile len fd (start + len') (bytes - len') refresh
371+#endif
372+
373+fileStartEnd :: FilePath -> Maybe FilePart -> IO (Integer, Integer)
374+fileStartEnd _ (Just part) =
375+ return (filePartOffset part, filePartByteCount part)
376+fileStartEnd _ _ = error "fileStartEnd"
377+
378+----------------------------------------------------------------
379+
380+fillStreamBodyGetNext :: Connection -> Int -> WindowSize -> TBQueue Sequence -> Stream -> IO Next
381+fillStreamBodyGetNext Connection{connWriteBuffer,connBufferSize}
382+ off lim sq strm = do
383+ let datBuf = connWriteBuffer `plusPtr` off
384+ room = min (connBufferSize - off) lim
385+ (leftover, cont, len) <- runStreamBuilder datBuf room sq
386+ return $ nextForStream sq strm leftover cont len
387+
388+----------------------------------------------------------------
389+
390+fillBufBuilder :: Leftover -> DynaNext
391+fillBufBuilder leftover buf0 siz0 lim = do
392+ let payloadBuf = buf0 `plusPtr` frameHeaderLength
393+ room = min (siz0 - frameHeaderLength) lim
394+ case leftover of
395+ LZero -> error "fillBufBuilder: LZero"
396+ LOne writer -> do
397+ (len, signal) <- writer payloadBuf room
398+ getNext len signal
399+ LTwo bs writer
400+ | BS.length bs <= room -> do
401+ buf1 <- copy payloadBuf bs
402+ let len1 = BS.length bs
403+ (len2, signal) <- writer buf1 (room - len1)
404+ getNext (len1 + len2) signal
405+ | otherwise -> do
406+ let (bs1,bs2) = BS.splitAt room bs
407+ void $ copy payloadBuf bs1
408+ getNext room (B.Chunk bs2 writer)
409+ where
410+ getNext l s = return $ nextForBuilder l s
411+
412+nextForBuilder :: BytesFilled -> B.Next -> Next
413+nextForBuilder len B.Done
414+ = Next len Nothing
415+nextForBuilder len (B.More _ writer)
416+ = Next len $ Just (fillBufBuilder (LOne writer))
417+nextForBuilder len (B.Chunk bs writer)
418+ = Next len $ Just (fillBufBuilder (LTwo bs writer))
419+
420+----------------------------------------------------------------
421+
422+runStreamBuilder :: Buffer -> BufSize -> TBQueue Sequence
423+ -> IO (Leftover, Bool, BytesFilled)
424+runStreamBuilder buf0 room0 sq = loop buf0 room0 0
425+ where
426+ loop !buf !room !total = do
427+ mbuilder <- atomically $ tryReadTBQueue sq
428+ case mbuilder of
429+ Nothing -> return (LZero, True, total)
430+ Just (SBuilder builder) -> do
431+ (len, signal) <- B.runBuilder builder buf room
432+ let !total' = total + len
433+ case signal of
434+ B.Done -> loop (buf `plusPtr` len) (room - len) total'
435+ B.More _ writer -> return (LOne writer, True, total')
436+ B.Chunk bs writer -> return (LTwo bs writer, True, total')
437+ Just SFlush -> return (LZero, True, total)
438+ Just SFinish -> return (LZero, False, total)
439+
440+fillBufStream :: Leftover -> TBQueue Sequence -> Stream -> DynaNext
441+fillBufStream leftover0 sq strm buf0 siz0 lim0 = do
442+ let payloadBuf = buf0 `plusPtr` frameHeaderLength
443+ room0 = min (siz0 - frameHeaderLength) lim0
444+ case leftover0 of
445+ LZero -> do
446+ (leftover, cont, len) <- runStreamBuilder payloadBuf room0 sq
447+ getNext leftover cont len
448+ LOne writer -> write writer payloadBuf room0 0
449+ LTwo bs writer
450+ | BS.length bs <= room0 -> do
451+ buf1 <- copy payloadBuf bs
452+ let len = BS.length bs
453+ write writer buf1 (room0 - len) len
454+ | otherwise -> do
455+ let (bs1,bs2) = BS.splitAt room0 bs
456+ void $ copy payloadBuf bs1
457+ getNext (LTwo bs2 writer) True room0
458+ where
459+ getNext l b r = return $ nextForStream sq strm l b r
460+ write writer1 buf room sofar = do
461+ (len, signal) <- writer1 buf room
462+ case signal of
463+ B.Done -> do
464+ (leftover, cont, extra) <- runStreamBuilder (buf `plusPtr` len) (room - len) sq
465+ let !total = sofar + len + extra
466+ getNext leftover cont total
467+ B.More _ writer -> do
468+ let !total = sofar + len
469+ getNext (LOne writer) True total
470+ B.Chunk bs writer -> do
471+ let !total = sofar + len
472+ getNext (LTwo bs writer) True total
473+
474+nextForStream :: TBQueue Sequence -> Stream
475+ -> Leftover -> Bool -> BytesFilled
476+ -> Next
477+nextForStream _ _ _ False len = Next len Nothing
478+nextForStream sq strm leftOrZero True len =
479+ Next len $ Just (fillBufStream leftOrZero sq strm)
480+
481+----------------------------------------------------------------
482+
483+#ifdef WINDOWS
484+fillBufFile :: IO.Handle -> Integer -> IO () -> DynaNext
485+fillBufFile h bytes refresh buf siz lim = do
486+ let payloadBuf = buf `plusPtr` frameHeaderLength
487+ room = min (siz - frameHeaderLength) lim
488+ len <- IO.hGetBufSome h payloadBuf room
489+ refresh
490+ let bytes' = bytes - fromIntegral len
491+ return $ nextForFile len h bytes' refresh
492+
493+nextForFile :: BytesFilled -> IO.Handle -> Integer -> IO () -> Next
494+nextForFile 0 _ _ _ = Next 0 Nothing
495+nextForFile len _ 0 _ = Next len Nothing
496+nextForFile len h bytes refresh =
497+ Next len $ Just (fillBufFile h bytes refresh)
498+#else
499+fillBufFile :: Fd -> Integer -> Integer -> IO () -> DynaNext
500+fillBufFile fd start bytes refresh buf siz lim = do
501+ let payloadBuf = buf `plusPtr` frameHeaderLength
502+ room = min (siz - frameHeaderLength) lim
503+ len <- positionRead fd payloadBuf (mini room bytes) start
504+ let len' = fromIntegral len
505+ refresh
506+ return $ nextForFile len fd (start + len') (bytes - len') refresh
507+
508+nextForFile :: BytesFilled -> Fd -> Integer -> Integer -> IO () -> Next
509+nextForFile 0 _ _ _ _ = Next 0 Nothing
510+nextForFile len _ _ 0 _ = Next len Nothing
511+nextForFile len fd start bytes refresh =
512+ Next len $ Just (fillBufFile fd start bytes refresh)
513+#endif
514+
515+{-# INLINE mini #-}
516+mini :: Int -> Integer -> Int
517+mini i n
518+ | fromIntegral i < n = i
519+ | otherwise = fromIntegral n
520+
521+
522+----------------------------------------------------------------
523+
524+poke32 :: Ptr Word8 -> Word32 -> IO ()
525+poke32 ptr i = do
526+ poke ptr w0
527+ poke8 ptr 1 w1
528+ poke8 ptr 2 w2
529+ poke8 ptr 3 w3
530+ where
531+ w0 = fromIntegral ((i `shiftR` 24) .&. 0xff)
532+ w1 = fromIntegral ((i `shiftR` 16) .&. 0xff)
533+ w2 = fromIntegral ((i `shiftR` 8) .&. 0xff)
534+ w3 = fromIntegral (i .&. 0xff)
535+ poke8 :: Ptr Word8 -> Int -> Word8 -> IO ()
536+ poke8 ptr0 n w = poke (ptr0 `plusPtr` n) w
warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+265−13
@@ -1,26 +1,282 @@
1−{-# LANGUAGE NamedFieldPuns #-}
2−{-# LANGUAGE OverloadedStrings #-}
3−{-# LANGUAGE RecordWildCards #-}
1+{-# LANGUAGE OverloadedStrings, CPP #-}
2+{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}
3+{-# LANGUAGE BangPatterns #-}
44
55module Network.Wai.Handler.Warp.HTTP2.Types where
66
7+import Control.Concurrent (forkIO)
8+import Control.Concurrent.STM
9+import Control.Exception (SomeException, bracket)
710import qualified Data.ByteString as BS
11+import Data.ByteString.Builder (Builder)
12+import Data.IORef
13+import Data.IntMap.Strict (IntMap, IntMap)
14+import qualified Data.IntMap.Strict as M
15+import Network.HPACK hiding (Buffer)
816import qualified Network.HTTP.Types as H
917import Network.HTTP2
10−import qualified Network.HTTP2.Server as H2
18+import Network.HTTP2.Priority
19+import Network.Wai (Request, FilePart)
1120
21+import Network.Wai.Handler.Warp.HTTP2.Manager
1222import Network.Wai.Handler.Warp.Imports
1323import Network.Wai.Handler.Warp.Types
1424
1525----------------------------------------------------------------
1626
27+http2ver :: H.HttpVersion
28+http2ver = H.HttpVersion 2 0
29+
1730isHTTP2 :: Transport -> Bool
1831isHTTP2 TCP = False
1932isHTTP2 tls = useHTTP2
2033 where
2134 useHTTP2 = case tlsNegotiatedProtocol tls of
2235 Nothing -> False
23− Just proto -> "h2" `BS.isPrefixOf` proto
36+ Just proto -> "h2-" `BS.isPrefixOf` proto
37+
38+----------------------------------------------------------------
39+
40+data Input = Input Stream Request ValueTable InternalInfo
41+
42+----------------------------------------------------------------
43+
44+type DynaNext = Buffer -> BufSize -> WindowSize -> IO Next
45+
46+type BytesFilled = Int
47+
48+data Next = Next !BytesFilled (Maybe DynaNext)
49+
50+data Rspn = RspnNobody H.Status (TokenHeaderList, ValueTable)
51+ | RspnStreaming H.Status (TokenHeaderList, ValueTable) (TBQueue Sequence)
52+ | RspnBuilder H.Status (TokenHeaderList, ValueTable) Builder
53+ | RspnFile H.Status (TokenHeaderList, ValueTable) FilePath (Maybe FilePart)
54+
55+rspnStatus :: Rspn -> H.Status
56+rspnStatus (RspnNobody s _) = s
57+rspnStatus (RspnStreaming s _ _) = s
58+rspnStatus (RspnBuilder s _ _) = s
59+rspnStatus (RspnFile s _ _ _ ) = s
60+
61+rspnHeaders :: Rspn -> (TokenHeaderList, ValueTable)
62+rspnHeaders (RspnNobody _ t) = t
63+rspnHeaders (RspnStreaming _ t _) = t
64+rspnHeaders (RspnBuilder _ t _) = t
65+rspnHeaders (RspnFile _ t _ _ ) = t
66+
67+data Output = Output {
68+ outputStream :: !Stream
69+ , outputRspn :: !Rspn
70+ , outputII :: !InternalInfo
71+ , outputHook :: IO () -- OPush: wait for done, O*: telling done
72+ , outputH2Data :: IO (Maybe HTTP2Data)
73+ , outputType :: !OutputType
74+ }
75+
76+data OutputType = ORspn
77+ | OWait
78+ | OPush !TokenHeaderList !StreamId -- associated stream id from client
79+ | ONext !DynaNext
80+
81+outputMaybeTBQueue :: Output -> Maybe (TBQueue Sequence)
82+outputMaybeTBQueue (Output _ (RspnStreaming _ _ tbq) _ _ _ _) = Just tbq
83+outputMaybeTBQueue _ = Nothing
84+
85+data Control = CFinish
86+ | CGoaway !ByteString
87+ | CFrame !ByteString
88+ | CSettings !ByteString !SettingsList
89+ | CSettings0 !ByteString !ByteString !SettingsList
90+
91+----------------------------------------------------------------
92+
93+data Sequence = SFinish
94+ | SFlush
95+ | SBuilder Builder
96+
97+----------------------------------------------------------------
98+
99+-- | The context for HTTP/2 connection.
100+data Context = Context {
101+ -- HTTP/2 settings received from a browser
102+ http2settings :: !(IORef Settings)
103+ , firstSettings :: !(IORef Bool)
104+ , streamTable :: !StreamTable
105+ , concurrency :: !(IORef Int)
106+ , priorityTreeSize :: !(IORef Int)
107+ -- | RFC 7540 says "Other frames (from any stream) MUST NOT
108+ -- occur between the HEADERS frame and any CONTINUATION
109+ -- frames that might follow". This field is used to implement
110+ -- this requirement.
111+ , continued :: !(IORef (Maybe StreamId))
112+ , clientStreamId :: !(IORef StreamId)
113+ , serverStreamId :: !(IORef StreamId)
114+ , inputQ :: !(TQueue Input)
115+ , outputQ :: !(PriorityTree Output)
116+ , controlQ :: !(TQueue Control)
117+ , encodeDynamicTable :: !DynamicTable
118+ , decodeDynamicTable :: !DynamicTable
119+ -- the connection window for data from a server to a browser.
120+ , connectionWindow :: !(TVar WindowSize)
121+ }
122+
123+----------------------------------------------------------------
124+
125+newContext :: IO Context
126+newContext = Context <$> newIORef defaultSettings
127+ <*> newIORef False
128+ <*> newStreamTable
129+ <*> newIORef 0
130+ <*> newIORef 0
131+ <*> newIORef Nothing
132+ <*> newIORef 0
133+ <*> newIORef 0
134+ <*> newTQueueIO
135+ <*> newPriorityTree
136+ <*> newTQueueIO
137+ <*> newDynamicTableForEncoding defaultDynamicTableSize
138+ <*> newDynamicTableForDecoding defaultDynamicTableSize 4096
139+ <*> newTVarIO defaultInitialWindowSize
140+
141+clearContext :: Context -> IO ()
142+clearContext _ctx = return ()
143+
144+----------------------------------------------------------------
145+
146+data OpenState =
147+ JustOpened
148+ | Continued [HeaderBlockFragment]
149+ !Int -- Total size
150+ !Int -- The number of continuation frames
151+ !Bool -- End of stream
152+ !Priority
153+ | NoBody (TokenHeaderList,ValueTable) !Priority
154+ | HasBody (TokenHeaderList,ValueTable) !Priority
155+ | Body !(TQueue ByteString)
156+ !(Maybe Int) -- received Content-Length
157+ -- compared the body length for error checking
158+ !(IORef Int) -- actual body length
159+
160+data ClosedCode = Finished
161+ | Killed
162+ | Reset !ErrorCodeId
163+ | ResetByMe SomeException
164+ deriving Show
165+
166+data StreamState =
167+ Idle
168+ | Open !OpenState
169+ | HalfClosed
170+ | Closed !ClosedCode
171+ | Reserved
172+
173+isIdle :: StreamState -> Bool
174+isIdle Idle = True
175+isIdle _ = False
176+
177+isOpen :: StreamState -> Bool
178+isOpen Open{} = True
179+isOpen _ = False
180+
181+isHalfClosed :: StreamState -> Bool
182+isHalfClosed HalfClosed = True
183+isHalfClosed _ = False
184+
185+isClosed :: StreamState -> Bool
186+isClosed Closed{} = True
187+isClosed _ = False
188+
189+instance Show StreamState where
190+ show Idle = "Idle"
191+ show Open{} = "Open"
192+ show HalfClosed = "HalfClosed"
193+ show (Closed e) = "Closed: " ++ show e
194+ show Reserved = "Reserved"
195+
196+----------------------------------------------------------------
197+
198+data Stream = Stream {
199+ streamNumber :: !StreamId
200+ , streamState :: !(IORef StreamState)
201+ , streamWindow :: !(TVar WindowSize)
202+ , streamPrecedence :: !(IORef Precedence)
203+ }
204+
205+instance Show Stream where
206+ show s = show (streamNumber s)
207+
208+newStream :: StreamId -> WindowSize -> IO Stream
209+newStream sid win = Stream sid <$> newIORef Idle
210+ <*> newTVarIO win
211+ <*> newIORef defaultPrecedence
212+
213+newPushStream :: Context -> WindowSize -> Precedence -> IO Stream
214+newPushStream Context{serverStreamId} win pre = do
215+ sid <- atomicModifyIORef' serverStreamId inc2
216+ Stream sid <$> newIORef Reserved
217+ <*> newTVarIO win
218+ <*> newIORef pre
219+ where
220+ inc2 x = let !x' = x + 2 in (x', x')
221+
222+----------------------------------------------------------------
223+
224+opened :: Context -> Stream -> IO ()
225+opened Context{concurrency} Stream{streamState} = do
226+ atomicModifyIORef' concurrency (\x -> (x+1,()))
227+ writeIORef streamState (Open JustOpened)
228+
229+closed :: Context -> Stream -> ClosedCode -> IO ()
230+closed Context{concurrency,streamTable} Stream{streamState,streamNumber} cc = do
231+ remove streamTable streamNumber
232+ atomicModifyIORef' concurrency (\x -> (x-1,()))
233+ writeIORef streamState (Closed cc) -- anyway
234+
235+----------------------------------------------------------------
236+
237+newtype StreamTable = StreamTable (IORef (IntMap Stream))
238+
239+newStreamTable :: IO StreamTable
240+newStreamTable = StreamTable <$> newIORef M.empty
241+
242+insert :: StreamTable -> M.Key -> Stream -> IO ()
243+insert (StreamTable ref) k v = atomicModifyIORef' ref $ \m ->
244+ let !m' = M.insert k v m
245+ in (m', ())
246+
247+remove :: StreamTable -> M.Key -> IO ()
248+remove (StreamTable ref) k = atomicModifyIORef' ref $ \m ->
249+ let !m' = M.delete k m
250+ in (m', ())
251+
252+search :: StreamTable -> M.Key -> IO (Maybe Stream)
253+search (StreamTable ref) k = M.lookup k <$> readIORef ref
254+
255+updateAllStreamWindow :: (WindowSize -> WindowSize) -> StreamTable -> IO ()
256+updateAllStreamWindow adst (StreamTable ref) = do
257+ strms <- M.elems <$> readIORef ref
258+ forM_ strms $ \strm -> atomically $ modifyTVar (streamWindow strm) adst
259+
260+{-# INLINE forkAndEnqueueWhenReady #-}
261+forkAndEnqueueWhenReady :: IO () -> PriorityTree Output -> Output -> Manager -> IO ()
262+forkAndEnqueueWhenReady wait outQ out mgr = bracket setup teardown $ \_ ->
263+ void . forkIO $ do
264+ wait
265+ enqueueOutput outQ out
266+ where
267+ setup = addMyId mgr
268+ teardown _ = deleteMyId mgr
269+
270+{-# INLINE enqueueOutput #-}
271+enqueueOutput :: PriorityTree Output -> Output -> IO ()
272+enqueueOutput outQ out = do
273+ let Stream{..} = outputStream out
274+ pre <- readIORef streamPrecedence
275+ enqueue outQ streamNumber pre out
276+
277+{-# INLINE enqueueControl #-}
278+enqueueControl :: TQueue Control -> Control -> IO ()
279+enqueueControl ctlQ ctl = atomically $ writeTQueue ctlQ ctl
24280
25281----------------------------------------------------------------
26282
@@ -32,21 +288,17 @@ data HTTP2Data = HTTP2Data {
32288 --
33289 -- Since: 3.2.7
34290 http2dataPushPromise :: [PushPromise]
35− -- | Accessor for 'H2.TrailersMaker' in 'HTTP2Data'.
36− --
37− -- Since: 3.2.8 but the type changed in 3.3.0
38− , http2dataTrailers :: H2.TrailersMaker
39− }
291+ -- Since: 3.2.8
292+ , http2dataTrailers :: H.ResponseHeaders
293+ } deriving (Eq,Show)
40294
41295-- | Default HTTP/2 specific data.
42296--
43297-- Since: 3.2.7
44298defaultHTTP2Data :: HTTP2Data
45−defaultHTTP2Data = HTTP2Data [] H2.defaultTrailersMaker
299+defaultHTTP2Data = HTTP2Data [] []
46300
47301-- | HTTP/2 push promise or sever push.
48−-- This allows files only for backward-compatibility
49−-- while the HTTP/2 library supports other types.
50302--
51303-- Since: 3.2.7
52304data PushPromise = PushPromise {
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hsnew file+328
@@ -0,0 +1,328 @@
1+{-# LANGUAGE NamedFieldPuns #-}
2+{-# LANGUAGE PatternGuards #-}
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE BangPatterns #-}
7+
8+module Network.Wai.Handler.Warp.HTTP2.Worker (
9+ Responder
10+ , response
11+ , worker
12+ ) where
13+
14+import Control.Concurrent.STM
15+import Control.Exception (SomeException(..), AsyncException(..))
16+import qualified Control.Exception as E
17+import Data.ByteString.Builder (byteString)
18+import Data.IORef
19+import qualified Data.Vault.Lazy as Vault
20+import Network.HPACK
21+import Network.HPACK.Token
22+import qualified Network.HTTP.Types as H
23+import Network.HTTP2
24+import Network.HTTP2.Priority
25+import Network.Wai
26+import qualified Network.Wai.Handler.Warp.Timeout as Timeout
27+import Network.Wai.Internal (Response(..), ResponseReceived(..), ResponseReceived(..))
28+
29+import Network.Wai.Handler.Warp.FileInfoCache
30+import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
31+import Network.Wai.Handler.Warp.HTTP2.File
32+import Network.Wai.Handler.Warp.HTTP2.Manager
33+import Network.Wai.Handler.Warp.HTTP2.Request
34+import Network.Wai.Handler.Warp.HTTP2.Types
35+import Network.Wai.Handler.Warp.Imports hiding (insert)
36+import Network.Wai.Handler.Warp.Request (pauseTimeoutKey)
37+import qualified Network.Wai.Handler.Warp.Response as R
38+import qualified Network.Wai.Handler.Warp.Settings as S
39+import qualified Network.Wai.Handler.Warp.Timeout as T
40+import Network.Wai.Handler.Warp.Types
41+
42+----------------------------------------------------------------
43+
44+-- | The wai definition is 'type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived'.
45+-- This type implements the second argument (Response -> IO ResponseReceived)
46+-- with extra arguments.
47+type Responder = InternalInfo
48+ -> ValueTable -- for Request
49+ -> ThreadContinue
50+ -> Stream
51+ -> Request
52+ -> Response
53+ -> IO ResponseReceived
54+
55+pushStream :: Context -> S.Settings
56+ -> StreamId -> ValueTable -> Request -> InternalInfo
57+ -> Maybe HTTP2Data
58+ -> IO (OutputType, IO ())
59+pushStream _ _ _ _ _ _ Nothing = return (ORspn, return ())
60+pushStream ctx@Context{http2settings,outputQ,streamTable}
61+ settings pid reqvt req ii (Just h2d)
62+ | len == 0 = return (ORspn, return ())
63+ | otherwise = do
64+ pushable <- enablePush <$> readIORef http2settings
65+ if pushable then do
66+ tvar <- newTVarIO 0
67+ lim <- push tvar pps0 0
68+ if lim == 0 then
69+ return (ORspn, return ())
70+ else
71+ return (OWait, waiter lim tvar)
72+ else
73+ return (ORspn, return ())
74+ where
75+ !pps0 = http2dataPushPromise h2d
76+ !len = length pps0
77+ !pushLogger = S.settingsServerPushLogger settings
78+ increment tvar = atomically $ modifyTVar' tvar (+1)
79+ waiter lim tvar = atomically $ do
80+ n <- readTVar tvar
81+ check (n >= lim)
82+ !h2data = getHTTP2Data req
83+ push _ [] !n = return (n :: Int)
84+ push tvar (pp:pps) !n = do
85+ let !file = promisedFile pp
86+ efinfo <- E.try $ getFileInfo ii file
87+ case efinfo of
88+ Left (_ex :: E.IOException) -> push tvar pps n
89+ Right (FileInfo _ size _ date) -> do
90+ ws <- initialWindowSize <$> readIORef http2settings
91+ let !w = promisedWeight pp
92+ !pri = defaultPriority { weight = w }
93+ !pre = toPrecedence pri
94+ strm <- newPushStream ctx ws pre
95+ let !sid = streamNumber strm
96+ insert streamTable sid strm
97+ (ths0, vt) <- toHeaderTable (promisedResponseHeaders pp)
98+ let !scheme = fromJust $ getHeaderValue tokenScheme reqvt
99+ -- fixme: this value can be Nothing
100+ !auth = fromJust (getHeaderValue tokenHost reqvt
101+ <|> getHeaderValue tokenAuthority reqvt)
102+ !path = promisedPath pp
103+ !promisedRequest = [(tokenMethod, H.methodGet)
104+ ,(tokenScheme, scheme)
105+ ,(tokenAuthority, auth)
106+ ,(tokenPath, path)]
107+ !part = FilePart 0 size size
108+ !rsp = RspnFile H.ok200 (ths,vt) file (Just part)
109+ !ths = (tokenLastModified,date) :
110+ addContentHeadersForFilePart ths0 part
111+ pushLogger req path size
112+ let !ot = OPush promisedRequest pid
113+ !out = Output strm rsp ii (increment tvar) h2data ot
114+ enqueueOutput outputQ out
115+ push tvar pps (n + 1)
116+
117+
118+-- | This function is passed to workers.
119+-- They also pass 'Response's from 'Application's to this function.
120+-- This function enqueues commands for the HTTP/2 sender.
121+response :: S.Settings -> Context -> Manager -> Responder
122+response settings ctx@Context{outputQ} mgr ii reqvt tconf strm req rsp = case rsp of
123+ ResponseStream s0 hs0 strmbdy
124+ | noBody s0 -> responseNoBody s0 hs0
125+ | isHead -> responseNoBody s0 hs0
126+ | otherwise -> getHTTP2Data req
127+ >>= pushStream ctx settings sid reqvt req ii
128+ >>= responseStreaming s0 hs0 strmbdy
129+ ResponseBuilder s0 hs0 b
130+ | noBody s0 -> responseNoBody s0 hs0
131+ | isHead -> responseNoBody s0 hs0
132+ | otherwise -> getHTTP2Data req
133+ >>= pushStream ctx settings sid reqvt req ii
134+ >>= responseBuilderBody s0 hs0 b
135+ ResponseFile s0 hs0 p mp
136+ | noBody s0 -> responseNoBody s0 hs0
137+ | otherwise -> getHTTP2Data req
138+ >>= pushStream ctx settings sid reqvt req ii
139+ >>= responseFileXXX s0 hs0 p mp
140+ ResponseRaw _ _ -> error "HTTP/2 does not support ResponseRaw"
141+ where
142+ noBody = not . R.hasBody
143+ !isHead = requestMethod req == H.methodHead
144+ !logger = S.settingsLogger settings
145+ !th = threadHandle ii
146+ sid = streamNumber strm
147+ !h2data = getHTTP2Data req
148+
149+ -- Ideally, log messages should be written when responses are
150+ -- actually sent. But there is no way to keep good memory usage
151+ -- (resist to Request leak) and throughput. By compromise,
152+ -- log message are written here even the window size of streams
153+ -- is 0.
154+
155+ responseNoBody s hs0 = toHeaderTable hs0 >>= responseNoBody' s
156+
157+ responseNoBody' s tbl = do
158+ logger req s Nothing
159+ setThreadContinue tconf True
160+ let !rspn = RspnNobody s tbl
161+ !out = Output strm rspn ii (return ()) h2data ORspn
162+ enqueueOutput outputQ out
163+ return ResponseReceived
164+
165+ responseBuilderBody s hs0 bdy (rspnOrWait,tell) = do
166+ logger req s Nothing
167+ setThreadContinue tconf True
168+ tbl <- toHeaderTable hs0
169+ let !rspn = RspnBuilder s tbl bdy
170+ !out = Output strm rspn ii tell h2data rspnOrWait
171+ enqueueOutput outputQ out
172+ return ResponseReceived
173+
174+ responseFileXXX _ hs0 path Nothing aux = do
175+ efinfo <- E.try $ getFileInfo ii path
176+ case efinfo of
177+ Left (_ex :: E.IOException) -> response404 hs0
178+ Right finfo -> do
179+ (rspths0,vt) <- toHeaderTable hs0
180+ case conditionalRequest finfo rspths0 reqvt of
181+ WithoutBody s -> responseNoBody s hs0
182+ WithBody s rspths beg len -> responseFile2XX s (rspths,vt) path (Just (FilePart beg len (fileInfoSize finfo))) aux
183+
184+ responseFileXXX s0 hs0 path mpart aux = do
185+ tbl <- toHeaderTable hs0
186+ responseFile2XX s0 tbl path mpart aux
187+
188+ responseFile2XX s tbl path mpart (rspnOrWait,tell)
189+ | isHead = do
190+ logger req s Nothing
191+ responseNoBody' s tbl
192+ | otherwise = do
193+ logger req s (filePartByteCount <$> mpart)
194+ setThreadContinue tconf True
195+ let !rspn = RspnFile s tbl path mpart
196+ !out = Output strm rspn ii tell h2data rspnOrWait
197+ enqueueOutput outputQ out
198+ return ResponseReceived
199+
200+ response404 hs0 = responseBuilderBody s hs body (ORspn, return ())
201+ where
202+ s = H.notFound404
203+ hs = R.replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
204+ body = byteString "File not found"
205+
206+ responseStreaming s0 hs0 strmbdy (rspnOrWait,tell) = do
207+ logger req s0 Nothing
208+ -- We must not exit this WAI application.
209+ -- If the application exits, streaming would be also closed.
210+ -- So, this work occupies this thread.
211+ --
212+ -- We need to increase the number of workers.
213+ spawnAction mgr
214+ -- After this work, this thread stops to decease
215+ -- the number of workers.
216+ setThreadContinue tconf False
217+ -- Since 'StreamingBody' is loop, we cannot control it.
218+ -- So, let's serialize 'Builder' with a designated queue.
219+ tbq <- newTBQueueIO 10 -- fixme: hard coding: 10
220+ tbl <- toHeaderTable hs0
221+ let !rspn = RspnStreaming s0 tbl tbq
222+ !out = Output strm rspn ii tell h2data rspnOrWait
223+ enqueueOutput outputQ out
224+ let push b = do
225+ T.pause th
226+ atomically $ writeTBQueue tbq (SBuilder b)
227+ T.resume th
228+ flush = atomically $ writeTBQueue tbq SFlush
229+ _ <- strmbdy push flush
230+ atomically $ writeTBQueue tbq SFinish
231+ deleteMyId mgr
232+ return ResponseReceived
233+
234+worker :: Context -> S.Settings -> Application -> Responder -> T.Manager -> IO ()
235+worker ctx@Context{inputQ,controlQ} set app responder tm = do
236+ sinfo <- newStreamInfo
237+ tcont <- newThreadContinue
238+ let timeoutAction = return () -- cannot close the shared connection
239+ E.bracket (T.registerKillThread tm timeoutAction) T.cancel $ go sinfo tcont
240+ where
241+ go sinfo tcont th = do
242+ setThreadContinue tcont True
243+ ex <- E.try $ do
244+ T.pause th
245+ inp@(Input strm req reqvt ii) <- atomically $ readTQueue inputQ
246+ setStreamInfo sinfo inp
247+ T.resume th
248+ T.tickle th
249+ let !ii' = ii { threadHandle = th }
250+ !body = requestBody req
251+ !body' = do
252+ T.pause th
253+ bs <- body
254+ T.resume th
255+ return bs
256+ !vaultValue = Vault.insert pauseTimeoutKey (Timeout.pause th) $ vault req
257+ !req' = req { vault = vaultValue, requestBody = body' }
258+ app req' $ responder ii' reqvt tcont strm req'
259+ cont1 <- case ex of
260+ Right ResponseReceived -> return True
261+ Left e@(SomeException _)
262+ -- killed by the local worker manager
263+ | Just ThreadKilled <- E.fromException e -> return False
264+ -- killed by the local timeout manager
265+ | Just T.TimeoutThread <- E.fromException e -> do
266+ cleanup sinfo Nothing
267+ return True
268+ | otherwise -> do
269+ cleanup sinfo $ Just e
270+ return True
271+ cont2 <- getThreadContinue tcont
272+ clearStreamInfo sinfo
273+ when (cont1 && cont2) $ go sinfo tcont th
274+ cleanup sinfo me = do
275+ minp <- getStreamInfo sinfo
276+ case minp of
277+ Nothing -> return ()
278+ Just (Input strm req _reqvt _ii) -> do
279+ closed ctx strm Killed
280+ let !frame = resetFrame InternalError (streamNumber strm)
281+ enqueueControl controlQ $ CFrame frame
282+ case me of
283+ Nothing -> return ()
284+ Just e -> S.settingsOnException set (Just req) e
285+
286+----------------------------------------------------------------
287+
288+-- | It would nice if responders could return values to workers.
289+-- Unfortunately, 'ResponseReceived' is already defined in WAI 2.0.
290+-- It is not wise to change this type.
291+-- So, a reference is shared by a responder and its worker.
292+-- The reference refers a value of this type as a return value.
293+-- If 'True', the worker continue to serve requests.
294+-- Otherwise, the worker get finished.
295+newtype ThreadContinue = ThreadContinue (IORef Bool)
296+
297+{-# INLINE newThreadContinue #-}
298+newThreadContinue :: IO ThreadContinue
299+newThreadContinue = ThreadContinue <$> newIORef True
300+
301+{-# INLINE setThreadContinue #-}
302+setThreadContinue :: ThreadContinue -> Bool -> IO ()
303+setThreadContinue (ThreadContinue ref) x = writeIORef ref x
304+
305+{-# INLINE getThreadContinue #-}
306+getThreadContinue :: ThreadContinue -> IO Bool
307+getThreadContinue (ThreadContinue ref) = readIORef ref
308+
309+----------------------------------------------------------------
310+
311+-- | The type to store enough information for 'settingsOnException'.
312+newtype StreamInfo = StreamInfo (IORef (Maybe Input))
313+
314+{-# INLINE newStreamInfo #-}
315+newStreamInfo :: IO StreamInfo
316+newStreamInfo = StreamInfo <$> newIORef Nothing
317+
318+{-# INLINE clearStreamInfo #-}
319+clearStreamInfo :: StreamInfo -> IO ()
320+clearStreamInfo (StreamInfo ref) = writeIORef ref Nothing
321+
322+{-# INLINE setStreamInfo #-}
323+setStreamInfo :: StreamInfo -> Input -> IO ()
324+setStreamInfo (StreamInfo ref) inp = writeIORef ref $ Just inp
325+
326+{-# INLINE getStreamInfo #-}
327+getStreamInfo :: StreamInfo -> IO (Maybe Input)
328+getStreamInfo (StreamInfo ref) = readIORef ref
warp/Network/Wai/Handler/Warp/HashMap.hs+15−24
@@ -1,5 +1,3 @@
1−{-# LANGUAGE BangPatterns #-}
2−
31module Network.Wai.Handler.Warp.HashMap where
42
53import Data.Hashable (hash)
@@ -9,34 +7,27 @@ import Data.Map.Strict (Map)
97import qualified Data.Map.Strict as M
108import Prelude hiding (lookup)
119
12−----------------------------------------------------------------
10+import Network.Wai.Handler.Warp.Imports hiding (insert, lookup)
1311
14−-- | 'HashMap' is used for cache of file information.
15−-- Hash values of file pathes are used as outer keys.
16−-- Because negative entries are also contained,
17−-- a bad guy can intentionally cause the hash collison.
18−-- So, 'Map' is used internally to prevent
19−-- the hash collision attack.
20−newtype HashMap v = HashMap (IntMap (Map FilePath v))
12+type Hash = Int
13+newtype HashMap k v = HashMap (IntMap (Map k v))
2114
22−----------------------------------------------------------------
15+hashByteString :: ByteString -> Hash
16+hashByteString = hash
2317
24−empty :: HashMap v
18+empty :: HashMap k v
2519empty = HashMap I.empty
2620
27−isEmpty :: HashMap v -> Bool
28−isEmpty (HashMap hm) = I.null hm
29−
30−----------------------------------------------------------------
21+null :: HashMap k v -> Bool
22+null (HashMap hm) = I.null hm
3123
32−insert :: FilePath -> v -> HashMap v -> HashMap v
33−insert path v (HashMap hm) = HashMap $ I.insertWith f h m hm
24+insert :: Ord k => Hash -> k -> v -> HashMap k v -> HashMap k v
25+insert h k v (HashMap hm) = HashMap $ I.insertWith f h m hm
3426 where
35− !h = hash path
36− !m = M.singleton path v
27+ m = M.singleton k v
3728 f = M.union -- fimxe
29+{-# SPECIALIZE insert :: Hash -> String -> v -> HashMap String v -> HashMap String v #-}
3830
39−lookup :: FilePath -> HashMap v -> Maybe v
40−lookup path (HashMap hm) = I.lookup h hm >>= M.lookup path
41− where
42− !h = hash path
31+lookup :: Ord k => Hash -> k -> HashMap k v -> Maybe v
32+lookup h k (HashMap hm) = I.lookup h hm >>= M.lookup k
33+{-# SPECIALIZE lookup :: Hash -> String -> HashMap String v -> Maybe v #-}
warp/Network/Wai/Handler/Warp/Header.hs−2
@@ -69,7 +69,6 @@ indexResponseHeader hdr = traverseHeader hdr responseMaxIndex responseKeyIndex
6969data ResponseHeaderIndex = ResContentLength
7070 | ResServer
7171 | ResDate
72− | ResLastModified
7372 deriving (Enum,Bounded)
7473
7574-- | The size for 'IndexedHeader' for HTTP Response.
@@ -80,7 +79,6 @@ responseKeyIndex :: HeaderName -> Int
8079responseKeyIndex hn = case BS.length bs of
8180 4 -> if bs == "date" then fromEnum ResDate else -1
8281 6 -> if bs == "server" then fromEnum ResServer else -1
83− 13 -> if bs == "last-modified" then fromEnum ResLastModified else -1
8482 14 -> if bs == "content-length" then fromEnum ResContentLength else -1
8583 _ -> -1
8684 where
warp/Network/Wai/Handler/Warp/Internal.hs+2−3
@@ -53,7 +53,7 @@ module Network.Wai.Handler.Warp.Internal (
5353 -- resumed as soon as we return from user code.
5454 --
5555 -- * Every time data is successfully sent to the client, the timeout is tickled.
56− , module System.TimeManager
56+ , module Network.Wai.Handler.Warp.Timeout
5757 -- * File descriptor cache
5858 , module Network.Wai.Handler.Warp.FdCache
5959 -- * File information cache
@@ -69,8 +69,6 @@ module Network.Wai.Handler.Warp.Internal (
6969 , windowsThreadBlockHack
7070 ) where
7171
72−import System.TimeManager
73−
7472import Network.Wai.Handler.Warp.Buffer
7573import Network.Wai.Handler.Warp.Date
7674import Network.Wai.Handler.Warp.FdCache
@@ -82,5 +80,6 @@ import Network.Wai.Handler.Warp.Response
8280import Network.Wai.Handler.Warp.Run
8381import Network.Wai.Handler.Warp.SendFile
8482import Network.Wai.Handler.Warp.Settings
83+import Network.Wai.Handler.Warp.Timeout
8584import Network.Wai.Handler.Warp.Types
8685import Network.Wai.Handler.Warp.Windows
warp/Network/Wai/Handler/Warp/MultiMap.hs+44−55
@@ -1,111 +1,100 @@
1−{-# LANGUAGE BangPatterns #-}
2−
31module Network.Wai.Handler.Warp.MultiMap (
4− MultiMap
2+ MMap
53 , isEmpty
64 , empty
75 , singleton
86 , insert
9− , Network.Wai.Handler.Warp.MultiMap.lookup
7+ , search
8+ , searchWith
109 , pruneWith
1110 , toList
1211 , merge
1312 ) where
1413
15−import Data.Hashable (hash)
1614import Data.IntMap.Strict (IntMap)
1715import qualified Data.IntMap.Strict as I
16+import qualified Data.List.NonEmpty as NE
1817import Data.Semigroup
1918import Prelude -- Silence redundant import warnings
2019
20+import Network.Wai.Handler.Warp.Imports hiding ((<>), union, empty, insert)
21+
2122----------------------------------------------------------------
2223
23−-- | 'MultiMap' is used for cache of file descriptors.
24−-- Since multiple threads would open file descriptors for
25−-- the same file simultaneously, multiple entries must
26−-- be contained for the file.
27−-- Since hash values of file pathes are used as outer keys,
28−-- collison would happen for multiple file pathes.
29−-- Becase only positive entries are contained,
30−-- a bad guy cannot be cause the hash collision intentinally.
31−-- So, lists are good enough.
32−newtype MultiMap v = MultiMap (IntMap [(FilePath,v)])
24+type MMap v = IntMap (NonEmpty v)
3325
3426----------------------------------------------------------------
3527
3628-- | O(1)
37−empty :: MultiMap v
38−empty = MultiMap $ I.empty
29+isEmpty :: MMap v -> Bool
30+isEmpty = I.null
3931
4032-- | O(1)
41−isEmpty :: MultiMap v -> Bool
42−isEmpty (MultiMap mm) = I.null mm
33+empty :: MMap v
34+empty = I.empty
4335
4436----------------------------------------------------------------
4537
4638-- | O(1)
47−singleton :: FilePath -> v -> MultiMap v
48−singleton path v = MultiMap mm
49− where
50− !h = hash path
51− !mm = I.singleton h [(path,v)]
39+singleton :: Int -> v -> MMap v
40+singleton k v = I.singleton k (v :| [])
5241
5342----------------------------------------------------------------
5443
55−-- | O(N)
56−lookup :: FilePath -> MultiMap v -> Maybe v
57−lookup path (MultiMap mm) = case I.lookup h mm of
44+-- | O(log n)
45+search :: Int -> MMap v -> Maybe v
46+search k m = case I.lookup k m of
5847 Nothing -> Nothing
59− Just s -> Prelude.lookup path s
60− where
61− !h = hash path
48+ Just s -> Just $! NE.head s
49+
50+-- | O(log n)
51+searchWith :: Int -> (v -> Bool) -> MMap v -> Maybe v
52+searchWith k f m = case I.lookup k m of
53+ Nothing -> Nothing
54+ Just nxs -> find f $ NE.toList nxs
6255
6356----------------------------------------------------------------
6457
6558-- | O(log n)
66−insert :: FilePath -> v -> MultiMap v -> MultiMap v
67−insert path v (MultiMap mm) = MultiMap mm'
68− where
69− !h = hash path
70− !mm' = I.insertWith (<>) h [(path,v)] mm
59+insert :: Int -> v -> MMap v -> MMap v
60+insert k v m = I.insertWith (<>) k (v :| []) m
7161
7262----------------------------------------------------------------
7363
7464-- | O(n)
75−toList :: MultiMap v -> [(FilePath,v)]
76−toList (MultiMap mm) = concatMap snd $ I.toAscList mm
65+toList :: MMap v -> [v]
66+toList m = concatMap f $ I.toAscList m
67+ where
68+ f (_,s) = NE.toList s
7769
7870----------------------------------------------------------------
7971
8072-- | O(n)
81−pruneWith :: MultiMap v
82− -> ((FilePath,v) -> IO Bool)
83− -> IO (MultiMap v)
84−pruneWith (MultiMap mm) action = MultiMap <$> mm'
73+pruneWith :: MMap v
74+ -> (v -> IO Bool)
75+ -> IO (MMap v)
76+pruneWith m action = I.fromAscList <$> go (I.toDescList m) []
8577 where
86− !mm' = I.fromAscList <$> go (I.toDescList mm) []
87− go [] !acc = return acc
88− go ((h,s):kss) !acc = do
89− rs <- prune action s
90− case rs of
91− [] -> go kss acc
92− _ -> go kss ((h,rs) : acc)
78+ go [] acc = return acc
79+ go ((k,s):kss) acc = do
80+ mt <- prune action s
81+ case mt of
82+ Nothing -> go kss acc
83+ Just t -> go kss ((k,t) : acc)
9384
9485----------------------------------------------------------------
9586
9687-- O(n + m) where N is the size of the second argument
97−merge :: MultiMap v -> MultiMap v -> MultiMap v
98−merge (MultiMap m1) (MultiMap m2) = MultiMap mm
99− where
100− !mm = I.unionWith (<>) m1 m2
88+merge :: MMap v -> MMap v -> MMap v
89+merge m1 m2 = I.unionWith (<>) m1 m2
10190
10291----------------------------------------------------------------
10392
104−prune :: ((FilePath,v) -> IO Bool) -> [(FilePath,v)] -> IO [(FilePath,v)]
105−prune action xs0 = go xs0
93+prune :: (a -> IO Bool) -> NonEmpty a -> IO (Maybe (NonEmpty a))
94+prune act nxs = NE.nonEmpty <$> go (NE.toList nxs)
10695 where
10796 go [] = return []
10897 go (x:xs) = do
109− keep <- action x
98+ keep <- act x
11099 rs <- go xs
111100 return $ if keep then x:rs else rs
warp/Network/Wai/Handler/Warp/Recv.hs+4−25
@@ -17,14 +17,7 @@ import Foreign.C.Types
1717import Foreign.ForeignPtr (withForeignPtr)
1818import Foreign.Ptr (Ptr, castPtr, plusPtr)
1919import GHC.Conc (threadWaitRead)
20−import qualified GHC.IO.Exception as E
21−import Network.Socket (Socket)
22−import qualified System.IO.Error as E
23−#if MIN_VERSION_network(3,1,0)
24−import Network.Socket (withFdSocket)
25−#else
26−import Network.Socket (fdSocket)
27−#endif
20+import Network.Socket (Socket, fdSocket)
2821import System.Posix.Types (Fd(..))
2922
3023import Network.Wai.Handler.Warp.Buffer
@@ -96,31 +89,19 @@ spell init0 siz0 recv recvBuf
9689 siz' = siz - len
9790 loop bss' siz'
9891
99−-- The timeout manager may close the socket.
100−-- In that case, an error of "Bad file descriptor" occurs.
101−-- We ignores it because we expect TimeoutThread.
10292receive :: Socket -> BufferPool -> Recv
103−receive sock pool = E.handle handler $ withBufferPool pool $ \ (ptr, size) -> do
104−#if MIN_VERSION_network(3,1,0)
105− withFdSocket sock $ \fd -> do
106−#elif MIN_VERSION_network(3,0,0)
93+receive sock pool = withBufferPool pool $ \ (ptr, size) -> do
94+#if MIN_VERSION_network(3,0,0)
10795 fd <- fdSocket sock
10896#else
10997 let fd = fdSocket sock
11098#endif
11199 let size' = fromIntegral size
112100 fromIntegral <$> receiveloop fd ptr size'
113− where
114− handler :: E.IOException -> IO ByteString
115− handler e
116− | E.ioeGetErrorType e == E.InvalidArgument = return ""
117− | otherwise = E.throwIO e
118101
119102receiveBuf :: Socket -> RecvBuf
120103receiveBuf sock buf0 siz0 = do
121−#if MIN_VERSION_network(3,1,0)
122− withFdSocket sock $ \fd -> do
123−#elif MIN_VERSION_network(3,0,0)
104+#if MIN_VERSION_network(3,0,0)
124105 fd <- fdSocket sock
125106#else
126107 let fd = fdSocket sock
@@ -153,8 +134,6 @@ receiveloop sock ptr size = do
153134 else
154135 return bytes
155136
156−#ifndef mingw32_HOST_OS
157137-- fixme: the type of the return value
158138foreign import ccall unsafe "recv"
159139 c_recv :: CInt -> Ptr CChar -> CSize -> CInt -> IO CInt
160−#endif
warp/Network/Wai/Handler/Warp/Request.hs+28−26
@@ -9,7 +9,6 @@ module Network.Wai.Handler.Warp.Request (
99 , headerLines
1010 , pauseTimeoutKey
1111 , getFileInfoKey
12− , getClientCertificateKey
1312 , NoKeepAliveRequest (..)
1413 ) where
1514
@@ -22,23 +21,29 @@ import qualified Data.CaseInsensitive as CI
2221import qualified Data.IORef as I
2322import Data.Typeable (Typeable)
2423import qualified Data.Vault.Lazy as Vault
25−import Data.X509
2624import qualified Network.HTTP.Types as H
2725import Network.Socket (SockAddr)
2826import Network.Wai
27+import qualified Network.Wai.Handler.Warp.Timeout as Timeout
2928import Network.Wai.Handler.Warp.Types
3029import Network.Wai.Internal
3130import Prelude hiding (lines)
3231import System.IO.Unsafe (unsafePerformIO)
33−import qualified System.TimeManager as Timeout
3432
3533import Network.Wai.Handler.Warp.Conduit
3634import Network.Wai.Handler.Warp.FileInfoCache
35+import Network.Wai.Handler.Warp.HashMap (hashByteString)
3736import Network.Wai.Handler.Warp.Header
3837import Network.Wai.Handler.Warp.Imports hiding (readInt, lines)
3938import Network.Wai.Handler.Warp.ReadInt
4039import Network.Wai.Handler.Warp.RequestHeader
41−import Network.Wai.Handler.Warp.Settings (Settings, settingsNoParsePath, settingsMaxTotalHeaderLength)
40+import Network.Wai.Handler.Warp.Settings (Settings, settingsNoParsePath)
41+
42+----------------------------------------------------------------
43+
44+-- FIXME come up with good values here
45+maxTotalHeaderLength :: Int
46+maxTotalHeaderLength = 50 * 1024
4247
4348----------------------------------------------------------------
4449
@@ -47,22 +52,21 @@ import Network.Wai.Handler.Warp.Settings (Settings, settingsNoParsePath, setting
4752recvRequest :: Bool -- ^ first request on this connection?
4853 -> Settings
4954 -> Connection
50− -> InternalInfo
51− -> Timeout.Handle
55+ -> InternalInfo1
5256 -> SockAddr -- ^ Peer's address.
5357 -> Source -- ^ Where HTTP request comes from.
54− -> Transport
5558 -> IO (Request
5659 ,Maybe (I.IORef Int)
5760 ,IndexedHeader
58− ,IO ByteString) -- ^
61+ ,IO ByteString
62+ ,InternalInfo) -- ^
5963 -- 'Request' passed to 'Application',
6064 -- how many bytes remain to be consumed, if known
6165 -- 'IndexedHeader' of HTTP request for internal use,
6266 -- Body producing action used for flushing the request body
6367
64−recvRequest firstRequest settings conn ii th addr src transport = do
65− hdrlines <- headerLines (settingsMaxTotalHeaderLength settings) firstRequest src
68+recvRequest firstRequest settings conn ii1 addr src = do
69+ hdrlines <- headerLines firstRequest src
6670 (method, unparsedPath, path, query, httpversion, hdr) <- parseHeaderLines hdrlines
6771 let idxhdr = indexRequestHeader hdr
6872 expect = idxhdr ! fromEnum ReqExpect
@@ -70,9 +74,11 @@ recvRequest firstRequest settings conn ii th addr src transport = do
7074 te = idxhdr ! fromEnum ReqTransferEncoding
7175 handle100Continue = handleExpect conn httpversion expect
7276 rawPath = if settingsNoParsePath settings then unparsedPath else path
77+ h = hashByteString rawPath
78+ ii = toInternalInfo ii1 h
79+ th = threadHandle ii
7380 vaultValue = Vault.insert pauseTimeoutKey (Timeout.pause th)
7481 $ Vault.insert getFileInfoKey (getFileInfo ii)
75− $ Vault.insert getClientCertificateKey (tlsClientCertificate transport)
7682 Vault.empty
7783 (rbody, remainingRef, bodyLength) <- bodyAndSource src cl te
7884 -- body producing function which will produce '100-continue', if needed
@@ -87,7 +93,7 @@ recvRequest firstRequest settings conn ii th addr src transport = do
8793 , rawQueryString = query
8894 , queryString = H.parseQuery query
8995 , requestHeaders = hdr
90− , isSecure = isTransportSecure transport
96+ , isSecure = False
9197 , remoteHost = addr
9298 , requestBody = rbody'
9399 , vault = vaultValue
@@ -97,12 +103,12 @@ recvRequest firstRequest settings conn ii th addr src transport = do
97103 , requestHeaderReferer = idxhdr ! fromEnum ReqReferer
98104 , requestHeaderUserAgent = idxhdr ! fromEnum ReqUserAgent
99105 }
100− return (req, remainingRef, idxhdr, rbodyFlush)
106+ return (req, remainingRef, idxhdr, rbodyFlush, ii)
101107
102108----------------------------------------------------------------
103109
104−headerLines :: Int -> Bool -> Source -> IO [ByteString]
105−headerLines maxTotalHeaderLength firstRequest src = do
110+headerLines :: Bool -> Source -> IO [ByteString]
111+headerLines firstRequest src = do
106112 bs <- readSource src
107113 if S.null bs
108114 -- When we're working on a keep-alive connection and trying to
@@ -110,7 +116,7 @@ headerLines maxTotalHeaderLength firstRequest src = do
110116 -- lack of data as a real exception. See the http1 function in
111117 -- the Run module for more details.
112118 then if firstRequest then throwIO ConnectionClosedByPeer else throwIO NoKeepAliveRequest
113− else push maxTotalHeaderLength src (THStatus 0 id id) bs
119+ else push src (THStatus 0 id id) bs
114120
115121data NoKeepAliveRequest = NoKeepAliveRequest
116122 deriving (Show, Typeable)
@@ -220,8 +226,8 @@ close :: Sink ByteString IO a
220226close = throwIO IncompleteHeaders
221227-}
222228
223−push :: Int -> Source -> THStatus -> ByteString -> IO [ByteString]
224−push maxTotalHeaderLength src (THStatus len lines prepend) bs'
229+push :: Source -> THStatus -> ByteString -> IO [ByteString]
230+push src (THStatus len lines prepend) bs'
225231 -- Too many bytes
226232 | len > maxTotalHeaderLength = throwIO OverLargeHeader
227233 | otherwise = push' mnl
@@ -249,13 +255,13 @@ push maxTotalHeaderLength src (THStatus len lines prepend) bs'
249255 push' Nothing = do
250256 bst <- readSource' src
251257 when (S.null bst) $ throwIO IncompleteHeaders
252− push maxTotalHeaderLength src status bst
258+ push src status bst
253259 where
254260 len' = len + bsLen
255261 prepend' = S.append bs
256262 status = THStatus len' lines prepend'
257263 -- Found a newline, but next line continues as a multiline header
258− push' (Just (end, True)) = push maxTotalHeaderLength src status rest
264+ push' (Just (end, True)) = push src status rest
259265 where
260266 rest = S.drop (end + 1) bs
261267 prepend' = S.append (SU.unsafeTake (checkCR bs end) bs)
@@ -274,12 +280,12 @@ push maxTotalHeaderLength src (THStatus len lines prepend) bs'
274280 in if start < bsLen then
275281 -- more bytes in this chunk, push again
276282 let bs'' = SU.unsafeDrop start bs
277− in push maxTotalHeaderLength src status bs''
283+ in push src status bs''
278284 else do
279285 -- no more bytes in this chunk, ask for more
280286 bst <- readSource' src
281287 when (S.null bs) $ throwIO IncompleteHeaders
282− push maxTotalHeaderLength src status bst
288+ push src status bst
283289 where
284290 start = end + 1 -- start of next chunk
285291 line = SU.unsafeTake (checkCR bs end) bs
@@ -297,7 +303,3 @@ pauseTimeoutKey = unsafePerformIO Vault.newKey
297303getFileInfoKey :: Vault.Key (FilePath -> IO FileInfo)
298304getFileInfoKey = unsafePerformIO Vault.newKey
299305{-# NOINLINE getFileInfoKey #-}
300−
301−getClientCertificateKey :: Vault.Key (Maybe CertificateChain)
302−getClientCertificateKey = unsafePerformIO Vault.newKey
303−{-# NOINLINE getClientCertificateKey #-}
warp/Network/Wai/Handler/Warp/Response.hs+19−22
@@ -30,7 +30,6 @@ import qualified Network.HTTP.Types.Header as H
3030import Network.Wai
3131import Network.Wai.Internal
3232import qualified Paths_warp
33−import qualified System.TimeManager as T
3433
3534import Network.Wai.Handler.Warp.Buffer (toBuilderBuffer)
3635import qualified Network.Wai.Handler.Warp.Date as D
@@ -40,6 +39,7 @@ import Network.Wai.Handler.Warp.IO (toBufIOWith)
4039import Network.Wai.Handler.Warp.Imports
4140import Network.Wai.Handler.Warp.ResponseHeader
4241import Network.Wai.Handler.Warp.Settings
42+import qualified Network.Wai.Handler.Warp.Timeout as T
4343import Network.Wai.Handler.Warp.Types
4444
4545-- $setup
@@ -101,13 +101,12 @@ import Network.Wai.Handler.Warp.Types
101101sendResponse :: Settings
102102 -> Connection
103103 -> InternalInfo
104− -> T.Handle
105104 -> Request -- ^ HTTP request.
106105 -> IndexedHeader -- ^ Indexed header of HTTP request.
107106 -> IO ByteString -- ^ source from client, for raw response
108107 -> Response -- ^ HTTP response including status code and response header.
109108 -> IO Bool -- ^ Returing True if the connection is persistent.
110−sendResponse settings conn ii th req reqidxhdr src response = do
109+sendResponse settings conn ii req reqidxhdr src response = do
111110 hs <- addServerAndDate hs0
112111 if hasBody s then do
113112 -- The response to HEAD does not have body.
@@ -116,14 +115,14 @@ sendResponse settings conn ii th req reqidxhdr src response = do
116115 -- and status, the response to HEAD is processed here.
117116 --
118117 -- See definition of rsp below for proper body stripping.
119− (ms, mlen) <- sendRsp conn ii ver s hs rspidxhdr rsp
118+ (ms, mlen) <- sendRsp conn ii ver s hs rsp
120119 case ms of
121120 Nothing -> return ()
122121 Just realStatus -> logger req realStatus mlen
123122 T.tickle th
124123 return ret
125124 else do
126− _ <- sendRsp conn ii ver s hs rspidxhdr RspNoBody
125+ _ <- sendRsp conn ii ver s hs RspNoBody
127126 logger req s Nothing
128127 T.tickle th
129128 return isPersist
@@ -134,6 +133,7 @@ sendResponse settings conn ii th req reqidxhdr src response = do
134133 s = responseStatus response
135134 hs0 = sanitizeHeaders $ responseHeaders response
136135 rspidxhdr = indexResponseHeader hs0
136+ th = threadHandle ii
137137 getdate = getDate ii
138138 addServerAndDate = addDate getdate rspidxhdr . addServer defServer rspidxhdr
139139 (isPersist,isChunked0) = infoFromRequest req reqidxhdr
@@ -196,13 +196,12 @@ sendRsp :: Connection
196196 -> H.HttpVersion
197197 -> H.Status
198198 -> H.ResponseHeaders
199− -> IndexedHeader -- Response
200199 -> Rsp
201200 -> IO (Maybe H.Status, Maybe Integer)
202201
203202----------------------------------------------------------------
204203
205−sendRsp conn _ ver s hs _ RspNoBody = do
204+sendRsp conn _ ver s hs RspNoBody = do
206205 -- Not adding Content-Length.
207206 -- User agents treats it as Content-Length: 0.
208207 composeHeader ver s hs >>= connSendAll conn
@@ -210,7 +209,7 @@ sendRsp conn _ ver s hs _ RspNoBody = do
210209
211210----------------------------------------------------------------
212211
213−sendRsp conn _ ver s hs _ (RspBuilder body needsChunked) = do
212+sendRsp conn _ ver s hs (RspBuilder body needsChunked) = do
214213 header <- composeHeaderBuilder ver s hs needsChunked
215214 let hdrBdy
216215 | needsChunked = header <> chunkedTransferEncoding body
@@ -223,7 +222,7 @@ sendRsp conn _ ver s hs _ (RspBuilder body needsChunked) = do
223222
224223----------------------------------------------------------------
225224
226−sendRsp conn _ ver s hs _ (RspStream streamingBody needsChunked th) = do
225+sendRsp conn _ ver s hs (RspStream streamingBody needsChunked th) = do
227226 header <- composeHeaderBuilder ver s hs needsChunked
228227 (recv, finish) <- newByteStringBuilderRecv $ reuseBufferStrategy
229228 $ toBuilderBuffer (connWriteBuffer conn) (connBufferSize conn)
@@ -247,7 +246,7 @@ sendRsp conn _ ver s hs _ (RspStream streamingBody needsChunked th) = do
247246
248247----------------------------------------------------------------
249248
250−sendRsp conn _ _ _ _ _ (RspRaw withApp src tickle) = do
249+sendRsp conn _ _ _ _ (RspRaw withApp src tickle) = do
251250 withApp recv send
252251 return (Nothing, Nothing)
253252 where
@@ -261,8 +260,8 @@ sendRsp conn _ _ _ _ _ (RspRaw withApp src tickle) = do
261260
262261-- Sophisticated WAI applications.
263262-- We respect s0. s0 MUST be a proper value.
264−sendRsp conn ii ver s0 hs0 rspidxhdr (RspFile path (Just part) _ isHead hook) =
265− sendRspFile2XX conn ii ver s0 hs rspidxhdr path beg len isHead hook
263+sendRsp conn ii ver s0 hs0 (RspFile path (Just part) _ isHead hook) =
264+ sendRspFile2XX conn ii ver s0 hs path beg len isHead hook
266265 where
267266 beg = filePartOffset part
268267 len = filePartByteCount part
@@ -272,17 +271,17 @@ sendRsp conn ii ver s0 hs0 rspidxhdr (RspFile path (Just part) _ isHead hook) =
272271
273272-- Simple WAI applications.
274273-- Status is ignored
275−sendRsp conn ii ver _ hs0 rspidxhdr (RspFile path Nothing reqidxhdr isHead hook) = do
274+sendRsp conn ii ver _ hs0 (RspFile path Nothing idxhdr isHead hook) = do
276275 efinfo <- E.try $ getFileInfo ii path
277276 case efinfo of
278277 Left (_ex :: E.IOException) ->
279278#ifdef WARP_DEBUG
280279 print _ex >>
281280#endif
282− sendRspFile404 conn ii ver hs0 rspidxhdr
283− Right finfo -> case conditionalRequest finfo hs0 rspidxhdr reqidxhdr of
284− WithoutBody s -> sendRsp conn ii ver s hs0 rspidxhdr RspNoBody
285− WithBody s hs beg len -> sendRspFile2XX conn ii ver s hs rspidxhdr path beg len isHead hook
281+ sendRspFile404 conn ii ver hs0
282+ Right finfo -> case conditionalRequest finfo hs0 idxhdr of
283+ WithoutBody s -> sendRsp conn ii ver s hs0 RspNoBody
284+ WithBody s hs beg len -> sendRspFile2XX conn ii ver s hs path beg len isHead hook
286285
287286----------------------------------------------------------------
288287
@@ -291,15 +290,14 @@ sendRspFile2XX :: Connection
291290 -> H.HttpVersion
292291 -> H.Status
293292 -> H.ResponseHeaders
294− -> IndexedHeader
295293 -> FilePath
296294 -> Integer
297295 -> Integer
298296 -> Bool
299297 -> IO ()
300298 -> IO (Maybe H.Status, Maybe Integer)
301−sendRspFile2XX conn ii ver s hs rspidxhdr path beg len isHead hook
302− | isHead = sendRsp conn ii ver s hs rspidxhdr RspNoBody
299+sendRspFile2XX conn ii ver s hs path beg len isHead hook
300+ | isHead = sendRsp conn ii ver s hs RspNoBody
303301 | otherwise = do
304302 lheader <- composeHeader ver s hs
305303 (mfd, fresher) <- getFd ii path
@@ -312,9 +310,8 @@ sendRspFile404 :: Connection
312310 -> InternalInfo
313311 -> H.HttpVersion
314312 -> H.ResponseHeaders
315− -> IndexedHeader
316313 -> IO (Maybe H.Status, Maybe Integer)
317−sendRspFile404 conn ii ver hs0 rspidxhdr = sendRsp conn ii ver s hs rspidxhdr (RspBuilder body True)
314+sendRspFile404 conn ii ver hs0 = sendRsp conn ii ver s hs (RspBuilder body True)
318315 where
319316 s = H.notFound404
320317 hs = replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
warp/Network/Wai/Handler/Warp/Run.hs+64−108
@@ -3,10 +3,8 @@
33{-# LANGUAGE OverloadedStrings #-}
44{-# LANGUAGE PackageImports #-}
55{-# LANGUAGE PatternGuards #-}
6−{-# LANGUAGE RecordWildCards #-}
76{-# LANGUAGE ScopedTypeVariables #-}
87{-# LANGUAGE TupleSections #-}
9−{-# LANGUAGE ViewPatterns #-}
108{-# OPTIONS_GHC -fno-warn-deprecations #-}
119
1210module Network.Wai.Handler.Warp.Run where
@@ -17,20 +15,15 @@ import qualified Control.Concurrent as Conc (yield)
1715import Control.Exception as E
1816import qualified Data.ByteString as S
1917import Data.Char (chr)
20−import Data.IORef (IORef, newIORef, readIORef, writeIORef)
18+import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef')
2119import Data.Streaming.Network (bindPortTCP)
2220import Foreign.C.Error (Errno(..), eCONNABORTED)
2321import GHC.IO.Exception (IOException(..))
24−import qualified Network.HTTP2 as H2
2522import Network.Socket (Socket, close, accept, withSocketsDo, SockAddr(SockAddrInet, SockAddrInet6), setSocketOption, SocketOption(..))
26−#if MIN_VERSION_network(3,1,1)
27−import Network.Socket (gracefulClose)
28−#endif
2923import qualified Network.Socket.ByteString as Sock
3024import Network.Wai
3125import Network.Wai.Internal (ResponseReceived (ResponseReceived))
3226import System.Environment (lookupEnv)
33−import qualified System.TimeManager as T
3427import System.Timeout (timeout)
3528
3629import Network.Wai.Handler.Warp.Buffer
@@ -38,8 +31,7 @@ import Network.Wai.Handler.Warp.Counter
3831import qualified Network.Wai.Handler.Warp.Date as D
3932import qualified Network.Wai.Handler.Warp.FdCache as F
4033import qualified Network.Wai.Handler.Warp.FileInfoCache as I
41−import Network.Wai.Handler.Warp.HTTP2 (http2)
42−import Network.Wai.Handler.Warp.HTTP2.Types (isHTTP2)
34+import Network.Wai.Handler.Warp.HTTP2 (http2, isHTTP2)
4335import Network.Wai.Handler.Warp.Header
4436import Network.Wai.Handler.Warp.Imports hiding (readInt)
4537import Network.Wai.Handler.Warp.ReadInt
@@ -48,6 +40,7 @@ import Network.Wai.Handler.Warp.Request
4840import Network.Wai.Handler.Warp.Response
4941import Network.Wai.Handler.Warp.SendFile
5042import Network.Wai.Handler.Warp.Settings
43+import qualified Network.Wai.Handler.Warp.Timeout as T
5144import Network.Wai.Handler.Warp.Types
5245
5346
@@ -58,38 +51,21 @@ import Network.Socket (fdSocket)
5851#endif
5952
6053-- | Creating 'Connection' for plain HTTP based on a given socket.
61−socketConnection :: Settings -> Socket -> IO Connection
62−#if MIN_VERSION_network(3,1,1)
63−socketConnection set s = do
64−#else
65−socketConnection _ s = do
66−#endif
54+socketConnection :: Socket -> IO Connection
55+socketConnection s = do
6756 bufferPool <- newBufferPool
6857 writeBuf <- allocateBuffer bufferSize
6958 let sendall = Sock.sendAll s
70− isH2 <- newIORef False -- HTTP/1.x
7159 return Connection {
7260 connSendMany = Sock.sendMany s
7361 , connSendAll = sendall
7462 , connSendFile = sendFile s writeBuf bufferSize sendall
75−#if MIN_VERSION_network(3,1,1)
76− , connClose = do
77− h2 <- readIORef isH2
78− let tm = if h2 then settingsGracefulCloseTimeout2 set
79− else settingsGracefulCloseTimeout1 set
80− if tm == 0 then
81− close s
82− else
83− gracefulClose s tm `E.catch` \(E.SomeException _) -> return ()
84−#else
8563 , connClose = close s
86−#endif
8764 , connFree = freeBuffer writeBuf
8865 , connRecv = receive s bufferPool
8966 , connRecvBuf = receiveBuf s
9067 , connWriteBuffer = writeBuf
9168 , connBufferSize = bufferSize
92− , connHTTP2 = isH2
9369 }
9470
9571-- | Run an 'Application' on the given port.
@@ -151,7 +127,7 @@ runSettingsSocket set socket app = do
151127 setSocketCloseOnExec s
152128 -- NoDelay causes an error for AF_UNIX.
153129 setSocketOption s NoDelay 1 `E.catch` \(E.SomeException _) -> return ()
154− conn <- socketConnection set s
130+ conn <- socketConnection s
155131 return (conn, sa)
156132
157133 closeListenSocket = close socket
@@ -192,15 +168,15 @@ runSettingsConnectionMakerSecure :: Settings -> IO (IO (Connection, Transport),
192168runSettingsConnectionMakerSecure set getConnMaker app = do
193169 settingsBeforeMainLoop set
194170 counter <- newCounter
195− withII $ acceptConnection set getConnMaker app counter
171+ withII0 $ acceptConnection set getConnMaker app counter
196172 where
197− withII action =
173+ withII0 action =
198174 withTimeoutManager $ \tm ->
199175 D.withDateCache $ \dc ->
200176 F.withFdCache fdCacheDurationInSeconds $ \fdc ->
201177 I.withFileInfoCache fdFileInfoDurationInSeconds $ \fic -> do
202− let ii = InternalInfo tm dc fdc fic
203− action ii
178+ let ii0 = InternalInfo0 tm dc fdc fic
179+ action ii0
204180
205181 !fdCacheDurationInSeconds = settingsFdCacheDuration set * 1000000
206182 !fdFileInfoDurationInSeconds = settingsFileInfoCacheDuration set * 1000000
@@ -229,9 +205,9 @@ acceptConnection :: Settings
229205 -> IO (IO (Connection, Transport), SockAddr)
230206 -> Application
231207 -> Counter
232− -> InternalInfo
208+ -> InternalInfo0
233209 -> IO ()
234−acceptConnection set getConnMaker app counter ii = do
210+acceptConnection set getConnMaker app counter ii0 = do
235211 -- First mask all exceptions in acceptLoop. This is necessary to
236212 -- ensure that no async exception is throw between the call to
237213 -- acceptNewConnection and the registering of connClose.
@@ -258,7 +234,7 @@ acceptConnection set getConnMaker app counter ii = do
258234 case mx of
259235 Nothing -> return ()
260236 Just (mkConn, addr) -> do
261− fork set mkConn addr app counter ii
237+ fork set mkConn addr app counter ii0
262238 acceptLoop
263239
264240 acceptNewConnection = do
@@ -281,12 +257,15 @@ fork :: Settings
281257 -> SockAddr
282258 -> Application
283259 -> Counter
284− -> InternalInfo
260+ -> InternalInfo0
285261 -> IO ()
286−fork set mkConn addr app counter ii = settingsFork set $ \unmask ->
262+fork set mkConn addr app counter ii0 = settingsFork set $ \unmask ->
287263 -- Call the user-supplied on exception code if any
288264 -- exceptions are thrown.
289− handle (settingsOnException set Nothing) $
265+ handle (settingsOnException set Nothing) .
266+ -- Allocate a new IORef indicating whether the connection has been
267+ -- closed, to avoid double-freeing a connection
268+ withClosedRef $ \ref ->
290269 -- Run the connection maker to get a new connection, and ensure
291270 -- that the connection is closed. If the mkConn call throws an
292271 -- exception, we will leak the connection. If the mkConn call is
@@ -297,15 +276,24 @@ fork set mkConn addr app counter ii = settingsFork set $ \unmask ->
297276 -- We grab the connection before registering timeouts since the
298277 -- timeouts will be useless during connection creation, due to the
299278 -- fact that async exceptions are still masked.
300− bracket mkConn cleanUp (serve unmask)
279+ bracket mkConn (cleanUp ref) (serve unmask ref)
301280 where
302− cleanUp (conn, _) = connClose conn `finally` connFree conn
281+ withClosedRef inner = newIORef False >>= inner
282+
283+ closeConn ref conn = do
284+ isClosed <- atomicModifyIORef' ref $ \x -> (True, x)
285+ unless isClosed $ connClose conn
286+
287+ cleanUp ref (conn, _) = closeConn ref conn `finally` connFree conn
303288
304289 -- We need to register a timeout handler for this thread, and
305− -- cancel that handler as soon as we exit.
306− serve unmask (conn, transport) = bracket register cancel $ \th -> do
290+ -- cancel that handler as soon as we exit. We additionally close
291+ -- the connection immediately in case the child thread catches the
292+ -- async exception or performs some long-running cleanup action.
293+ serve unmask ref (conn, transport) = bracket register cancel $ \th -> do
294+ let ii1 = toInternalInfo1 ii0 th
307295 -- We now have fully registered a connection close handler in
308− -- the case of all exceptions, so it is safe to once again
296+ -- the case of all exceptions, so it is safe to one again
309297 -- allow async exceptions.
310298 unmask .
311299 -- Call the user-supplied code for connection open and
@@ -313,23 +301,23 @@ fork set mkConn addr app counter ii = settingsFork set $ \unmask ->
313301 bracket (onOpen addr) (onClose addr) $ \goingon ->
314302 -- Actually serve this connection. bracket with closeConn
315303 -- above ensures the connection is closed.
316− when goingon $ serveConnection conn ii th addr transport set app
304+ when goingon $ serveConnection conn ii1 addr transport set app
317305 where
318− register = T.registerKillThread (timeoutManager ii) (connClose conn)
306+ register = T.registerKillThread (timeoutManager0 ii0)
307+ (closeConn ref conn)
319308 cancel = T.cancel
320309
321310 onOpen adr = increase counter >> settingsOnOpen set adr
322311 onClose adr _ = decrease counter >> settingsOnClose set adr
323312
324313serveConnection :: Connection
325− -> InternalInfo
326− -> T.Handle
314+ -> InternalInfo1
327315 -> SockAddr
328316 -> Transport
329317 -> Settings
330318 -> Application
331319 -> IO ()
332−serveConnection conn ii th origAddr transport settings app = do
320+serveConnection conn ii1 origAddr transport settings app = do
333321 -- fixme: Upgrading to HTTP/2 should be supported.
334322 (h2,bs) <- if isHTTP2 transport then
335323 return (True, "")
@@ -342,35 +330,22 @@ serveConnection conn ii th origAddr transport settings app = do
342330 istatus <- newIORef False
343331 if settingsHTTP2Enabled settings && h2 then do
344332 rawRecvN <- makeReceiveN bs (connRecv conn) (connRecvBuf conn)
345− -- This thread becomes the sender in http2 library.
346− -- In the case of event source, one request comes and one
347− -- worker gets busy. But it is likely that the receiver does
348− -- not receive any data at all while the sender is sending
349− -- output data from the worker. It's not good enough to tickle
350− -- the time handler in the receiver only. So, we should tickle
351− -- the time handler in both the receiver and the sender.
352333 let recvN = wrappedRecvN th istatus (settingsSlowlorisSize settings) rawRecvN
353− sendBS x = connSendAll conn x >> T.tickle th
354334 -- fixme: origAddr
355− checkTLS
356− setConnHTTP2 conn True
357− http2 conn transport ii origAddr settings recvN sendBS app
335+ http2 conn ii1 origAddr transport settings recvN app
358336 else do
359337 src <- mkSource (wrappedRecv conn th istatus (settingsSlowlorisSize settings))
360338 writeIORef istatus True
361339 leftoverSource src bs
362340 addr <- getProxyProtocolAddr src
363341 http1 True addr istatus src `E.catch` \e ->
364− case () of
365− ()
366− -- See comment below referencing
367− -- https://github.com/yesodweb/wai/issues/618
368− | Just NoKeepAliveRequest <- fromException e -> return ()
369− -- No valid request
370− | Just (BadFirstLine _) <- fromException e -> return ()
371− | otherwise -> do
372− _ <- sendErrorResponse (dummyreq addr) istatus e
373− throwIO e
342+ case fromException e of
343+ -- See comment below referencing
344+ -- https://github.com/yesodweb/wai/issues/618
345+ Just NoKeepAliveRequest -> return ()
346+ Nothing -> do
347+ sendErrorResponse (dummyreq addr) istatus e
348+ throwIO e
374349
375350 where
376351 getProxyProtocolAddr src =
@@ -413,25 +388,29 @@ serveConnection conn ii th origAddr transport settings app = do
413388
414389 decodeAscii = map (chr . fromEnum) . S.unpack
415390
391+ th = threadHandle1 ii1
392+
416393 shouldSendErrorResponse se
417394 | Just ConnectionClosedByPeer <- fromException se = False
418395 | otherwise = True
419396
420397 sendErrorResponse req istatus e = do
421398 status <- readIORef istatus
422− if shouldSendErrorResponse e && status
423− then do
424− sendResponse settings conn ii th req defaultIndexRequestHeader (return S.empty) (errorResponse e)
425− else return False
399+ when (shouldSendErrorResponse e && status) $ do
400+ let ii = toInternalInfo ii1 0 -- dummy
401+ void $ sendResponse settings conn ii req defaultIndexRequestHeader (return S.empty) (errorResponse e)
426402
427403 dummyreq addr = defaultRequest { remoteHost = addr }
428404
429405 errorResponse e = settingsOnExceptionResponse settings e
430406
431407 http1 firstRequest addr istatus src = do
432− (req, mremainingRef, idxhdr, nextBodyFlush) <- recvRequest firstRequest settings conn ii th addr src transport
433− keepAlive <- processRequest istatus src req mremainingRef idxhdr nextBodyFlush
408+ (req', mremainingRef, idxhdr, nextBodyFlush, ii) <- recvRequest firstRequest settings conn ii1 addr src
409+ let req = req' { isSecure = isTransportSecure transport }
410+ keepAlive <- processRequest istatus src req mremainingRef idxhdr nextBodyFlush ii
434411 `E.catch` \e -> do
412+ -- Call the user-supplied exception handlers, passing the request.
413+ sendErrorResponse req istatus e
435414 settingsOnException settings (Just req) e
436415 -- Don't throw the error again to prevent calling settingsOnException twice.
437416 return False
@@ -446,7 +425,7 @@ serveConnection conn ii th origAddr transport settings app = do
446425 -- and ignore. See: https://github.com/yesodweb/wai/issues/618
447426 when keepAlive $ http1 False addr istatus src
448427
449− processRequest istatus src req mremainingRef idxhdr nextBodyFlush = do
428+ processRequest istatus src req mremainingRef idxhdr nextBodyFlush ii = do
450429 -- Let the application run for as long as it wants
451430 T.pause th
452431
@@ -454,38 +433,30 @@ serveConnection conn ii th origAddr transport settings app = do
454433 -- creating the request, we need to make sure that we don't get
455434 -- an async exception before calling the ResponseSource.
456435 keepAliveRef <- newIORef $ error "keepAliveRef not filled"
457− r <- E.try $ app req $ \res -> do
436+ _ <- app req $ \res -> do
458437 T.resume th
459438 -- FIXME consider forcing evaluation of the res here to
460439 -- send more meaningful error messages to the user.
461440 -- However, it may affect performance.
462441 writeIORef istatus False
463− keepAlive <- sendResponse settings conn ii th req idxhdr (readSource src) res
442+ keepAlive <- sendResponse settings conn ii req idxhdr (readSource src) res
464443 writeIORef keepAliveRef keepAlive
465444 return ResponseReceived
466− case r of
467− Right ResponseReceived -> return ()
468− Left e@(SomeException _)
469− | Just (ExceptionInsideResponseBody e') <- fromException e -> throwIO e'
470− | otherwise -> do
471− keepAlive <- sendErrorResponse req istatus e
472− settingsOnException settings (Just req) e
473− writeIORef keepAliveRef keepAlive
474−
475445 keepAlive <- readIORef keepAliveRef
476446
477447 -- We just send a Response and it takes a time to
478448 -- receive a Request again. If we immediately call recv,
479− -- it is likely to fail and cause the IO manager to do some work.
480− -- It is very costly, so we yield to another Haskell
449+ -- it is likely to fail and the IO manager works.
450+ -- It is very costly. So, we yield to another Haskell
481451 -- thread hoping that the next Request will arrive
482452 -- when this Haskell thread will be re-scheduled.
483453 -- This improves performance at least when
484454 -- the number of cores is small.
485455 Conc.yield
486456
487− if keepAlive
488− then
457+ if not keepAlive then
458+ return False
459+ else
489460 -- If there is an unknown or large amount of data to still be read
490461 -- from the request body, simple drop this connection instead of
491462 -- reading it all in to satisfy a keep-alive request.
@@ -511,21 +482,6 @@ serveConnection conn ii th origAddr transport settings app = do
511482 else
512483 return False
513484 Nothing -> tryKeepAlive
514− else
515− return False
516−
517− checkTLS = case transport of
518− TCP -> return () -- direct
519− tls -> unless (tls12orLater tls) $ goaway conn H2.InadequateSecurity "Weak TLS"
520− tls12orLater tls = tlsMajorVersion tls == 3 && tlsMinorVersion tls >= 3
521−
522−-- connClose must not be called here since Run:fork calls it
523−goaway :: Connection -> H2.ErrorCodeId -> ByteString -> IO ()
524−goaway Connection{..} etype debugmsg = connSendAll bytestream
525− where
526− einfo = H2.encodeInfo id 0
527− frame = H2.GoAwayFrame 0 etype debugmsg
528− bytestream = H2.encodeFrame einfo frame
529485
530486flushEntireBody :: IO ByteString -> IO ()
531487flushEntireBody src =
warp/Network/Wai/Handler/Warp/Settings.hs+10−54
@@ -8,22 +8,20 @@ import Control.Concurrent (forkIOWithUnmask)
88import Control.Exception
99import Data.ByteString.Builder (byteString)
1010import qualified Data.ByteString.Char8 as C8
11−import Data.ByteString.Lazy (fromStrict)
1211import Data.Streaming.Network (HostPreference)
1312import qualified Data.Text as T
1413import qualified Data.Text.IO as TIO
1514import Data.Version (showVersion)
1615import GHC.IO.Exception (IOErrorType(..))
1716import qualified Network.HTTP.Types as H
18−import Network.HTTP2( HTTP2Error (..), ErrorCodeId (..) )
1917import Network.Socket (SockAddr)
2018import Network.Wai
2119import qualified Paths_warp
2220import System.IO (stderr)
2321import System.IO.Error (ioeGetErrorType)
24−import System.TimeManager
2522
2623import Network.Wai.Handler.Warp.Imports
24+import Network.Wai.Handler.Warp.Timeout
2725import Network.Wai.Handler.Warp.Types
2826
2927-- | Various Warp server settings. This is purposely kept as an abstract data
@@ -76,13 +74,6 @@ data Settings = Settings
7674 --
7775 -- Since 2.0.3
7876 , settingsInstallShutdownHandler :: IO () -> IO ()
79− -- ^ An action to install a handler (e.g. Unix signal handler)
80− -- to close a listen socket.
81− -- The first argument is an action to close the listen socket.
82− --
83− -- Default: no action
84− --
85− -- Since 3.0.1
8677 , settingsServerName :: ByteString
8778 -- ^ Default server name if application does not set one.
8879 --
@@ -94,44 +85,28 @@ data Settings = Settings
9485 , settingsProxyProtocol :: ProxyProtocol
9586 -- ^ Specify usage of the PROXY protocol.
9687 --
97− -- Since 3.0.5
88+ -- Since 3.0.5.
9889 , settingsSlowlorisSize :: Int
9990 -- ^ Size of bytes read to prevent Slowloris protection. Default value: 2048
10091 --
101− -- Since 3.1.2
92+ -- Since 3.1.2.
10293 , settingsHTTP2Enabled :: Bool
10394 -- ^ Whether to enable HTTP2 ALPN/upgrades. Default: True
10495 --
105− -- Since 3.1.7
96+ -- Since 3.1.7.
10697 , settingsLogger :: Request -> H.Status -> Maybe Integer -> IO ()
10798 -- ^ A log function. Default: no action.
10899 --
109− -- Since 3.1.10
100+ -- Since 3.X.X.
110101 , settingsServerPushLogger :: Request -> ByteString -> Integer -> IO ()
111102 -- ^ A HTTP/2 server push log function. Default: no action.
112103 --
113− -- Since 3.2.7
104+ -- Since 3.X.X.
114105 , settingsGracefulShutdownTimeout :: Maybe Int
115106 -- ^ An optional timeout to limit the time (in seconds) waiting for
116107 -- a graceful shutdown of the web server.
117108 --
118109 -- Since 3.2.8
119− , settingsGracefulCloseTimeout1 :: Int
120− -- ^ A timeout to limit the time (in milliseconds) waiting for
121− -- FIN for HTTP/1.x. 0 means uses immediate close.
122− -- Default: 0.
123− --
124− -- Since 3.3.5
125− , settingsGracefulCloseTimeout2 :: Int
126− -- ^ A timeout to limit the time (in milliseconds) waiting for
127− -- FIN for HTTP/2. 0 means uses immediate close.
128− -- Default: 2000.
129− --
130− -- Since 3.3.5
131− , settingsMaxTotalHeaderLength :: Int
132− -- ^ Determines the maximum header size that Warp will tolerate when using HTTP/1.x.
133− --
134− -- Since 3.3.8
135110 }
136111
137112-- | Specify usage of the PROXY protocol.
@@ -168,9 +143,6 @@ defaultSettings = Settings
168143 , settingsLogger = \_ _ _ -> return ()
169144 , settingsServerPushLogger = \_ _ _ -> return ()
170145 , settingsGracefulShutdownTimeout = Nothing
171− , settingsGracefulCloseTimeout1 = 0
172− , settingsGracefulCloseTimeout2 = 2000
173− , settingsMaxTotalHeaderLength = 50 * 1024
174146 }
175147
176148-- | Apply the logic provided by 'defaultOnException' to determine if an
@@ -196,29 +168,13 @@ defaultOnException _ e =
196168 when (defaultShouldDisplayException e)
197169 $ TIO.hPutStrLn stderr $ T.pack $ show e
198170
199−-- | Sending 400 for bad requests.
200−-- Sending 500 for internal server errors.
171+-- | Sending 400 for bad requests. Sending 500 for internal server errors.
172+--
201173-- Since: 3.1.0
202−-- Sending 413 for too large payload.
203−-- Sending 431 for too large headers.
204−-- Since 3.2.27
205174defaultOnExceptionResponse :: SomeException -> Response
206175defaultOnExceptionResponse e
207− | Just (_ :: InvalidRequest) <-
208− fromException e = responseLBS H.badRequest400
209− [(H.hContentType, "text/plain; charset=utf-8")]
210− "Bad Request"
211− | Just (ConnectionError (UnknownErrorCode 413) t) <-
212− fromException e = responseLBS H.status413
213− [(H.hContentType, "text/plain; charset=utf-8")]
214− (fromStrict t)
215− | Just (ConnectionError (UnknownErrorCode 431) t) <-
216− fromException e = responseLBS H.status431
217− [(H.hContentType, "text/plain; charset=utf-8")]
218− (fromStrict t)
219− | otherwise = responseLBS H.internalServerError500
220− [(H.hContentType, "text/plain; charset=utf-8")]
221− "Something went wrong"
176+ | Just (_ :: InvalidRequest) <- fromException e = responseLBS H.badRequest400 [(H.hContentType, "text/plain; charset=utf-8")] "Bad Request"
177+ | otherwise = responseLBS H.internalServerError500 [(H.hContentType, "text/plain; charset=utf-8")] "Something went wrong"
222178
223179-- | Exception handler for the debugging purpose.
224180-- 500, text/plain, a showed exception.
time-manager/System/TimeManager.hs → warp/Network/Wai/Handler/Warp/Timeout.hsrenamed+3−2
@@ -1,6 +1,7 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE DeriveDataTypeable #-}
23
3−module System.TimeManager (
4+module Network.Wai.Handler.Warp.Timeout (
45 -- ** Types
56 Manager
67 , TimeoutAction
@@ -114,7 +115,7 @@ instance E.Exception TimeoutThread where
114115 toException = E.asyncExceptionToException
115116 fromException = E.asyncExceptionFromException
116117instance Show TimeoutThread where
117− show TimeoutThread = "Thread killed by timeout manager"
118+ show TimeoutThread = "Thread killed by Warp's timeout reaper"
118119
119120----------------------------------------------------------------
120121
warp/Network/Wai/Handler/Warp/Types.hs+30−25
@@ -8,15 +8,14 @@ import Control.Exception
88import qualified Data.ByteString as S
99import Data.IORef (IORef, readIORef, writeIORef, newIORef)
1010import Data.Typeable (Typeable)
11−import Data.X509
1211import Foreign.Ptr (Ptr)
1312import System.Posix.Types (Fd)
14−import qualified System.TimeManager as T
1513
1614import qualified Network.Wai.Handler.Warp.Date as D
1715import qualified Network.Wai.Handler.Warp.FdCache as F
1816import qualified Network.Wai.Handler.Warp.FileInfoCache as I
1917import Network.Wai.Handler.Warp.Imports
18+import qualified Network.Wai.Handler.Warp.Timeout as T
2019
2120----------------------------------------------------------------
2221
@@ -53,20 +52,6 @@ instance Exception InvalidRequest
5352
5453----------------------------------------------------------------
5554
56−-- | Exception thrown if something goes wrong while in the midst of
57−-- sending a response, since the status code can't be altered at that
58−-- point.
59−--
60−-- Used to determine whether keeping the HTTP1.1 connection / HTTP2 stream alive is safe
61−-- or irrecoverable.
62−
63−newtype ExceptionInsideResponseBody = ExceptionInsideResponseBody SomeException
64− deriving (Show, Typeable)
65−
66−instance Exception ExceptionInsideResponseBody
67−
68−----------------------------------------------------------------
69−
7055-- | Data type to abstract file identifiers.
7156-- On Unix, a file descriptor would be specified to make use of
7257-- the file descriptor cache.
@@ -123,25 +108,46 @@ data Connection = Connection {
123108 , connWriteBuffer :: Buffer
124109 -- | The size of the write buffer.
125110 , connBufferSize :: BufSize
126− -- | Is this connection HTTP/2?
127− , connHTTP2 :: IORef Bool
128111 }
129112
130−getConnHTTP2 :: Connection -> IO Bool
131−getConnHTTP2 conn = readIORef (connHTTP2 conn)
113+----------------------------------------------------------------
114+
115+type Hash = Int
132116
133−setConnHTTP2 :: Connection -> Bool -> IO ()
134−setConnHTTP2 conn b = writeIORef (connHTTP2 conn) b
135117
136−----------------------------------------------------------------
118+data InternalInfo0 =
119+ InternalInfo0 T.Manager
120+ (IO D.GMTDate)
121+ (Hash -> FilePath -> IO (Maybe F.Fd, F.Refresh))
122+ (Hash -> FilePath -> IO I.FileInfo)
123+
124+timeoutManager0 :: InternalInfo0 -> T.Manager
125+timeoutManager0 (InternalInfo0 tm _ _ _) = tm
126+
127+data InternalInfo1 =
128+ InternalInfo1 T.Handle
129+ T.Manager
130+ (IO D.GMTDate)
131+ (Hash -> FilePath -> IO (Maybe F.Fd, F.Refresh))
132+ (Hash -> FilePath -> IO I.FileInfo)
133+
134+toInternalInfo1 :: InternalInfo0 -> T.Handle -> InternalInfo1
135+toInternalInfo1 (InternalInfo0 b c d e) a = InternalInfo1 a b c d e
136+
137+threadHandle1 :: InternalInfo1 -> T.Handle
138+threadHandle1 (InternalInfo1 th _ _ _ _) = th
137139
138140data InternalInfo = InternalInfo {
139− timeoutManager :: T.Manager
141+ threadHandle :: T.Handle
142+ , timeoutManager :: T.Manager
140143 , getDate :: IO D.GMTDate
141144 , getFd :: FilePath -> IO (Maybe F.Fd, F.Refresh)
142145 , getFileInfo :: FilePath -> IO I.FileInfo
143146 }
144147
148+toInternalInfo :: InternalInfo1 -> Hash -> InternalInfo
149+toInternalInfo (InternalInfo1 a b c d e) h = InternalInfo a b c (d h) (e h)
150+
145151----------------------------------------------------------------
146152
147153-- | Type for input streaming.
@@ -180,7 +186,6 @@ data Transport = TCP -- ^ Plain channel: TCP
180186 , tlsMinorVersion :: Int
181187 , tlsNegotiatedProtocol :: Maybe ByteString -- ^ The result of Application Layer Protocol Negociation in RFC 7301
182188 , tlsChiperID :: Word16
183− , tlsClientCertificate :: Maybe CertificateChain
184189 } -- ^ Encrypted channel: TLS or SSL
185190
186191isTransportSecure :: Transport -> Bool
warp/test/FdCacheSpec.hs+1−1
@@ -17,7 +17,7 @@ spec = describe "withFdCache" $ do
1717 it "clean up Fd" $ do
1818 ref <- newIORef (Fd (-1))
1919 withFdCache 30000000 $ \getFd -> do
20− (Just fd,_) <- getFd "warp.cabal"
20+ (Just fd,_) <- getFd 0 "warp.cabal"
2121 writeIORef ref fd
2222 nfd <- readIORef ref
2323 fdRead nfd 1 `shouldThrow` anyIOException
warp/test/FileSpec.hs+1−1
@@ -21,7 +21,7 @@ testFileRange desc reqhs file ans = it desc $ do
2121 let WithBody s hs off len = ans
2222 hs' = ("Last-Modified",fileInfoDate finfo) : hs
2323 ans' = WithBody s hs' off len
24− conditionalRequest finfo [] (indexResponseHeader hs) (indexRequestHeader reqhs) `shouldBe` ans'
24+ conditionalRequest finfo [] (indexRequestHeader reqhs) `shouldBe` ans'
2525
2626spec :: Spec
2727spec = do
warp/test/RequestSpec.hs+7−11
@@ -7,7 +7,6 @@ module RequestSpec (main, spec) where
77import Network.Wai.Handler.Warp.File (parseByteRanges)
88import Network.Wai.Handler.Warp.Request
99import Network.Wai.Handler.Warp.Types
10−import Network.Wai.Handler.Warp.Settings (settingsMaxTotalHeaderLength, defaultSettings)
1110import Test.Hspec
1211import Test.Hspec.QuickCheck
1312import qualified Data.ByteString as S
@@ -18,9 +17,6 @@ import Data.IORef
1817main :: IO ()
1918main = hspec spec
2019
21−defaultMaxTotalHeaderLength :: Int
22−defaultMaxTotalHeaderLength = settingsMaxTotalHeaderLength defaultSettings
23−
2420spec :: Spec
2521spec = do
2622 describe "headerLines" $ do
@@ -67,29 +63,29 @@ spec = do
6763 describe "headerLines" $ do
6864 it "can handle a nomarl case" $ do
6965 src <- mkSourceFunc ["Status: 200\r\nContent-Type: text/plain\r\n\r\n"] >>= mkSource
70− x <- headerLines defaultMaxTotalHeaderLength True src
66+ x <- headerLines True src
7167 x `shouldBe` ["Status: 200", "Content-Type: text/plain"]
7268
7369 it "can handle a nasty case (1)" $ do
7470 src <- mkSourceFunc ["Status: 200", "\r\nContent-Type: text/plain", "\r\n\r\n"] >>= mkSource
75− x <- headerLines defaultMaxTotalHeaderLength True src
71+ x <- headerLines True src
7672 x `shouldBe` ["Status: 200", "Content-Type: text/plain"]
7773
7874 it "can handle a nasty case (1)" $ do
7975 src <- mkSourceFunc ["Status: 200", "\r", "\nContent-Type: text/plain", "\r", "\n\r\n"] >>= mkSource
80− x <- headerLines defaultMaxTotalHeaderLength True src
76+ x <- headerLines True src
8177 x `shouldBe` ["Status: 200", "Content-Type: text/plain"]
8278
8379 it "can handle a nasty case (1)" $ do
8480 src <- mkSourceFunc ["Status: 200", "\r", "\n", "Content-Type: text/plain", "\r", "\n", "\r", "\n"] >>= mkSource
85− x <- headerLines defaultMaxTotalHeaderLength True src
81+ x <- headerLines True src
8682 x `shouldBe` ["Status: 200", "Content-Type: text/plain"]
8783
8884 it "can handle an illegal case (1)" $ do
8985 src <- mkSourceFunc ["\nStatus:", "\n 200", "\nContent-Type: text/plain", "\r\n\r\n"] >>= mkSource
90− x <- headerLines defaultMaxTotalHeaderLength True src
86+ x <- headerLines True src
9187 x `shouldBe` []
92− y <- headerLines defaultMaxTotalHeaderLength True src
88+ y <- headerLines True src
9389 y `shouldBe` ["Status: 200", "Content-Type: text/plain"]
9490
9591 where
@@ -115,7 +111,7 @@ headerLinesList' orig = do
115111 writeIORef ref z
116112 return y
117113 src' <- mkSource src
118− res <- headerLines defaultMaxTotalHeaderLength True src'
114+ res <- headerLines True src'
119115 return (res, src')
120116
121117consumeLen :: Int -> Source -> IO S8.ByteString
warp/test/ResponseHeaderSpec.hs+35−2
@@ -7,6 +7,9 @@ import qualified Network.HTTP.Types as H
77import Network.Wai.Handler.Warp.ResponseHeader
88import Network.Wai.Handler.Warp.Response
99import Network.Wai.Handler.Warp.Header
10+import Network.Wai.Handler.Warp.HTTP2.HPACK
11+import Network.HPACK
12+import Network.HPACK.Token
1013import Test.Hspec
1114
1215main :: IO ()
@@ -34,15 +37,45 @@ spec = do
3437 let hdrs = [("Server","MyServer")]
3538 rspidxhdr = indexResponseHeader hdrs
3639 addServer "" rspidxhdr hdrs `shouldBe` []
40+ describe "addHeader" $ do
41+ it "adds Server if not exist" $ do
42+ let v = "MyServer"
43+ hdrs = []
44+ hdrs1 = [("Server",v)]
45+ (thl, vt) <- toHeaderTable hdrs
46+ (thl1, _) <- toHeaderTable hdrs1
47+ addHeader tokenServer v vt thl `shouldBe` thl1
48+ it "does not add Server if exists" $ do
49+ let v = "MyServer2"
50+ hdrs = [("Server","MyServer")]
51+ (thl, vt) <- toHeaderTable hdrs
52+ addHeader tokenServer v vt thl `shouldBe` thl
53+ it "does not add Server if empty" $ do
54+ let v = ""
55+ hdrs = []
56+ (thl, vt) <- toHeaderTable hdrs
57+ addHeader tokenServer v vt thl `shouldBe` thl
58+ it "deletes Server" $ do
59+ let v = ""
60+ hdrs = [("Server","MyServer")]
61+ hdrs1 = []
62+ (thl, vt) <- toHeaderTable hdrs
63+ (thl1, _) <- toHeaderTable hdrs1
64+ addHeader tokenServer v vt thl `shouldBe` thl1
65+ it "keeps Server" $ do
66+ let v = ""
67+ hdrs = [("Server","MyServer"),("Content-Type","Text/HTML")]
68+ (thl, vt) <- toHeaderTable hdrs
69+ addHeader tokenContentType v vt thl `shouldBe` thl
3770
3871headers :: H.ResponseHeaders
3972headers = [
4073 ("Date", "Mon, 13 Aug 2012 04:22:55 GMT")
41− , ("Content-Length", "151")
74+ , ("Content-Lenght", "151")
4275 , ("Server", "Mighttpd/2.5.8")
4376 , ("Last-Modified", "Fri, 22 Jun 2012 01:18:08 GMT")
4477 , ("Content-Type", "text/html")
4578 ]
4679
4780composedHeader :: ByteString
48−composedHeader = "HTTP/1.1 200 OK\r\nDate: Mon, 13 Aug 2012 04:22:55 GMT\r\nContent-Length: 151\r\nServer: Mighttpd/2.5.8\r\nLast-Modified: Fri, 22 Jun 2012 01:18:08 GMT\r\nContent-Type: text/html\r\n\r\n"
81+composedHeader = "HTTP/1.1 200 OK\r\nDate: Mon, 13 Aug 2012 04:22:55 GMT\r\nContent-Lenght: 151\r\nServer: Mighttpd/2.5.8\r\nLast-Modified: Fri, 22 Jun 2012 01:18:08 GMT\r\nContent-Type: text/html\r\n\r\n"
warp/test/ResponseSpec.hs+15−11
@@ -10,7 +10,7 @@ import Network.HTTP.Types
1010import Network.Wai hiding (responseHeaders)
1111import Network.Wai.Handler.Warp
1212import Network.Wai.Handler.Warp.Response
13−import RunSpec (withApp, msWrite, msRead, withMySocket)
13+import RunSpec (withApp, msClose, msWrite, msRead, connectTo)
1414import Test.Hspec
1515
1616main :: IO ()
@@ -20,13 +20,15 @@ testRange :: S.ByteString -- ^ range value
2020 -> String -- ^ expected output
2121 -> Maybe String -- ^ expected content-range value
2222 -> Spec
23−testRange range out crange = it title $ withApp defaultSettings app $ withMySocket $ \ms -> do
24− msWrite ms "GET / HTTP/1.0\r\n"
25− msWrite ms "Range: bytes="
26− msWrite ms range
27− msWrite ms "\r\n\r\n"
23+testRange range out crange = it title $ withApp defaultSettings app $ \port -> do
24+ handle <- connectTo port
25+ msWrite handle "GET / HTTP/1.0\r\n"
26+ msWrite handle "Range: bytes="
27+ msWrite handle range
28+ msWrite handle "\r\n\r\n"
2829 threadDelay 10000
29− bss <- fmap (lines . filter (/= '\r') . S8.unpack) $ msRead ms 1024
30+ bss <- fmap (lines . filter (/= '\r') . S8.unpack) $ msRead handle 1024
31+ msClose handle
3032 last bss `shouldBe` out
3133 let hs = mapMaybe toHeader bss
3234 lookup "Content-Range" hs `shouldBe` fmap ("bytes " ++) crange
@@ -44,10 +46,12 @@ testPartial :: Integer -- ^ file size
4446 -> Integer -- ^ byte count
4547 -> String -- ^ expected output
4648 -> Spec
47−testPartial size offset count out = it title $ withApp defaultSettings app $ withMySocket $ \ms -> do
48− msWrite ms "GET / HTTP/1.0\r\n\r\n"
49+testPartial size offset count out = it title $ withApp defaultSettings app $ \port -> do
50+ handle <- connectTo port
51+ msWrite handle "GET / HTTP/1.0\r\n\r\n"
4952 threadDelay 10000
50− bss <- fmap (lines . filter (/= '\r') . S8.unpack) $ msRead ms 1024
53+ bss <- fmap (lines . filter (/= '\r') . S8.unpack) $ msRead handle 1024
54+ msClose handle
5155 out `shouldBe` last bss
5256 let hs = mapMaybe toHeader bss
5357 lookup "Content-Length" hs `shouldBe` Just (show $ length $ last bss)
@@ -83,7 +87,7 @@ spec = do
8387 it "discards empty lines" $ do
8488 sanitizeHeaderValue "foo\r\n\r\nbar" `shouldBe` "foo\r\n bar"
8589
86− context "when sanitizing single occurrences of \n" $ do
90+ context "when sanitizing single occurences of \n" $ do
8791 it "replaces \n with \r\n" $ do
8892 sanitizeHeaderValue "foo\n bar" `shouldBe` "foo\r\n bar"
8993
warp/test/RunSpec.hs+62−56
@@ -1,7 +1,8 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE OverloadedStrings #-}
23{-# LANGUAGE ScopedTypeVariables #-}
34
4−module RunSpec (main, spec, withApp, MySocket, msWrite, msRead, withMySocket) where
5+module RunSpec (main, spec, withApp, connectTo, MySocket, msWrite, msRead, msClose) where
56
67import Control.Concurrent (forkIO, killThread, threadDelay)
78import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
@@ -21,7 +22,6 @@ import Network.HTTP.Types
2122import Network.Socket
2223import Network.Socket.ByteString (sendAll)
2324import Network.Wai hiding (responseHeaders)
24−import Network.Wai.Internal (getRequestBodyChunk)
2525import Network.Wai.Handler.Warp
2626import System.IO.Unsafe (unsafePerformIO)
2727import System.Timeout (timeout)
@@ -37,7 +37,7 @@ type CounterApplication = Counter -> Application
3737
3838data MySocket = MySocket
3939 { msSocket :: !Socket
40− , msBuffer :: !(I.IORef ByteString)
40+ , _msBuffer :: !(I.IORef ByteString)
4141 }
4242
4343msWrite :: MySocket -> ByteString -> IO ()
@@ -70,16 +70,9 @@ msClose :: MySocket -> IO ()
7070msClose = Network.Socket.close . msSocket
7171
7272connectTo :: Int -> IO MySocket
73−connectTo port = do
74− s <- fst <$> getSocketTCP "127.0.0.1" port
75− ref <- I.newIORef mempty
76− return MySocket {
77− msSocket = s
78− , msBuffer = ref
79− }
80−
81−withMySocket :: (MySocket -> IO a) -> Int -> IO a
82−withMySocket body port = bracket (connectTo port) msClose body
73+connectTo port = MySocket
74+ <$> (fst <$> getSocketTCP "127.0.0.1" port)
75+ <*> I.newIORef mempty
8376
8477incr :: MonadIO m => Counter -> m ()
8578incr icount = liftIO $ I.atomicModifyIORef icount $ \ecount ->
@@ -92,7 +85,7 @@ err icount msg = liftIO $ I.writeIORef icount $ Left $ show msg
9285
9386readBody :: CounterApplication
9487readBody icount req f = do
95− body <- consumeBody $ getRequestBodyChunk req
88+ body <- consumeBody $ requestBody req
9689 case () of
9790 ()
9891 | pathInfo req == ["hello"] && L.fromChunks body /= "Hello"
@@ -113,8 +106,8 @@ ignoreBody icount req f = do
113106
114107doubleConnect :: CounterApplication
115108doubleConnect icount req f = do
116− _ <- consumeBody $ getRequestBodyChunk req
117− _ <- consumeBody $ getRequestBodyChunk req
109+ _ <- consumeBody $ requestBody req
110+ _ <- consumeBody $ requestBody req
118111 incr icount
119112 f $ responseLBS status200 [] "double connect"
120113
@@ -143,13 +136,7 @@ withApp settings app f = do
143136 bracket
144137 (forkIO $ runSettings settings' app `onException` putMVar baton ())
145138 killThread
146− (const $ do
147− takeMVar baton
148− -- use timeout to make sure we don't take too long
149− mres <- timeout (60 * 1000 * 1000) (f port)
150− case mres of
151− Nothing -> error "Timeout triggered, too slow!"
152− Just a -> pure a)
139+ (const $ takeMVar baton >> f port)
153140
154141runTest :: Int -- ^ expected number of requests
155142 -> CounterApplication
@@ -157,7 +144,8 @@ runTest :: Int -- ^ expected number of requests
157144 -> IO ()
158145runTest expected app chunks = do
159146 ref <- I.newIORef (Right 0)
160− withApp defaultSettings (app ref) $ withMySocket $ \ms -> do
147+ withApp defaultSettings (app ref) $ \port -> do
148+ ms <- connectTo port
161149 forM_ chunks $ \chunk -> msWrite ms chunk
162150 _ <- timeout 100000 $ replicateM_ expected $ msRead ms 4096
163151 res <- I.readIORef ref
@@ -174,9 +162,10 @@ runTerminateTest :: InvalidRequest
174162runTerminateTest expected input = do
175163 ref <- I.newIORef Nothing
176164 let onExc _ = I.writeIORef ref . Just
177− withApp (setOnException onExc defaultSettings) dummyApp $ withMySocket $ \ms -> do
178− msWrite ms input
179− msClose ms -- explicitly
165+ withApp (setOnException onExc defaultSettings) dummyApp $ \port -> do
166+ handle <- connectTo port
167+ msWrite handle input
168+ msClose handle
180169 threadDelay 1000
181170 res <- I.readIORef ref
182171 show res `shouldBe` show (Just expected)
@@ -247,11 +236,13 @@ spec = do
247236 let app req f = do
248237 liftIO $ I.writeIORef iheaders $ requestHeaders req
249238 f $ responseLBS status200 [] ""
250− withApp defaultSettings app $ withMySocket $ \ms -> do
239+ withApp defaultSettings app $ \port -> do
240+ handle <- connectTo port
251241 let input = S.concat
252242 [ "GET / HTTP/1.1\r\nfoo: bar\r\n baz\r\n\tbin\r\n\r\n"
253243 ]
254− msWrite ms input
244+ msWrite handle input
245+ msClose handle
255246 threadDelay 1000
256247 headers <- I.readIORef iheaders
257248 headers `shouldBe`
@@ -262,11 +253,13 @@ spec = do
262253 let app req f = do
263254 liftIO $ I.writeIORef iheaders $ requestHeaders req
264255 f $ responseLBS status200 [] ""
265− withApp defaultSettings app $ withMySocket $ \ms -> do
256+ withApp defaultSettings app $ \port -> do
257+ handle <- connectTo port
266258 let input = S.concat
267259 [ "GET / HTTP/1.1\r\nfoo:bar\r\n\r\n"
268260 ]
269− msWrite ms input
261+ msWrite handle input
262+ msClose handle
270263 threadDelay 1000
271264 headers <- I.readIORef iheaders
272265 headers `shouldBe`
@@ -278,18 +271,20 @@ spec = do
278271 countVar <- newTVarIO (0 :: Int)
279272 ifront <- I.newIORef id
280273 let app req f = do
281− bss <- consumeBody $ getRequestBodyChunk req
274+ bss <- consumeBody $ requestBody req
282275 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
283276 atomically $ modifyTVar countVar (+ 1)
284277 f $ responseLBS status200 [] ""
285− withApp defaultSettings app $ withMySocket $ \ms -> do
278+ withApp defaultSettings app $ \port -> do
279+ handle <- connectTo port
286280 let input = S.concat
287281 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
288282 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n\r\n"
289283 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
290284 , "b\r\nHello World\r\n0\r\n\r\n"
291285 ]
292− msWrite ms input
286+ msWrite handle input
287+ msClose handle
293288 atomically $ do
294289 count <- readTVar countVar
295290 check $ count == 2
@@ -302,16 +297,18 @@ spec = do
302297 ifront <- I.newIORef id
303298 countVar <- newTVarIO (0 :: Int)
304299 let app req f = do
305− bss <- consumeBody $ getRequestBodyChunk req
300+ bss <- consumeBody $ requestBody req
306301 I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
307302 atomically $ modifyTVar countVar (+ 1)
308303 f $ responseLBS status200 [] ""
309− withApp defaultSettings app $ withMySocket $ \ms -> do
304+ withApp defaultSettings app $ \port -> do
305+ handle <- connectTo port
310306 let input = concat $ replicate 2 $
311307 ["POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"] ++
312308 (replicate 50 "5\r\n12345\r\n") ++
313309 ["0\r\n\r\n"]
314− mapM_ (msWrite ms) input
310+ mapM_ (msWrite handle) input
311+ msClose handle
315312 atomically $ do
316313 count <- readTVar countVar
317314 check $ count == 2
@@ -319,22 +316,25 @@ spec = do
319316 front [] `shouldBe` replicate 2 (S.concat $ replicate 50 "12345")
320317-- For some reason, the following test on Windows causes the socket
321318-- to be killed prematurely. Worth investigating in the future if possible.
319+#if !WINDOWS
322320 it "in chunks" $ do
323321 ifront <- I.newIORef id
324322 countVar <- newTVarIO (0 :: Int)
325323 let app req f = do
326− bss <- consumeBody $ getRequestBodyChunk req
324+ bss <- consumeBody $ requestBody req
327325 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
328326 atomically $ modifyTVar countVar (+ 1)
329327 f $ responseLBS status200 [] ""
330− withApp defaultSettings app $ withMySocket $ \ms -> do
328+ withApp defaultSettings app $ \port -> do
329+ handle <- connectTo port
331330 let input = S.concat
332331 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
333332 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n"
334333 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
335334 , "b\r\nHello World\r\n0\r\n\r\n"
336335 ]
337− mapM_ (msWrite ms) $ map S.singleton $ S.unpack input
336+ mapM_ (msWrite handle) $ map S.singleton $ S.unpack input
337+ msClose handle
338338 atomically $ do
339339 count <- readTVar countVar
340340 check $ count == 2
@@ -343,10 +343,11 @@ spec = do
343343 [ "Hello World\nBye"
344344 , "Hello World"
345345 ]
346+#endif
346347 it "timeout in request body" $ do
347348 ifront <- I.newIORef id
348349 let app req f = do
349− bss <- (consumeBody $ getRequestBodyChunk req) `onException`
350+ bss <- (consumeBody $ requestBody req) `onException`
350351 liftIO (I.atomicModifyIORef ifront (\front -> (front . ("consume interrupted":), ())))
351352 liftIO $ threadDelay 4000000 `E.catch` \e -> do
352353 I.atomicModifyIORef ifront (\front ->
@@ -355,18 +356,20 @@ spec = do
355356 E.throwIO (e :: E.SomeException)
356357 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
357358 f $ responseLBS status200 [] ""
358− withApp (setTimeout 1 defaultSettings) app $ withMySocket $ \ms -> do
359+ withApp (setTimeout 1 defaultSettings) app $ \port -> do
359360 let bs1 = S.replicate 2048 88
360361 bs2 = "This is short"
361362 bs = S.append bs1 bs2
362− msWrite ms "POST / HTTP/1.1\r\n"
363− msWrite ms "content-length: "
364− msWrite ms $ S8.pack $ show $ S.length bs
365− msWrite ms "\r\n\r\n"
363+ handle <- connectTo port
364+ msWrite handle "POST / HTTP/1.1\r\n"
365+ msWrite handle "content-length: "
366+ msWrite handle $ S8.pack $ show $ S.length bs
367+ msWrite handle "\r\n\r\n"
366368 threadDelay 100000
367− msWrite ms bs1
369+ msWrite handle bs1
368370 threadDelay 100000
369− msWrite ms bs2
371+ msWrite handle bs2
372+ msClose handle
370373 threadDelay 5000000
371374 front <- I.readIORef ifront
372375 S.concat (front []) `shouldBe` bs
@@ -382,11 +385,13 @@ spec = do
382385 loop
383386 loop
384387 doubleBS = S.concatMap $ \w -> S.pack [w, w]
385− withApp defaultSettings app $ withMySocket $ \ms -> do
388+ withApp defaultSettings app $ \port -> do
389+ ms <- connectTo port
386390 msWrite ms "POST / HTTP/1.1\r\n\r\n12345"
387391 timeout 100000 (msRead ms 10) >>= (`shouldBe` Just "1122334455")
388392 msWrite ms "67890"
389393 timeout 100000 (msRead ms 10) >>= (`shouldBe` Just "6677889900")
394+
390395 it "only one date and server header" $ do
391396 let app _ f = f $ responseLBS status200
392397 [ ("server", "server")
@@ -403,21 +408,22 @@ spec = do
403408 it "streaming echo #249" $ do
404409 countVar <- newTVarIO (0 :: Int)
405410 let app req f = f $ responseStream status200 [] $ \write _ -> do
406− let loop = do
407− bs <- getRequestBodyChunk req
411+ let loop = do
412+ bs <- requestBody req
408413 unless (S.null bs) $ do
409414 write $ byteString bs
410415 atomically $ modifyTVar countVar (+ 1)
411416 loop
412− loop
413− withApp defaultSettings app $ withMySocket $ \ms -> do
414− msWrite ms "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n"
417+ loop
418+ withApp defaultSettings app $ \port -> do
419+ (sock, _addr) <- getSocketTCP "127.0.0.1" port
420+ sendAll sock "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n"
415421 threadDelay 10000
416− msWrite ms "5\r\nhello\r\n0\r\n\r\n"
422+ sendAll sock "5\r\nhello\r\n0\r\n\r\n"
417423 atomically $ do
418424 count <- readTVar countVar
419425 check $ count >= 1
420− bs <- safeRecv (msSocket ms) 4096 -- must not use msRead
426+ bs <- safeRecv sock 4096
421427 S.takeWhile (/= 13) bs `shouldBe` "HTTP/1.1 200 OK"
422428
423429 it "streaming response with length" $ do
warp/warp.cabal+44−52
@@ -1,5 +1,5 @@
11Name: warp
2−Version: 3.3.9
2+Version: 3.2.26
33Synopsis: A fast, light-weight web server for WAI applications.
44License: MIT
55License-file: LICENSE
@@ -8,7 +8,7 @@ Maintainer: michael@snoyman.com
88Homepage: http://github.com/yesodweb/wai
99Category: Web, Yesod
1010Build-Type: Simple
11−Cabal-Version: >= 1.10
11+Cabal-Version: >=1.8
1212Stability: Stable
1313description: HTTP\/1.0, HTTP\/1.1 and HTTP\/2 are supported.
1414 For HTTP\/2, Warp supports direct and ALPN (in TLS)
@@ -42,21 +42,19 @@ Library
4242 , case-insensitive >= 0.2
4343 , containers
4444 , ghc-prim
45− , hashable
46− , http-date
47− , http-types >= 0.12
48− , http2 >= 2.0 && < 2.1
45+ , http-types >= 0.9.1
4946 , iproute >= 1.3.1
47+ , http2 >= 1.6 && < 1.7
5048 , simple-sendfile >= 0.2.7 && < 0.3
51− , stm >= 2.3
52− , streaming-commons >= 0.1.10
53− , text
54− , time-manager
5549 , unix-compat >= 0.2
56− , vault >= 0.3
5750 , wai >= 3.2 && < 3.3
51+ , text
52+ , streaming-commons >= 0.1.10
53+ , vault >= 0.3
54+ , stm >= 2.3
5855 , word8
59− , x509
56+ , hashable
57+ , http-date
6058 if impl(ghc < 8)
6159 Build-Depends: semigroups
6260 if flag(network-bytestring)
@@ -75,11 +73,15 @@ Library
7573 Network.Wai.Handler.Warp.FileInfoCache
7674 Network.Wai.Handler.Warp.HashMap
7775 Network.Wai.Handler.Warp.HTTP2
76+ Network.Wai.Handler.Warp.HTTP2.EncodeFrame
7877 Network.Wai.Handler.Warp.HTTP2.File
79− Network.Wai.Handler.Warp.HTTP2.PushPromise
78+ Network.Wai.Handler.Warp.HTTP2.HPACK
79+ Network.Wai.Handler.Warp.HTTP2.Manager
80+ Network.Wai.Handler.Warp.HTTP2.Receiver
8081 Network.Wai.Handler.Warp.HTTP2.Request
81− Network.Wai.Handler.Warp.HTTP2.Response
82+ Network.Wai.Handler.Warp.HTTP2.Sender
8283 Network.Wai.Handler.Warp.HTTP2.Types
84+ Network.Wai.Handler.Warp.HTTP2.Worker
8385 Network.Wai.Handler.Warp.Header
8486 Network.Wai.Handler.Warp.IO
8587 Network.Wai.Handler.Warp.Imports
@@ -93,6 +95,7 @@ Library
9395 Network.Wai.Handler.Warp.Run
9496 Network.Wai.Handler.Warp.SendFile
9597 Network.Wai.Handler.Warp.Settings
98+ Network.Wai.Handler.Warp.Timeout
9699 Network.Wai.Handler.Warp.Types
97100 Network.Wai.Handler.Warp.Windows
98101 Network.Wai.Handler.Warp.WithApplication
@@ -109,12 +112,8 @@ Library
109112 else
110113 Build-Depends: unix
111114 Other-modules: Network.Wai.Handler.Warp.MultiMap
112− if impl(ghc >= 8)
113− Default-Extensions: Strict StrictData
114− Default-Language: Haskell2010
115115
116116Test-Suite doctest
117− buildable: False
118117 Type: exitcode-stdio-1.0
119118 HS-Source-Dirs: test
120119 Ghc-Options: -threaded -Wall
@@ -123,9 +122,6 @@ Test-Suite doctest
123122 , doctest >= 0.10.1
124123 if os(windows)
125124 Buildable: False
126− if impl(ghc >= 8)
127− Default-Extensions: Strict StrictData
128− Default-Language: Haskell2010
129125
130126Test-Suite spec
131127 Main-Is: Spec.hs
@@ -151,11 +147,15 @@ Test-Suite spec
151147 Network.Wai.Handler.Warp.File
152148 Network.Wai.Handler.Warp.FileInfoCache
153149 Network.Wai.Handler.Warp.HTTP2
150+ Network.Wai.Handler.Warp.HTTP2.EncodeFrame
154151 Network.Wai.Handler.Warp.HTTP2.File
155− Network.Wai.Handler.Warp.HTTP2.PushPromise
152+ Network.Wai.Handler.Warp.HTTP2.HPACK
153+ Network.Wai.Handler.Warp.HTTP2.Manager
154+ Network.Wai.Handler.Warp.HTTP2.Receiver
156155 Network.Wai.Handler.Warp.HTTP2.Request
157− Network.Wai.Handler.Warp.HTTP2.Response
156+ Network.Wai.Handler.Warp.HTTP2.Sender
158157 Network.Wai.Handler.Warp.HTTP2.Types
158+ Network.Wai.Handler.Warp.HTTP2.Worker
159159 Network.Wai.Handler.Warp.HashMap
160160 Network.Wai.Handler.Warp.Header
161161 Network.Wai.Handler.Warp.IO
@@ -171,6 +171,7 @@ Test-Suite spec
171171 Network.Wai.Handler.Warp.Run
172172 Network.Wai.Handler.Warp.SendFile
173173 Network.Wai.Handler.Warp.Settings
174+ Network.Wai.Handler.Warp.Timeout
174175 Network.Wai.Handler.Warp.Types
175176 Network.Wai.Handler.Warp.Windows
176177 Network.Wai.Handler.Warp.WithApplication
@@ -179,44 +180,42 @@ Test-Suite spec
179180 Hs-Source-Dirs: test, .
180181 Type: exitcode-stdio-1.0
181182
182− Ghc-Options: -Wall -threaded
183+ Ghc-Options: -Wall
183184 Build-Depends: base >= 4.8 && < 5
184− , HUnit
185− , QuickCheck
186185 , array
187− , async
188186 , auto-update
189187 , bsb-http-chunked < 0.1
190188 , bytestring >= 0.9.1.4
191189 , case-insensitive >= 0.2
192− , containers
193− , directory
194190 , ghc-prim
195− , hashable
196− , hspec >= 1.3
197191 , http-client
198− , http-date
199− , http-types >= 0.12
200− , http2 >= 2.0 && < 2.1
192+ , http-types >= 0.8.5
201193 , iproute >= 1.3.1
202194 , lifted-base >= 0.1
203− , network
204− , process
205195 , simple-sendfile >= 0.2.4 && < 0.3
206− , stm >= 2.3
207− , streaming-commons >= 0.1.10
208− , text
209− , time
210− , time-manager
196+ , transformers >= 0.2.2
211197 , unix-compat >= 0.2
212− , vault
213198 , wai >= 3.2 && < 3.3
199+ , network
200+ , HUnit
201+ , QuickCheck
202+ , hspec >= 1.3
203+ , time
204+ , text
205+ , streaming-commons >= 0.1.10
206+ , async
207+ , vault
208+ , stm >= 2.3
209+ , directory
210+ , process
211+ , containers
212+ , http2 >= 1.6 && < 1.7
214213 , word8
215− , x509
214+ , hashable
215+ , http-date
216216 -- Build-Tool-Depends: hspec-discover:hspec-discover
217217 if impl(ghc < 8)
218218 Build-Depends: semigroups
219− , transformers
220219
221220 if (os(linux) || os(freebsd) || os(darwin)) && flag(allow-sendfilefd)
222221 Cpp-Options: -DSENDFILEFD
@@ -224,9 +223,6 @@ Test-Suite spec
224223 if os(windows)
225224 Cpp-Options: -DWINDOWS
226225 Build-Depends: time
227− if impl(ghc >= 8)
228− Default-Extensions: Strict StrictData
229− Default-Language: Haskell2010
230226
231227Benchmark parser
232228 Type: exitcode-stdio-1.0
@@ -237,6 +233,7 @@ Benchmark parser
237233 Network.Wai.Handler.Warp.HashMap
238234 Network.Wai.Handler.Warp.Imports
239235 Network.Wai.Handler.Warp.MultiMap
236+ Network.Wai.Handler.Warp.Timeout
240237 Network.Wai.Handler.Warp.Types
241238 HS-Source-Dirs: bench .
242239 Build-Depends: base >= 4.8 && < 5
@@ -249,9 +246,7 @@ Benchmark parser
249246 , http-types
250247 , network
251248 , network
252− , time-manager
253249 , unix-compat
254− , x509
255250 if impl(ghc < 8)
256251 Build-Depends: semigroups
257252
@@ -261,9 +256,6 @@ Benchmark parser
261256 if os(windows)
262257 Cpp-Options: -DWINDOWS
263258 Build-Depends: time
264− if impl(ghc >= 8)
265− Default-Extensions: Strict StrictData
266− Default-Language: Haskell2010
267259
268260Source-Repository head
269261 Type: git