on ce607dd help sign in
75 files changed+2605−1375
since splitsnapshots
unifiedsplit
.azure/azure-linux-template.yml+28−10 .azure/azure-osx-template.yml+12−2 .azure/azure-windows-template.yml+3−4 .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−11 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−5 stack-lts-12.yaml+6−6 stack-lts-6.yaml+33 stack-lts-9.yaml+29 stack-nightly.yaml−20 stack.yaml+7−6 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+2−2 wai-conduit/Network/Wai/Conduit.hs+6−5 wai-extra/ChangeLog.md−21 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/Test.hs+14−22 wai-extra/example/Main.hs−11 wai-extra/test/Network/Wai/TestSpec.hs+11−41 wai-extra/wai-extra.cabal+4−4 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−27 warp-tls/Network/Wai/Handler/WarpTLS.hs+15−27 warp-tls/warp-tls.cabal+5−5 warp/ChangeLog.md−66 warp/Network/Wai/Handler/Warp.hs+1−51 warp/Network/Wai/Handler/Warp/File.hs+4−7 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+418 warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+13−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+305−12 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+331 warp/Network/Wai/Handler/Warp/Header.hs−2 warp/Network/Wai/Handler/Warp/Recv.hs+4−25 warp/Network/Wai/Handler/Warp/Request.hs+19−21 warp/Network/Wai/Handler/Warp/Response.hs+16−19 warp/Network/Wai/Handler/Warp/Run.hs+34−71 warp/Network/Wai/Handler/Warp/Settings.hs+5−31 warp/Network/Wai/Handler/Warp/Types.hs−10 warp/test/FileSpec.hs+1−1 warp/test/RequestSpec.hs+7−11 warp/test/ResponseHeaderSpec.hs+33 warp/test/ResponseSpec.hs+15−11 warp/test/RunSpec.hs+47−38 warp/warp.cabal+18−14
.azure/azure-linux-template.yml+28−10
@@ -5,6 +5,16 @@ jobs:
55 vmImage: ${{ parameters.vmImage }}
66 strategy:
77 matrix:
8+ stack-def:
9+ BUILD: stack
10+ stack-lts-6:
11+ BUILD: stack
12+ STACK_YAML: stack-lts-6.yaml
13+ ARGS: "--resolver lts-6"
14+ stack-lts-9:
15+ BUILD: stack
16+ STACK_YAML: stack-lts-9.yaml
17+ ARGS: "--resolver lts-9"
818 stack-lts-11:
919 BUILD: stack
1020 STACK_YAML: stack-lts-11.yaml
@@ -13,13 +23,17 @@ jobs:
1323 BUILD: stack
1424 STACK_YAML: stack-lts-12.yaml
1525 ARGS: "--resolver lts-12"
16− stack-lts-14:
26+ stack-lts-13:
1727 BUILD: stack
18− ARGS: "--resolver lts-14"
19− stack-nightly:
20− BUILD: stack
21− STACK_YAML: stack-nightly.yaml
22− ARGS: "--resolver nightly"
28+ ARGS: "--resolver lts-13"
29+ cabal-7.10.3:
30+ BUILD: cabal
31+ GHCVER: 7.10.3
32+ CABALVER: 1.22
33+ cabal-8.0.2:
34+ BUILD: cabal
35+ GHCVER: 8.0.2
36+ CABALVER: 1.24
2337 cabal-8.2.2:
2438 BUILD: cabal
2539 GHCVER: 8.4.4
@@ -28,9 +42,9 @@ jobs:
2842 BUILD: cabal
2943 GHCVER: 8.4.4
3044 CABALVER: 2.2
31− cabal-8.6.5:
45+ cabal-8.6.4:
3246 BUILD: cabal
33− GHCVER: 8.6.5
47+ GHCVER: 8.6.4
3448 CABALVER: 2.4
3549 maxParallel: 6
3650 steps:
@@ -70,7 +84,7 @@ jobs:
7084 for dir in $PACKAGES
7185 do
7286 cd $dir
73− cabal check || [ "$CABALVER" == "1.22" ] # https://github.com/yesodweb/wai/pull/754#issuecomment-504866279
87+ cabal check || [ "$CABALVER" == "1.16" ]
7488 cabal sdist
7589 PKGVER=$(cabal info . | awk '{print $2;exit}')
7690 SRC_TGZ=$PKGVER.tar.gz
@@ -79,7 +93,11 @@ jobs:
7993 cd "$PKGVER"
8094 cabal configure --enable-tests --ghc-options -O0
8195 cabal build
82− cabal test --show-details=streaming
96+ if [ "$CABALVER" = "1.16" ] || [ "$CABALVER" = "1.18" ]; then
97+ cabal test
98+ else
99+ cabal test --show-details=streaming
100+ fi
83101 cd $ORIGDIR
84102 done
85103 ;;
.azure/azure-osx-template.yml+12−2
@@ -5,6 +5,16 @@ jobs:
55 vmImage: ${{ parameters.vmImage }}
66 strategy:
77 matrix:
8+ stack-def:
9+ BUILD: stack
10+ stack-lts-6:
11+ BUILD: stack
12+ STACK_YAML: stack-lts-6.yaml
13+ ARGS: "--resolver lts-6"
14+ stack-lts-9:
15+ BUILD: stack
16+ STACK_YAML: stack-lts-9.yaml
17+ ARGS: "--resolver lts-9"
818 stack-lts-11:
919 BUILD: stack
1020 STACK_YAML: stack-lts-11.yaml
@@ -13,9 +23,9 @@ jobs:
1323 BUILD: stack
1424 STACK_YAML: stack-lts-12.yaml
1525 ARGS: "--resolver lts-12"
16− stack-lts-14:
26+ stack-lts-13:
1727 BUILD: stack
18− ARGS: "--resolver lts-14"
28+ ARGS: "--resolver lts-13"
1929 maxParallel: 3
2030 steps:
2131 - script: |
.azure/azure-windows-template.yml+3−4
@@ -5,9 +5,9 @@ jobs:
55 vmImage: ${{ parameters.vmImage }}
66 strategy:
77 matrix:
8− stack-lts-14:
8+ stack-lts-13:
99 BUILD: stack
10− ARGS: "--resolver lts-14"
10+ ARGS: "--resolver lts-13"
1111 maxParallel: 3
1212 steps:
1313 - bash: |
@@ -16,8 +16,7 @@ jobs:
1616 unzip -o /usr/bin/stack.zip -d /usr/bin/
1717 stack setup $ARGS > /dev/null
1818 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
19+ stack $ARGS test --bench --no-run-benchmarks
2120 env:
2221 OS_NAME: ${{ parameters.os }}
2322 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−11
@@ -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,7 +17,6 @@ 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
@@ -27,12 +26,11 @@ library
2726
2827-- Test suite is currently not robust enough, gives too many false negatives.
2928
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
29+-- test-suite spec
30+-- main-is: Spec.hs
31+-- other-modules: Control.AutoUpdateSpec
32+-- Control.ReaperSpec
33+-- hs-source-dirs: test
34+-- type: exitcode-stdio-1.0
35+-- build-depends: base, auto-update, hspec
36+-- 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−5
@@ -15,13 +15,14 @@ flags:
1515 build-example: true
1616extra-deps:
1717- 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
2318nix:
2419 enable: false
2520 packages:
2621 - fcgi
2722 - zlib
23+image:
24+ containers:
25+ - base: "fpco/pid1"
26+ name: "yesodweb/warp"
27+ executables:
28+ - warp
stack-lts-12.yaml+6−6
@@ -18,9 +18,9 @@ nix:
1818 packages:
1919 - fcgi
2020 - 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
21+image:
22+ containers:
23+ - base: "fpco/pid1"
24+ name: "yesodweb/warp"
25+ executables:
26+ - warp
stack-lts-6.yamlnew file+33
@@ -0,0 +1,33 @@
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+- ./time-manager
13+extra-deps:
14+- bsb-http-chunked-0.0.0.2@sha256:0571ef33ec5530104503d32eaf24f2d7c221f350e5fc475a6dd8e84311bc0ca1,1792
15+- streaming-commons-0.2.1.0@sha256:163af02944f374f316e35257c1afbffefd2c2852a85a151d1ea0ad22152cf28d,4990
16+- tls-session-manager-0.0.0.2@sha256:e3d3f8a026196df46746a19d3d7d31211b2fca3aefed9074e951b2dcc1b8833c,872
17+- wai-logger-2.3.2@sha256:7f5dbc72c0753bd3e0e73f8f542d9d159d1d70f8ff3a3ec16d0b74b500534fc6,1572
18+- gauge-0.2.3@sha256:ce41cb66ebf730a5050181db0338b1a4b3f76ac71331bf5070089bcd4352ff46,3777
19+- psqueues-0.2.7.0@sha256:189970837d0aae01f48cd2a8aac6ce314191939e118cc0488aee1fb12da1956a,4757
20+- basement-0.0.7@rev:0
21+- http-client-0.5.13.1@rev:0
22+- http-types-0.12.3@rev:0
23+nix:
24+ enable: false
25+ packages:
26+ - fcgi
27+ - zlib
28+image:
29+ containers:
30+ - base: "fpco/pid1"
31+ name: "yesodweb/warp"
32+ executables:
33+ - warp
stack-lts-9.yamlnew file+29
@@ -0,0 +1,29 @@
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+- ./time-manager
13+extra-deps:
14+- bsb-http-chunked-0.0.0.2@sha256:0571ef33ec5530104503d32eaf24f2d7c221f350e5fc475a6dd8e84311bc0ca1,1792
15+- streaming-commons-0.2.1.0@sha256:163af02944f374f316e35257c1afbffefd2c2852a85a151d1ea0ad22152cf28d,4990
16+- wai-logger-2.3.2@sha256:7f5dbc72c0753bd3e0e73f8f542d9d159d1d70f8ff3a3ec16d0b74b500534fc6,1572
17+- http-client-0.5.13.1@rev:0
18+- http-types-0.12.3@rev:0
19+nix:
20+ enable: false
21+ packages:
22+ - fcgi
23+ - zlib
24+image:
25+ containers:
26+ - base: "fpco/pid1"
27+ name: "yesodweb/warp"
28+ executables:
29+ - 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−6
@@ -1,4 +1,4 @@
1−resolver: lts-14.22
1+resolver: lts-13.14
22packages:
33- ./auto-update
44- ./mime-types
@@ -18,8 +18,9 @@ nix:
1818 packages:
1919 - fcgi
2020 - 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
21+image:
22+ containers:
23+ - base: "fpco/pid1"
24+ name: "yesodweb/warp"
25+ executables:
26+ - warp
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+2−2
@@ -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
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−21
@@ -1,26 +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−
243## 3.0.26
254
265* Throw 413 for too large payload
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/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/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−4
@@ -1,5 +1,5 @@
11Name: wai-extra
2−Version: 3.0.29.1
2+Version: 3.0.26
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
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−27
@@ -1,30 +1,3 @@
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−
281## 3.2.5
292
303* When tls 1.5.0 is available, TLS 1.3 is automatically supported.
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+5−5
@@ -1,5 +1,5 @@
11Name: warp-tls
2−Version: 3.2.11
2+Version: 3.2.5
33Synopsis: HTTP over TLS support for Warp via the TLS package
44License: MIT
55License-file: LICENSE
@@ -18,16 +18,16 @@ 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)
warp/ChangeLog.md−66
@@ -1,69 +1,3 @@
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
warp/Network/Wai/Handler/Warp.hs+1−51
@@ -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,7 +121,6 @@ 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)
@@ -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/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/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 -> InternalInfo -> SockAddr -> Transport -> S.Settings -> (BufSize -> IO ByteString) -> Application -> IO ()
26+http2 conn ii 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 ii settings addr
44+ tid <- forkIO $ frameReceiver ctx ii 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+import qualified System.TimeManager as T
23+
24+import Network.Wai.Handler.Warp.Imports
25+import Network.Wai.Handler.Warp.Settings
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+418
@@ -0,0 +1,418 @@
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 -> InternalInfo -> MkReq -> (BufSize -> IO ByteString) -> IO ()
31+frameReceiver ctx ii 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{streamPrecedence} -> do
113+ state <- readStreamState strm
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+ halfClosedRemote ctx strm
123+ !req <- 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+ setStreamState ctx strm $ Open (Body q mcl bodyLength)
132+ readQ <- newReadBody q
133+ bodySource <- mkSource readQ
134+ !req <- mkreq tbl (mcl, readSource bodySource)
135+ atomically $ writeTQueue inputQ $ Input strm req reqvt ii
136+ s@(Open Continued{}) -> do
137+ setContinued
138+ setStreamState ctx strm s
139+ HalfClosedRemote -> do
140+ resetContinued
141+ halfClosedRemote ctx strm
142+ s -> do -- Idle, Open Body, Closed
143+ resetContinued
144+ setStreamState ctx strm s
145+ return True
146+ where
147+ setContinued = writeIORef continued (Just streamId)
148+ resetContinued = writeIORef continued Nothing
149+ checkContinued = do
150+ mx <- readIORef continued
151+ case mx of
152+ Nothing -> return ()
153+ Just sid
154+ | sid == streamId && ftyp == FrameContinuation -> return ()
155+ | otherwise -> E.throwIO $ ConnectionError ProtocolError "continuation frame must follow"
156+ getStream = do
157+ mstrm0 <- search streamTable streamId
158+ case mstrm0 of
159+ js@(Just strm0) -> do
160+ when (ftyp == FrameHeaders) $ do
161+ st <- readStreamState strm0
162+ when (isHalfClosedRemote st) $ E.throwIO $ ConnectionError StreamClosed "header must not be sent to half or fully closed stream"
163+ -- Priority made an idele stream
164+ when (isIdle st) $ opened ctx strm0
165+ return js
166+ Nothing
167+ | isResponse streamId -> return Nothing
168+ | otherwise -> do
169+ csid <- readIORef clientStreamId
170+ if streamId <= csid then -- consider the stream closed
171+ if ftyp `elem` [FrameWindowUpdate, FrameRSTStream, FramePriority] then
172+ return Nothing -- will be ignored
173+ else
174+ E.throwIO $ ConnectionError ProtocolError "stream identifier must not decrease"
175+ else do -- consider the stream idle
176+ when (ftyp `notElem` [FrameHeaders,FramePriority]) $
177+ E.throwIO $ ConnectionError ProtocolError $ "this frame is not allowed in an idle stream: " `BS.append` C8.pack (show ftyp)
178+ when (ftyp == FrameHeaders) $ do
179+ writeIORef clientStreamId streamId
180+ cnt <- readIORef concurrency
181+ -- Checking the limitation of concurrency
182+ when (cnt >= maxConcurrency) $
183+ E.throwIO $ StreamError RefusedStream streamId
184+ ws <- initialWindowSize <$> readIORef http2settings
185+ newstrm <- newStream streamId (fromIntegral ws)
186+ when (ftyp == FrameHeaders) $ opened ctx newstrm
187+ insert streamTable streamId newstrm
188+ return $ Just newstrm
189+
190+ consume = void . recvN
191+
192+maxConcurrency :: Int
193+maxConcurrency = recommendedConcurrency
194+
195+initialFrame :: ByteString
196+initialFrame = settingsFrame id [(SettingsMaxConcurrentStreams,maxConcurrency)]
197+
198+----------------------------------------------------------------
199+
200+control :: FrameTypeId -> FrameHeader -> ByteString -> Context -> IO Bool
201+control FrameSettings header@FrameHeader{flags} bs Context{http2settings, controlQ, firstSettings, streamTable} = do
202+ SettingsFrame alist <- guardIt $ decodeSettingsFrame header bs
203+ case checkSettingsList alist of
204+ Just x -> E.throwIO x
205+ Nothing -> return ()
206+ -- HTTP/2 Setting from a browser
207+ unless (testAck flags) $ do
208+ oldws <- initialWindowSize <$> readIORef http2settings
209+ modifyIORef' http2settings $ \old -> updateSettings old alist
210+ newws <- initialWindowSize <$> readIORef http2settings
211+ let diff = newws - oldws
212+ when (diff /= 0) $ updateAllStreamWindow (+ diff) streamTable
213+ let !frame = settingsFrame setAck []
214+ sent <- readIORef firstSettings
215+ let !setframe
216+ | sent = CSettings frame alist
217+ | otherwise = CSettings0 initialFrame frame alist
218+ unless sent $ writeIORef firstSettings True
219+ enqueueControl controlQ setframe
220+ return True
221+
222+control FramePing FrameHeader{flags} bs Context{controlQ} =
223+ if testAck flags then
224+ return True -- just ignore
225+ else do
226+ let !frame = pingFrame bs
227+ enqueueControl controlQ $ CFrame frame
228+ return True
229+
230+control FrameGoAway _ _ Context{controlQ} = do
231+ enqueueControl controlQ CFinish
232+ return False
233+
234+control FrameWindowUpdate header bs Context{connectionWindow} = do
235+ WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
236+ !w <- atomically $ do
237+ w0 <- readTVar connectionWindow
238+ let !w1 = w0 + n
239+ writeTVar connectionWindow w1
240+ return w1
241+ when (isWindowOverflow w) $ E.throwIO $ ConnectionError FlowControlError "control window should be less than 2^31"
242+ return True
243+
244+control _ _ _ _ =
245+ -- must not reach here
246+ return False
247+
248+----------------------------------------------------------------
249+
250+{-# INLINE guardIt #-}
251+guardIt :: Either HTTP2Error a -> IO a
252+guardIt x = case x of
253+ Left err -> E.throwIO err
254+ Right frame -> return frame
255+
256+
257+{-# INLINE checkPriority #-}
258+checkPriority :: Priority -> StreamId -> IO ()
259+checkPriority p me
260+ | dep == me = E.throwIO $ StreamError ProtocolError me
261+ | otherwise = return ()
262+ where
263+ dep = streamDependency p
264+
265+stream :: FrameTypeId -> FrameHeader -> ByteString -> Context -> StreamState -> Stream -> IO StreamState
266+stream FrameHeaders header@FrameHeader{flags} bs ctx (Open JustOpened) Stream{streamNumber} = do
267+ HeadersFrame mp frag <- guardIt $ decodeHeadersFrame header bs
268+ pri <- case mp of
269+ Nothing -> return defaultPriority
270+ Just p -> do
271+ checkPriority p streamNumber
272+ return p
273+ let !endOfStream = testEndStream flags
274+ !endOfHeader = testEndHeader flags
275+ if endOfHeader then do
276+ tbl <- hpackDecodeHeader frag ctx -- fixme
277+ return $ if endOfStream then
278+ Open (NoBody tbl pri)
279+ else
280+ Open (HasBody tbl pri)
281+ else do
282+ let !siz = BS.length frag
283+ return $ Open $ Continued [frag] siz 1 endOfStream pri
284+
285+stream FrameHeaders header@FrameHeader{flags} bs _ (Open (Body q _ _)) _ = do
286+ -- trailer is not supported.
287+ -- let's read and ignore it.
288+ HeadersFrame _ _ <- guardIt $ decodeHeadersFrame header bs
289+ let !endOfStream = testEndStream flags
290+ if endOfStream then do
291+ atomically $ writeTQueue q ""
292+ return HalfClosedRemote
293+ else
294+ -- we don't support continuation here.
295+ E.throwIO $ ConnectionError ProtocolError "continuation in trailer is not supported"
296+
297+-- ignore data-frame except for flow-control when we're done locally
298+stream FrameData
299+ FrameHeader{flags,payloadLength}
300+ _bs
301+ Context{controlQ} s@(HalfClosedLocal _)
302+ Stream{streamNumber} = do
303+ let !endOfStream = testEndStream flags
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+ if endOfStream then do
310+ return HalfClosedRemote
311+ else
312+ return s
313+
314+stream FrameData
315+ header@FrameHeader{flags,payloadLength,streamId}
316+ bs
317+ Context{controlQ} s@(Open (Body q mcl bodyLength))
318+ Stream{streamNumber} = do
319+ DataFrame body <- guardIt $ decodeDataFrame header bs
320+ let !endOfStream = testEndStream flags
321+ len0 <- readIORef bodyLength
322+ let !len = len0 + payloadLength
323+ writeIORef bodyLength len
324+ when (payloadLength /= 0) $ do
325+ let !frame1 = windowUpdateFrame 0 payloadLength
326+ !frame2 = windowUpdateFrame streamNumber payloadLength
327+ !frame = frame1 `BS.append` frame2
328+ enqueueControl controlQ $ CFrame frame
329+ atomically $ writeTQueue q body
330+ if endOfStream then do
331+ case mcl of
332+ Nothing -> return ()
333+ Just cl -> when (cl /= len) $ E.throwIO $ StreamError ProtocolError streamId
334+ atomically $ writeTQueue q ""
335+ return HalfClosedRemote
336+ else
337+ return s
338+
339+stream FrameContinuation FrameHeader{flags} frag ctx (Open (Continued rfrags siz n endOfStream pri)) _ = do
340+ let !endOfHeader = testEndHeader flags
341+ !rfrags' = frag : rfrags
342+ !siz' = siz + BS.length frag
343+ !n' = n + 1
344+ when (siz' > 51200) $ -- fixme: hard coding: 50K
345+ E.throwIO $ ConnectionError EnhanceYourCalm "Header is too big"
346+ when (n' > 10) $ -- fixme: hard coding
347+ E.throwIO $ ConnectionError EnhanceYourCalm "Header is too fragmented"
348+ if endOfHeader then do
349+ let !hdrblk = BS.concat $ reverse rfrags'
350+ tbl <- hpackDecodeHeader hdrblk ctx
351+ return $ if endOfStream then
352+ Open (NoBody tbl pri)
353+ else
354+ Open (HasBody tbl pri)
355+ else
356+ return $ Open $ Continued rfrags' siz' n' endOfStream pri
357+
358+stream FrameWindowUpdate header@FrameHeader{streamId} bs _ s Stream{streamWindow} = do
359+ WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
360+ !w <- atomically $ do
361+ w0 <- readTVar streamWindow
362+ let !w1 = w0 + n
363+ writeTVar streamWindow w1
364+ return w1
365+ when (isWindowOverflow w) $
366+ E.throwIO $ StreamError FlowControlError streamId
367+ return s
368+
369+stream FrameRSTStream header bs ctx _ strm = do
370+ RSTStreamFrame e <- guardIt $ decoderstStreamFrame header bs
371+ let !cc = Reset e
372+ closed ctx strm cc
373+ return $ Closed cc -- will be written to streamState again
374+
375+stream FramePriority header bs Context{outputQ,priorityTreeSize} s Stream{streamNumber,streamPrecedence} = do
376+ PriorityFrame newpri <- guardIt $ decodePriorityFrame header bs
377+ checkPriority newpri streamNumber
378+ oldpre <- readIORef streamPrecedence
379+ let !newpre = toPrecedence newpri
380+ writeIORef streamPrecedence newpre
381+ if isIdle s then do
382+ n <- atomicModifyIORef' priorityTreeSize (\x -> (x+1,x+1))
383+ -- fixme hard coding
384+ when (n >= 20) $ E.throwIO $ ConnectionError EnhanceYourCalm "too many idle priority frames"
385+ prepare outputQ streamNumber newpri
386+ else do
387+ mx <- delete outputQ streamNumber oldpre
388+ case mx of
389+ Nothing -> return ()
390+ Just out -> enqueueOutput outputQ out
391+ return s
392+
393+-- this ordering is important
394+stream FrameContinuation _ _ _ _ _ = E.throwIO $ ConnectionError ProtocolError "continue frame cannot come here"
395+stream _ _ _ _ (Open Continued{}) _ = E.throwIO $ ConnectionError ProtocolError "an illegal frame follows header/continuation frames"
396+-- Ignore frames to streams we have just reset, per section 5.1.
397+stream _ _ _ _ st@(Closed (ResetByMe _)) _ = return st
398+stream FrameData FrameHeader{streamId} _ _ _ _ = E.throwIO $ StreamError StreamClosed streamId
399+stream _ FrameHeader{streamId} _ _ _ _ = E.throwIO $ StreamError ProtocolError streamId
400+
401+----------------------------------------------------------------
402+
403+{-# INLINE newReadBody #-}
404+newReadBody :: TQueue ByteString -> IO (IO ByteString)
405+newReadBody q = do
406+ ref <- newIORef False
407+ return $ readBody q ref
408+
409+{-# INLINE readBody #-}
410+readBody :: TQueue ByteString -> IORef Bool -> IO ByteString
411+readBody q ref = do
412+ eof <- readIORef ref
413+ if eof then
414+ return ""
415+ else do
416+ bs <- atomically $ readTQueue q
417+ when (bs == "") $ writeIORef ref True
418+ return bs
warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+13−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,34 @@ 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
2425import Network.Wai.Handler.Warp.Imports
25−import Network.Wai.Handler.Warp.Request (getFileInfoKey, pauseTimeoutKey, getClientCertificateKey)
26+import Network.Wai.Handler.Warp.Request (getFileInfoKey)
2627import qualified Network.Wai.Handler.Warp.Settings as S (Settings, settingsNoParsePath)
2728import Network.Wai.Handler.Warp.Types
2829
29−type ToReq = (TokenHeaderList,ValueTable) -> Maybe Int -> IO ByteString -> T.Handle -> Transport -> IO Request
30+type MkReq = (TokenHeaderList,ValueTable) -> (Maybe Int,IO ByteString) -> IO (Request)
3031
31−----------------------------------------------------------------
32−
33−toRequest :: InternalInfo -> S.Settings -> SockAddr -> ToReq
34−toRequest ii settings addr ht bodylen body th transport = do
32+mkRequest :: InternalInfo -> S.Settings -> SockAddr -> MkReq
33+mkRequest ii1 settings addr (reqths,reqvt) (bodylen,body) = do
3534 ref <- newIORef Nothing
36− toRequest' ii settings addr ref ht bodylen body th transport
35+ mkRequest' ii1 settings addr ref (reqths,reqvt) (bodylen,body)
3736
38−toRequest' :: InternalInfo -> S.Settings -> SockAddr
37+mkRequest' :: InternalInfo -> S.Settings -> SockAddr
3938 -> IORef (Maybe HTTP2Data)
40− -> ToReq
41−toRequest' ii settings addr ref (reqths,reqvt) bodylen body th transport = return req
39+ -> MkReq
40+mkRequest' ii settings addr ref (reqths,reqvt) (bodylen,body) = return req
4241 where
4342 !req = Request {
4443 requestMethod = colonMethod
45− , httpVersion = H.http20
44+ , httpVersion = http2ver
4645 , rawPathInfo = rawPath
4746 , pathInfo = H.decodePathSegments path
4847 , rawQueryString = query
4948 , queryString = H.parseQuery query
5049 , requestHeaders = headers
51− , isSecure = isTransportSecure transport
50+ , isSecure = True
5251 , remoteHost = addr
5352 , requestBody = body
5453 , vault = vaultValue
@@ -77,13 +76,10 @@ toRequest' ii settings addr ref (reqths,reqvt) bodylen body th transport = retur
7776 (unparsedPath,query) = C8.break (=='?') $ fromJust (mPath <|> mAuth)
7877 !path = H.extractPath unparsedPath
7978 !rawPath = if S.settingsNoParsePath settings then unparsedPath else path
80− -- fixme: pauseTimeout. th is not available here.
8179 !vaultValue = Vault.insert getFileInfoKey (getFileInfo ii)
8280 $ Vault.insert getHTTP2DataKey (readIORef ref)
8381 $ Vault.insert setHTTP2DataKey (writeIORef ref)
8482 $ Vault.insert modifyHTTP2DataKey (modifyIORef' ref)
85− $ Vault.insert pauseTimeoutKey (T.pause th)
86− $ Vault.insert getClientCertificateKey (tlsClientCertificate transport)
8783 Vault.empty
8884
8985getHTTP2DataKey :: 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 System.TimeManager 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+ halfClosedLocal 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 <- readStreamState strm
178+ if isHalfClosedLocal 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+ halfClosedLocal 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+305−12
@@ -1,19 +1,32 @@
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
@@ -24,6 +37,290 @@ isHTTP2 tls = useHTTP2
2437
2538----------------------------------------------------------------
2639
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+ | HalfClosedRemote
170+ | HalfClosedLocal !ClosedCode
171+ | Closed !ClosedCode
172+ | Reserved
173+
174+isIdle :: StreamState -> Bool
175+isIdle Idle = True
176+isIdle _ = False
177+
178+isOpen :: StreamState -> Bool
179+isOpen Open{} = True
180+isOpen _ = False
181+
182+isHalfClosedRemote :: StreamState -> Bool
183+isHalfClosedRemote HalfClosedRemote = True
184+isHalfClosedRemote (Closed _) = True
185+isHalfClosedRemote _ = False
186+
187+isHalfClosedLocal :: StreamState -> Bool
188+isHalfClosedLocal (HalfClosedLocal _) = True
189+isHalfClosedLocal (Closed _) = True
190+isHalfClosedLocal _ = False
191+
192+isClosed :: StreamState -> Bool
193+isClosed Closed{} = True
194+isClosed _ = False
195+
196+instance Show StreamState where
197+ show Idle = "Idle"
198+ show Open{} = "Open"
199+ show HalfClosedRemote = "HalfClosedRemote"
200+ show (HalfClosedLocal e) = "HalfClosedLocal: " ++ show e
201+ show (Closed e) = "Closed: " ++ show e
202+ show Reserved = "Reserved"
203+
204+----------------------------------------------------------------
205+
206+data Stream = Stream {
207+ streamNumber :: !StreamId
208+ , streamState :: !(IORef StreamState)
209+ , streamWindow :: !(TVar WindowSize)
210+ , streamPrecedence :: !(IORef Precedence)
211+ }
212+
213+instance Show Stream where
214+ show s = show (streamNumber s)
215+
216+newStream :: StreamId -> WindowSize -> IO Stream
217+newStream sid win = Stream sid <$> newIORef Idle
218+ <*> newTVarIO win
219+ <*> newIORef defaultPrecedence
220+
221+newPushStream :: Context -> WindowSize -> Precedence -> IO Stream
222+newPushStream Context{serverStreamId} win pre = do
223+ sid <- atomicModifyIORef' serverStreamId inc2
224+ Stream sid <$> newIORef Reserved
225+ <*> newTVarIO win
226+ <*> newIORef pre
227+ where
228+ inc2 x = let !x' = x + 2 in (x', x')
229+
230+----------------------------------------------------------------
231+
232+{-# INLINE readStreamState #-}
233+readStreamState :: Stream -> IO StreamState
234+readStreamState Stream{streamState} =
235+ readIORef streamState
236+
237+{-# INLINE setStreamState #-}
238+setStreamState :: Context -> Stream -> StreamState -> IO ()
239+setStreamState _ Stream{streamState} val = writeIORef streamState val
240+
241+opened :: Context -> Stream -> IO ()
242+opened ctx@Context{concurrency} strm = do
243+ atomicModifyIORef' concurrency (\x -> (x+1,()))
244+ setStreamState ctx strm (Open JustOpened)
245+
246+halfClosedRemote :: Context -> Stream -> IO ()
247+halfClosedRemote ctx stream@Stream{streamState} = do
248+ !closingCode <- atomicModifyIORef streamState closeHalf
249+ case closingCode of
250+ Nothing -> return ()
251+ Just cc -> closed ctx stream cc
252+ where
253+ closeHalf :: StreamState -> (StreamState, Maybe ClosedCode)
254+ closeHalf x@(Closed _) = (x, Nothing)
255+ closeHalf (HalfClosedLocal cc) = (Closed cc, Just cc)
256+ closeHalf _ = (HalfClosedRemote, Nothing)
257+
258+halfClosedLocal :: Context -> Stream -> ClosedCode -> IO ()
259+halfClosedLocal ctx stream@Stream{streamState} cc = do
260+ shouldFinalize <- atomicModifyIORef streamState closeHalf
261+ when shouldFinalize $
262+ closed ctx stream cc
263+ where
264+ closeHalf :: StreamState -> (StreamState, Bool)
265+ closeHalf x@(Closed _) = (x, False)
266+ closeHalf HalfClosedRemote = (Closed cc, True)
267+ closeHalf _ = (HalfClosedLocal cc, False)
268+
269+closed :: Context -> Stream -> ClosedCode -> IO ()
270+closed ctx@Context{concurrency,streamTable} strm@Stream{streamNumber} cc = do
271+ remove streamTable streamNumber
272+ -- TODO: prevent double-counting
273+ atomicModifyIORef' concurrency (\x -> (x-1,()))
274+ setStreamState ctx strm (Closed cc) -- anyway
275+
276+----------------------------------------------------------------
277+
278+newtype StreamTable = StreamTable (IORef (IntMap Stream))
279+
280+newStreamTable :: IO StreamTable
281+newStreamTable = StreamTable <$> newIORef M.empty
282+
283+insert :: StreamTable -> M.Key -> Stream -> IO ()
284+insert (StreamTable ref) k v = atomicModifyIORef' ref $ \m ->
285+ let !m' = M.insert k v m
286+ in (m', ())
287+
288+remove :: StreamTable -> M.Key -> IO ()
289+remove (StreamTable ref) k = atomicModifyIORef' ref $ \m ->
290+ let !m' = M.delete k m
291+ in (m', ())
292+
293+search :: StreamTable -> M.Key -> IO (Maybe Stream)
294+search (StreamTable ref) k = M.lookup k <$> readIORef ref
295+
296+updateAllStreamWindow :: (WindowSize -> WindowSize) -> StreamTable -> IO ()
297+updateAllStreamWindow adst (StreamTable ref) = do
298+ strms <- M.elems <$> readIORef ref
299+ forM_ strms $ \strm -> atomically $ modifyTVar (streamWindow strm) adst
300+
301+{-# INLINE forkAndEnqueueWhenReady #-}
302+forkAndEnqueueWhenReady :: IO () -> PriorityTree Output -> Output -> Manager -> IO ()
303+forkAndEnqueueWhenReady wait outQ out mgr = bracket setup teardown $ \_ ->
304+ void . forkIO $ do
305+ wait
306+ enqueueOutput outQ out
307+ where
308+ setup = addMyId mgr
309+ teardown _ = deleteMyId mgr
310+
311+{-# INLINE enqueueOutput #-}
312+enqueueOutput :: PriorityTree Output -> Output -> IO ()
313+enqueueOutput outQ out = do
314+ let Stream{..} = outputStream out
315+ pre <- readIORef streamPrecedence
316+ enqueue outQ streamNumber pre out
317+
318+{-# INLINE enqueueControl #-}
319+enqueueControl :: TQueue Control -> Control -> IO ()
320+enqueueControl ctlQ ctl = atomically $ writeTQueue ctlQ ctl
321+
322+----------------------------------------------------------------
323+
27324-- | HTTP/2 specific data.
28325--
29326-- Since: 3.2.7
@@ -32,21 +329,17 @@ data HTTP2Data = HTTP2Data {
32329 --
33330 -- Since: 3.2.7
34331 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− }
332+ -- Since: 3.2.8
333+ , http2dataTrailers :: H.ResponseHeaders
334+ } deriving (Eq,Show)
40335
41336-- | Default HTTP/2 specific data.
42337--
43338-- Since: 3.2.7
44339defaultHTTP2Data :: HTTP2Data
45−defaultHTTP2Data = HTTP2Data [] H2.defaultTrailersMaker
340+defaultHTTP2Data = HTTP2Data [] []
46341
47342-- | HTTP/2 push promise or sever push.
48−-- This allows files only for backward-compatibility
49−-- while the HTTP/2 library supports other types.
50343--
51344-- Since: 3.2.7
52345data PushPromise = PushPromise {
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hsnew file+331
@@ -0,0 +1,331 @@
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 Network.Wai.Internal (Response(..), ResponseReceived(..), ResponseReceived(..), getRequestBodyChunk)
27+import qualified System.TimeManager as T
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 Network.Wai.Handler.Warp.Types
40+
41+----------------------------------------------------------------
42+
43+-- | The wai definition is 'type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived'.
44+-- This type implements the second argument (Response -> IO ResponseReceived)
45+-- with extra arguments.
46+type Responder = InternalInfo
47+ -> T.Handle
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 th reqvt tconf strm req rsp = E.handle (E.throwIO . ExceptionInsideResponseBody) $ 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+ sid = streamNumber strm
146+ !h2data = getHTTP2Data req
147+
148+ -- Ideally, log messages should be written when responses are
149+ -- actually sent. But there is no way to keep good memory usage
150+ -- (resist to Request leak) and throughput. By compromise,
151+ -- log message are written here even the window size of streams
152+ -- is 0.
153+
154+ responseNoBody s hs0 = toHeaderTable hs0 >>= responseNoBody' s
155+
156+ responseNoBody' s tbl = do
157+ logger req s Nothing
158+ setThreadContinue tconf True
159+ let !rspn = RspnNobody s tbl
160+ !out = Output strm rspn ii (return ()) h2data ORspn
161+ enqueueOutput outputQ out
162+ return ResponseReceived
163+
164+ responseBuilderBody s hs0 bdy (rspnOrWait,tell) = do
165+ logger req s Nothing
166+ setThreadContinue tconf True
167+ tbl <- toHeaderTable hs0
168+ let !rspn = RspnBuilder s tbl bdy
169+ !out = Output strm rspn ii tell h2data rspnOrWait
170+ enqueueOutput outputQ out
171+ return ResponseReceived
172+
173+ responseFileXXX _ hs0 path Nothing aux = do
174+ efinfo <- E.try $ getFileInfo ii path
175+ case efinfo of
176+ Left (_ex :: E.IOException) -> response404 hs0
177+ Right finfo -> do
178+ (rspths0,vt) <- toHeaderTable hs0
179+ case conditionalRequest finfo rspths0 reqvt of
180+ WithoutBody s -> responseNoBody s hs0
181+ WithBody s rspths beg len -> responseFile2XX s (rspths,vt) path (Just (FilePart beg len (fileInfoSize finfo))) aux
182+
183+ responseFileXXX s0 hs0 path mpart aux = do
184+ tbl <- toHeaderTable hs0
185+ responseFile2XX s0 tbl path mpart aux
186+
187+ responseFile2XX s tbl path mpart (rspnOrWait,tell)
188+ | isHead = do
189+ logger req s Nothing
190+ responseNoBody' s tbl
191+ | otherwise = do
192+ logger req s (filePartByteCount <$> mpart)
193+ setThreadContinue tconf True
194+ let !rspn = RspnFile s tbl path mpart
195+ !out = Output strm rspn ii tell h2data rspnOrWait
196+ enqueueOutput outputQ out
197+ return ResponseReceived
198+
199+ response404 hs0 = responseBuilderBody s hs body (ORspn, return ())
200+ where
201+ s = H.notFound404
202+ hs = R.replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
203+ body = byteString "File not found"
204+
205+ responseStreaming s0 hs0 strmbdy (rspnOrWait,tell) = do
206+ logger req s0 Nothing
207+ -- We must not exit this WAI application.
208+ -- If the application exits, streaming would be also closed.
209+ -- So, this work occupies this thread.
210+ --
211+ -- We need to increase the number of workers.
212+ spawnAction mgr
213+ -- After this work, this thread stops to decease
214+ -- the number of workers.
215+ setThreadContinue tconf False
216+ -- Since 'StreamingBody' is loop, we cannot control it.
217+ -- So, let's serialize 'Builder' with a designated queue.
218+ tbq <- newTBQueueIO 10 -- fixme: hard coding: 10
219+ tbl <- toHeaderTable hs0
220+ let !rspn = RspnStreaming s0 tbl tbq
221+ !out = Output strm rspn ii tell h2data rspnOrWait
222+ enqueueOutput outputQ out
223+ let push b = do
224+ T.pause th
225+ atomically $ writeTBQueue tbq (SBuilder b)
226+ T.resume th
227+ flush = atomically $ writeTBQueue tbq SFlush
228+ _ <- strmbdy push flush
229+ atomically $ writeTBQueue tbq SFinish
230+ deleteMyId mgr
231+ return ResponseReceived
232+
233+worker :: Context -> S.Settings -> Application -> Responder -> T.Manager -> IO ()
234+worker ctx@Context{inputQ,controlQ} set app responder tm = do
235+ sinfo <- newStreamInfo
236+ tcont <- newThreadContinue
237+ let timeoutAction = return () -- cannot close the shared connection
238+ E.bracket (T.registerKillThread tm timeoutAction) T.cancel $ go sinfo tcont
239+ where
240+ go sinfo tcont th = do
241+ setThreadContinue tcont True
242+ ex <- E.try $ do
243+ T.pause th
244+ inp@(Input strm req reqvt ii) <- atomically $ readTQueue inputQ
245+ setStreamInfo sinfo inp
246+ T.resume th
247+ T.tickle th
248+ let !body = getRequestBodyChunk req
249+ !body' = do
250+ T.pause th
251+ bs <- body
252+ T.resume th
253+ return bs
254+ !vaultValue = Vault.insert pauseTimeoutKey (T.pause th) $ vault req
255+ !req' = req { vault = vaultValue, requestBody = body' }
256+ mr <- E.try $ app req' $ responder ii th reqvt tcont strm req'
257+ case mr of
258+ Right ok -> return ok
259+ Left e@(SomeException _)
260+ | Just (ExceptionInsideResponseBody e') <- E.fromException e -> E.throwIO e'
261+ | otherwise -> responder ii th reqvt tcont strm req' (S.settingsOnExceptionResponse set e)
262+ cont1 <- case ex of
263+ Right ResponseReceived -> return True
264+ Left e@(SomeException _)
265+ -- killed by the local worker manager
266+ | Just ThreadKilled <- E.fromException e -> return False
267+ -- killed by the local timeout manager
268+ | Just T.TimeoutThread <- E.fromException e -> do
269+ cleanup sinfo Nothing
270+ return True
271+ | otherwise -> do
272+ cleanup sinfo $ Just e
273+ return True
274+ cont2 <- getThreadContinue tcont
275+ clearStreamInfo sinfo
276+ when (cont1 && cont2) $ go sinfo tcont th
277+ cleanup sinfo me = do
278+ minp <- getStreamInfo sinfo
279+ case minp of
280+ Nothing -> return ()
281+ Just (Input strm req _reqvt _ii) -> do
282+ closed ctx strm Killed
283+ let !frame = resetFrame InternalError (streamNumber strm)
284+ enqueueControl controlQ $ CFrame frame
285+ case me of
286+ Nothing -> return ()
287+ Just e -> S.settingsOnException set (Just req) e
288+
289+----------------------------------------------------------------
290+
291+-- | It would nice if responders could return values to workers.
292+-- Unfortunately, 'ResponseReceived' is already defined in WAI 2.0.
293+-- It is not wise to change this type.
294+-- So, a reference is shared by a responder and its worker.
295+-- The reference refers a value of this type as a return value.
296+-- If 'True', the worker continue to serve requests.
297+-- Otherwise, the worker get finished.
298+newtype ThreadContinue = ThreadContinue (IORef Bool)
299+
300+{-# INLINE newThreadContinue #-}
301+newThreadContinue :: IO ThreadContinue
302+newThreadContinue = ThreadContinue <$> newIORef True
303+
304+{-# INLINE setThreadContinue #-}
305+setThreadContinue :: ThreadContinue -> Bool -> IO ()
306+setThreadContinue (ThreadContinue ref) x = writeIORef ref x
307+
308+{-# INLINE getThreadContinue #-}
309+getThreadContinue :: ThreadContinue -> IO Bool
310+getThreadContinue (ThreadContinue ref) = readIORef ref
311+
312+----------------------------------------------------------------
313+
314+-- | The type to store enough information for 'settingsOnException'.
315+newtype StreamInfo = StreamInfo (IORef (Maybe Input))
316+
317+{-# INLINE newStreamInfo #-}
318+newStreamInfo :: IO StreamInfo
319+newStreamInfo = StreamInfo <$> newIORef Nothing
320+
321+{-# INLINE clearStreamInfo #-}
322+clearStreamInfo :: StreamInfo -> IO ()
323+clearStreamInfo (StreamInfo ref) = writeIORef ref Nothing
324+
325+{-# INLINE setStreamInfo #-}
326+setStreamInfo :: StreamInfo -> Input -> IO ()
327+setStreamInfo (StreamInfo ref) inp = writeIORef ref $ Just inp
328+
329+{-# INLINE getStreamInfo #-}
330+getStreamInfo :: StreamInfo -> IO (Maybe Input)
331+getStreamInfo (StreamInfo ref) = readIORef ref
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/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+19−21
@@ -9,7 +9,6 @@ module Network.Wai.Handler.Warp.Request (
99 , headerLines
1010 , pauseTimeoutKey
1111 , getFileInfoKey
12− , getClientCertificateKey
1312 , NoKeepAliveRequest (..)
1413 ) where
1514
@@ -22,7 +21,6 @@ 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
@@ -38,7 +36,13 @@ import Network.Wai.Handler.Warp.Header
3836import Network.Wai.Handler.Warp.Imports hiding (readInt, lines)
3937import Network.Wai.Handler.Warp.ReadInt
4038import Network.Wai.Handler.Warp.RequestHeader
41−import Network.Wai.Handler.Warp.Settings (Settings, settingsNoParsePath, settingsMaxTotalHeaderLength)
39+import Network.Wai.Handler.Warp.Settings (Settings, settingsNoParsePath)
40+
41+----------------------------------------------------------------
42+
43+-- FIXME come up with good values here
44+maxTotalHeaderLength :: Int
45+maxTotalHeaderLength = 50 * 1024
4246
4347----------------------------------------------------------------
4448
@@ -51,7 +55,6 @@ recvRequest :: Bool -- ^ first request on this connection?
5155 -> Timeout.Handle
5256 -> SockAddr -- ^ Peer's address.
5357 -> Source -- ^ Where HTTP request comes from.
54− -> Transport
5558 -> IO (Request
5659 ,Maybe (I.IORef Int)
5760 ,IndexedHeader
@@ -61,8 +64,8 @@ recvRequest :: Bool -- ^ first request on this connection?
6164 -- 'IndexedHeader' of HTTP request for internal use,
6265 -- Body producing action used for flushing the request body
6366
64−recvRequest firstRequest settings conn ii th addr src transport = do
65− hdrlines <- headerLines (settingsMaxTotalHeaderLength settings) firstRequest src
67+recvRequest firstRequest settings conn ii th addr src = do
68+ hdrlines <- headerLines firstRequest src
6669 (method, unparsedPath, path, query, httpversion, hdr) <- parseHeaderLines hdrlines
6770 let idxhdr = indexRequestHeader hdr
6871 expect = idxhdr ! fromEnum ReqExpect
@@ -72,7 +75,6 @@ recvRequest firstRequest settings conn ii th addr src transport = do
7275 rawPath = if settingsNoParsePath settings then unparsedPath else path
7376 vaultValue = Vault.insert pauseTimeoutKey (Timeout.pause th)
7477 $ Vault.insert getFileInfoKey (getFileInfo ii)
75− $ Vault.insert getClientCertificateKey (tlsClientCertificate transport)
7678 Vault.empty
7779 (rbody, remainingRef, bodyLength) <- bodyAndSource src cl te
7880 -- body producing function which will produce '100-continue', if needed
@@ -87,7 +89,7 @@ recvRequest firstRequest settings conn ii th addr src transport = do
8789 , rawQueryString = query
8890 , queryString = H.parseQuery query
8991 , requestHeaders = hdr
90− , isSecure = isTransportSecure transport
92+ , isSecure = False
9193 , remoteHost = addr
9294 , requestBody = rbody'
9395 , vault = vaultValue
@@ -101,8 +103,8 @@ recvRequest firstRequest settings conn ii th addr src transport = do
101103
102104----------------------------------------------------------------
103105
104−headerLines :: Int -> Bool -> Source -> IO [ByteString]
105−headerLines maxTotalHeaderLength firstRequest src = do
106+headerLines :: Bool -> Source -> IO [ByteString]
107+headerLines firstRequest src = do
106108 bs <- readSource src
107109 if S.null bs
108110 -- When we're working on a keep-alive connection and trying to
@@ -110,7 +112,7 @@ headerLines maxTotalHeaderLength firstRequest src = do
110112 -- lack of data as a real exception. See the http1 function in
111113 -- the Run module for more details.
112114 then if firstRequest then throwIO ConnectionClosedByPeer else throwIO NoKeepAliveRequest
113− else push maxTotalHeaderLength src (THStatus 0 id id) bs
115+ else push src (THStatus 0 id id) bs
114116
115117data NoKeepAliveRequest = NoKeepAliveRequest
116118 deriving (Show, Typeable)
@@ -220,8 +222,8 @@ close :: Sink ByteString IO a
220222close = throwIO IncompleteHeaders
221223-}
222224
223−push :: Int -> Source -> THStatus -> ByteString -> IO [ByteString]
224−push maxTotalHeaderLength src (THStatus len lines prepend) bs'
225+push :: Source -> THStatus -> ByteString -> IO [ByteString]
226+push src (THStatus len lines prepend) bs'
225227 -- Too many bytes
226228 | len > maxTotalHeaderLength = throwIO OverLargeHeader
227229 | otherwise = push' mnl
@@ -249,13 +251,13 @@ push maxTotalHeaderLength src (THStatus len lines prepend) bs'
249251 push' Nothing = do
250252 bst <- readSource' src
251253 when (S.null bst) $ throwIO IncompleteHeaders
252− push maxTotalHeaderLength src status bst
254+ push src status bst
253255 where
254256 len' = len + bsLen
255257 prepend' = S.append bs
256258 status = THStatus len' lines prepend'
257259 -- Found a newline, but next line continues as a multiline header
258− push' (Just (end, True)) = push maxTotalHeaderLength src status rest
260+ push' (Just (end, True)) = push src status rest
259261 where
260262 rest = S.drop (end + 1) bs
261263 prepend' = S.append (SU.unsafeTake (checkCR bs end) bs)
@@ -274,12 +276,12 @@ push maxTotalHeaderLength src (THStatus len lines prepend) bs'
274276 in if start < bsLen then
275277 -- more bytes in this chunk, push again
276278 let bs'' = SU.unsafeDrop start bs
277− in push maxTotalHeaderLength src status bs''
279+ in push src status bs''
278280 else do
279281 -- no more bytes in this chunk, ask for more
280282 bst <- readSource' src
281283 when (S.null bs) $ throwIO IncompleteHeaders
282− push maxTotalHeaderLength src status bst
284+ push src status bst
283285 where
284286 start = end + 1 -- start of next chunk
285287 line = SU.unsafeTake (checkCR bs end) bs
@@ -297,7 +299,3 @@ pauseTimeoutKey = unsafePerformIO Vault.newKey
297299getFileInfoKey :: Vault.Key (FilePath -> IO FileInfo)
298300getFileInfoKey = unsafePerformIO Vault.newKey
299301{-# NOINLINE getFileInfoKey #-}
300−
301−getClientCertificateKey :: Vault.Key (Maybe CertificateChain)
302−getClientCertificateKey = unsafePerformIO Vault.newKey
303−{-# NOINLINE getClientCertificateKey #-}
warp/Network/Wai/Handler/Warp/Response.hs+16−19
@@ -116,14 +116,14 @@ sendResponse settings conn ii th req reqidxhdr src response = do
116116 -- and status, the response to HEAD is processed here.
117117 --
118118 -- See definition of rsp below for proper body stripping.
119− (ms, mlen) <- sendRsp conn ii ver s hs rspidxhdr rsp
119+ (ms, mlen) <- sendRsp conn ii ver s hs rsp
120120 case ms of
121121 Nothing -> return ()
122122 Just realStatus -> logger req realStatus mlen
123123 T.tickle th
124124 return ret
125125 else do
126− _ <- sendRsp conn ii ver s hs rspidxhdr RspNoBody
126+ _ <- sendRsp conn ii ver s hs RspNoBody
127127 logger req s Nothing
128128 T.tickle th
129129 return isPersist
@@ -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+34−71
@@ -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,29 +15,24 @@ 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)
28+import qualified System.TimeManager as T
3529
3630import Network.Wai.Handler.Warp.Buffer
3731import Network.Wai.Handler.Warp.Counter
3832import qualified Network.Wai.Handler.Warp.Date as D
3933import qualified Network.Wai.Handler.Warp.FdCache as F
4034import 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)
35+import Network.Wai.Handler.Warp.HTTP2 (http2, isHTTP2)
4336import Network.Wai.Handler.Warp.Header
4437import Network.Wai.Handler.Warp.Imports hiding (readInt)
4538import Network.Wai.Handler.Warp.ReadInt
@@ -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
@@ -286,7 +262,10 @@ fork :: Settings
286262fork set mkConn addr app counter ii = 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,13 +276,21 @@ 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
307294 -- We now have fully registered a connection close handler in
308295 -- the case of all exceptions, so it is safe to once again
309296 -- allow async exceptions.
@@ -315,7 +302,8 @@ fork set mkConn addr app counter ii = settingsFork set $ \unmask ->
315302 -- above ensures the connection is closed.
316303 when goingon $ serveConnection conn ii th addr transport set app
317304 where
318− register = T.registerKillThread (timeoutManager ii) (connClose conn)
305+ register = T.registerKillThread (timeoutManager ii)
306+ (closeConn ref conn)
319307 cancel = T.cancel
320308
321309 onOpen adr = increase counter >> settingsOnOpen set adr
@@ -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 ii 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 =
@@ -429,7 +404,8 @@ serveConnection conn ii th origAddr transport settings app = do
429404 errorResponse e = settingsOnExceptionResponse settings e
430405
431406 http1 firstRequest addr istatus src = do
432− (req, mremainingRef, idxhdr, nextBodyFlush) <- recvRequest firstRequest settings conn ii th addr src transport
407+ (req', mremainingRef, idxhdr, nextBodyFlush) <- recvRequest firstRequest settings conn ii th addr src
408+ let req = req' { isSecure = isTransportSecure transport }
433409 keepAlive <- processRequest istatus src req mremainingRef idxhdr nextBodyFlush
434410 `E.catch` \e -> do
435411 settingsOnException settings (Just req) e
@@ -514,19 +490,6 @@ serveConnection conn ii th origAddr transport settings app = do
514490 else
515491 return False
516492
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
529−
530493flushEntireBody :: IO ByteString -> IO ()
531494flushEntireBody src =
532495 loop
warp/Network/Wai/Handler/Warp/Settings.hs+5−31
@@ -76,13 +76,6 @@ data Settings = Settings
7676 --
7777 -- Since 2.0.3
7878 , 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
8679 , settingsServerName :: ByteString
8780 -- ^ Default server name if application does not set one.
8881 --
@@ -94,44 +87,28 @@ data Settings = Settings
9487 , settingsProxyProtocol :: ProxyProtocol
9588 -- ^ Specify usage of the PROXY protocol.
9689 --
97− -- Since 3.0.5
90+ -- Since 3.0.5.
9891 , settingsSlowlorisSize :: Int
9992 -- ^ Size of bytes read to prevent Slowloris protection. Default value: 2048
10093 --
101− -- Since 3.1.2
94+ -- Since 3.1.2.
10295 , settingsHTTP2Enabled :: Bool
10396 -- ^ Whether to enable HTTP2 ALPN/upgrades. Default: True
10497 --
105− -- Since 3.1.7
98+ -- Since 3.1.7.
10699 , settingsLogger :: Request -> H.Status -> Maybe Integer -> IO ()
107100 -- ^ A log function. Default: no action.
108101 --
109− -- Since 3.1.10
102+ -- Since 3.X.X.
110103 , settingsServerPushLogger :: Request -> ByteString -> Integer -> IO ()
111104 -- ^ A HTTP/2 server push log function. Default: no action.
112105 --
113− -- Since 3.2.7
106+ -- Since 3.X.X.
114107 , settingsGracefulShutdownTimeout :: Maybe Int
115108 -- ^ An optional timeout to limit the time (in seconds) waiting for
116109 -- a graceful shutdown of the web server.
117110 --
118111 -- 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
135112 }
136113
137114-- | Specify usage of the PROXY protocol.
@@ -168,9 +145,6 @@ defaultSettings = Settings
168145 , settingsLogger = \_ _ _ -> return ()
169146 , settingsServerPushLogger = \_ _ _ -> return ()
170147 , settingsGracefulShutdownTimeout = Nothing
171− , settingsGracefulCloseTimeout1 = 0
172− , settingsGracefulCloseTimeout2 = 2000
173− , settingsMaxTotalHeaderLength = 50 * 1024
174148 }
175149
176150-- | Apply the logic provided by 'defaultOnException' to determine if an
warp/Network/Wai/Handler/Warp/Types.hs−10
@@ -8,7 +8,6 @@ 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)
1413import qualified System.TimeManager as T
@@ -123,16 +122,8 @@ data Connection = Connection {
123122 , connWriteBuffer :: Buffer
124123 -- | The size of the write buffer.
125124 , connBufferSize :: BufSize
126− -- | Is this connection HTTP/2?
127− , connHTTP2 :: IORef Bool
128125 }
129126
130−getConnHTTP2 :: Connection -> IO Bool
131−getConnHTTP2 conn = readIORef (connHTTP2 conn)
132−
133−setConnHTTP2 :: Connection -> Bool -> IO ()
134−setConnHTTP2 conn b = writeIORef (connHTTP2 conn) b
135−
136127----------------------------------------------------------------
137128
138129data InternalInfo = InternalInfo {
@@ -180,7 +171,6 @@ data Transport = TCP -- ^ Plain channel: TCP
180171 , tlsMinorVersion :: Int
181172 , tlsNegotiatedProtocol :: Maybe ByteString -- ^ The result of Application Layer Protocol Negociation in RFC 7301
182173 , tlsChiperID :: Word16
183− , tlsClientCertificate :: Maybe CertificateChain
184174 } -- ^ Encrypted channel: TLS or SSL
185175
186176isTransportSecure :: Transport -> Bool
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+33
@@ -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,6 +37,36 @@ 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 = [
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+47−38
@@ -1,7 +1,7 @@
11{-# LANGUAGE OverloadedStrings #-}
22{-# LANGUAGE ScopedTypeVariables #-}
33
4−module RunSpec (main, spec, withApp, MySocket, msWrite, msRead, withMySocket) where
4+module RunSpec (main, spec, withApp, connectTo, MySocket, msWrite, msRead, msClose) where
55
66import Control.Concurrent (forkIO, killThread, threadDelay)
77import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
@@ -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 ->
@@ -157,7 +150,8 @@ runTest :: Int -- ^ expected number of requests
157150 -> IO ()
158151runTest expected app chunks = do
159152 ref <- I.newIORef (Right 0)
160− withApp defaultSettings (app ref) $ withMySocket $ \ms -> do
153+ withApp defaultSettings (app ref) $ \port -> do
154+ ms <- connectTo port
161155 forM_ chunks $ \chunk -> msWrite ms chunk
162156 _ <- timeout 100000 $ replicateM_ expected $ msRead ms 4096
163157 res <- I.readIORef ref
@@ -174,9 +168,10 @@ runTerminateTest :: InvalidRequest
174168runTerminateTest expected input = do
175169 ref <- I.newIORef Nothing
176170 let onExc _ = I.writeIORef ref . Just
177− withApp (setOnException onExc defaultSettings) dummyApp $ withMySocket $ \ms -> do
178− msWrite ms input
179− msClose ms -- explicitly
171+ withApp (setOnException onExc defaultSettings) dummyApp $ \port -> do
172+ handle <- connectTo port
173+ msWrite handle input
174+ msClose handle
180175 threadDelay 1000
181176 res <- I.readIORef ref
182177 show res `shouldBe` show (Just expected)
@@ -247,11 +242,13 @@ spec = do
247242 let app req f = do
248243 liftIO $ I.writeIORef iheaders $ requestHeaders req
249244 f $ responseLBS status200 [] ""
250− withApp defaultSettings app $ withMySocket $ \ms -> do
245+ withApp defaultSettings app $ \port -> do
246+ handle <- connectTo port
251247 let input = S.concat
252248 [ "GET / HTTP/1.1\r\nfoo: bar\r\n baz\r\n\tbin\r\n\r\n"
253249 ]
254− msWrite ms input
250+ msWrite handle input
251+ msClose handle
255252 threadDelay 1000
256253 headers <- I.readIORef iheaders
257254 headers `shouldBe`
@@ -262,11 +259,13 @@ spec = do
262259 let app req f = do
263260 liftIO $ I.writeIORef iheaders $ requestHeaders req
264261 f $ responseLBS status200 [] ""
265− withApp defaultSettings app $ withMySocket $ \ms -> do
262+ withApp defaultSettings app $ \port -> do
263+ handle <- connectTo port
266264 let input = S.concat
267265 [ "GET / HTTP/1.1\r\nfoo:bar\r\n\r\n"
268266 ]
269− msWrite ms input
267+ msWrite handle input
268+ msClose handle
270269 threadDelay 1000
271270 headers <- I.readIORef iheaders
272271 headers `shouldBe`
@@ -282,14 +281,16 @@ spec = do
282281 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
283282 atomically $ modifyTVar countVar (+ 1)
284283 f $ responseLBS status200 [] ""
285− withApp defaultSettings app $ withMySocket $ \ms -> do
284+ withApp defaultSettings app $ \port -> do
285+ handle <- connectTo port
286286 let input = S.concat
287287 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
288288 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n\r\n"
289289 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
290290 , "b\r\nHello World\r\n0\r\n\r\n"
291291 ]
292− msWrite ms input
292+ msWrite handle input
293+ msClose handle
293294 atomically $ do
294295 count <- readTVar countVar
295296 check $ count == 2
@@ -306,12 +307,14 @@ spec = do
306307 I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
307308 atomically $ modifyTVar countVar (+ 1)
308309 f $ responseLBS status200 [] ""
309− withApp defaultSettings app $ withMySocket $ \ms -> do
310+ withApp defaultSettings app $ \port -> do
311+ handle <- connectTo port
310312 let input = concat $ replicate 2 $
311313 ["POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"] ++
312314 (replicate 50 "5\r\n12345\r\n") ++
313315 ["0\r\n\r\n"]
314− mapM_ (msWrite ms) input
316+ mapM_ (msWrite handle) input
317+ msClose handle
315318 atomically $ do
316319 count <- readTVar countVar
317320 check $ count == 2
@@ -327,14 +330,16 @@ spec = do
327330 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
328331 atomically $ modifyTVar countVar (+ 1)
329332 f $ responseLBS status200 [] ""
330− withApp defaultSettings app $ withMySocket $ \ms -> do
333+ withApp defaultSettings app $ \port -> do
334+ handle <- connectTo port
331335 let input = S.concat
332336 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
333337 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n"
334338 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
335339 , "b\r\nHello World\r\n0\r\n\r\n"
336340 ]
337− mapM_ (msWrite ms) $ map S.singleton $ S.unpack input
341+ mapM_ (msWrite handle) $ map S.singleton $ S.unpack input
342+ msClose handle
338343 atomically $ do
339344 count <- readTVar countVar
340345 check $ count == 2
@@ -355,18 +360,20 @@ spec = do
355360 E.throwIO (e :: E.SomeException)
356361 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
357362 f $ responseLBS status200 [] ""
358− withApp (setTimeout 1 defaultSettings) app $ withMySocket $ \ms -> do
363+ withApp (setTimeout 1 defaultSettings) app $ \port -> do
359364 let bs1 = S.replicate 2048 88
360365 bs2 = "This is short"
361366 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"
367+ handle <- connectTo port
368+ msWrite handle "POST / HTTP/1.1\r\n"
369+ msWrite handle "content-length: "
370+ msWrite handle $ S8.pack $ show $ S.length bs
371+ msWrite handle "\r\n\r\n"
366372 threadDelay 100000
367− msWrite ms bs1
373+ msWrite handle bs1
368374 threadDelay 100000
369− msWrite ms bs2
375+ msWrite handle bs2
376+ msClose handle
370377 threadDelay 5000000
371378 front <- I.readIORef ifront
372379 S.concat (front []) `shouldBe` bs
@@ -382,7 +389,8 @@ spec = do
382389 loop
383390 loop
384391 doubleBS = S.concatMap $ \w -> S.pack [w, w]
385− withApp defaultSettings app $ withMySocket $ \ms -> do
392+ withApp defaultSettings app $ \port -> do
393+ ms <- connectTo port
386394 msWrite ms "POST / HTTP/1.1\r\n\r\n12345"
387395 timeout 100000 (msRead ms 10) >>= (`shouldBe` Just "1122334455")
388396 msWrite ms "67890"
@@ -410,14 +418,15 @@ spec = do
410418 atomically $ modifyTVar countVar (+ 1)
411419 loop
412420 loop
413− withApp defaultSettings app $ withMySocket $ \ms -> do
414− msWrite ms "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n"
421+ withApp defaultSettings app $ \port -> do
422+ (sock, _addr) <- getSocketTCP "127.0.0.1" port
423+ sendAll sock "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n"
415424 threadDelay 10000
416− msWrite ms "5\r\nhello\r\n0\r\n\r\n"
425+ sendAll sock "5\r\nhello\r\n0\r\n\r\n"
417426 atomically $ do
418427 count <- readTVar countVar
419428 check $ count >= 1
420− bs <- safeRecv (msSocket ms) 4096 -- must not use msRead
429+ bs <- safeRecv sock 4096
421430 S.takeWhile (/= 13) bs `shouldBe` "HTTP/1.1 200 OK"
422431
423432 it "streaming response with length" $ do
warp/warp.cabal+18−14
@@ -1,5 +1,5 @@
11Name: warp
2−Version: 3.3.9
2+Version: 3.2.27
33Synopsis: A fast, light-weight web server for WAI applications.
44License: MIT
55License-file: LICENSE
@@ -44,8 +44,8 @@ Library
4444 , ghc-prim
4545 , hashable
4646 , http-date
47− , http-types >= 0.12
48− , http2 >= 2.0 && < 2.1
47+ , http-types >= 0.11
48+ , http2 >= 1.6 && < 1.7
4949 , iproute >= 1.3.1
5050 , simple-sendfile >= 0.2.7 && < 0.3
5151 , stm >= 2.3
@@ -56,7 +56,6 @@ Library
5656 , vault >= 0.3
5757 , wai >= 3.2 && < 3.3
5858 , word8
59− , x509
6059 if impl(ghc < 8)
6160 Build-Depends: semigroups
6261 if flag(network-bytestring)
@@ -75,11 +74,15 @@ Library
7574 Network.Wai.Handler.Warp.FileInfoCache
7675 Network.Wai.Handler.Warp.HashMap
7776 Network.Wai.Handler.Warp.HTTP2
77+ Network.Wai.Handler.Warp.HTTP2.EncodeFrame
7878 Network.Wai.Handler.Warp.HTTP2.File
79− Network.Wai.Handler.Warp.HTTP2.PushPromise
79+ Network.Wai.Handler.Warp.HTTP2.HPACK
80+ Network.Wai.Handler.Warp.HTTP2.Manager
81+ Network.Wai.Handler.Warp.HTTP2.Receiver
8082 Network.Wai.Handler.Warp.HTTP2.Request
81− Network.Wai.Handler.Warp.HTTP2.Response
83+ Network.Wai.Handler.Warp.HTTP2.Sender
8284 Network.Wai.Handler.Warp.HTTP2.Types
85+ Network.Wai.Handler.Warp.HTTP2.Worker
8386 Network.Wai.Handler.Warp.Header
8487 Network.Wai.Handler.Warp.IO
8588 Network.Wai.Handler.Warp.Imports
@@ -114,7 +117,6 @@ Library
114117 Default-Language: Haskell2010
115118
116119Test-Suite doctest
117− buildable: False
118120 Type: exitcode-stdio-1.0
119121 HS-Source-Dirs: test
120122 Ghc-Options: -threaded -Wall
@@ -151,11 +153,15 @@ Test-Suite spec
151153 Network.Wai.Handler.Warp.File
152154 Network.Wai.Handler.Warp.FileInfoCache
153155 Network.Wai.Handler.Warp.HTTP2
156+ Network.Wai.Handler.Warp.HTTP2.EncodeFrame
154157 Network.Wai.Handler.Warp.HTTP2.File
155− Network.Wai.Handler.Warp.HTTP2.PushPromise
158+ Network.Wai.Handler.Warp.HTTP2.HPACK
159+ Network.Wai.Handler.Warp.HTTP2.Manager
160+ Network.Wai.Handler.Warp.HTTP2.Receiver
156161 Network.Wai.Handler.Warp.HTTP2.Request
157− Network.Wai.Handler.Warp.HTTP2.Response
162+ Network.Wai.Handler.Warp.HTTP2.Sender
158163 Network.Wai.Handler.Warp.HTTP2.Types
164+ Network.Wai.Handler.Warp.HTTP2.Worker
159165 Network.Wai.Handler.Warp.HashMap
160166 Network.Wai.Handler.Warp.Header
161167 Network.Wai.Handler.Warp.IO
@@ -196,8 +202,8 @@ Test-Suite spec
196202 , hspec >= 1.3
197203 , http-client
198204 , http-date
199− , http-types >= 0.12
200− , http2 >= 2.0 && < 2.1
205+ , http-types >= 0.8.5
206+ , http2 >= 1.6 && < 1.7
201207 , iproute >= 1.3.1
202208 , lifted-base >= 0.1
203209 , network
@@ -208,15 +214,14 @@ Test-Suite spec
208214 , text
209215 , time
210216 , time-manager
217+ , transformers >= 0.2.2
211218 , unix-compat >= 0.2
212219 , vault
213220 , wai >= 3.2 && < 3.3
214221 , word8
215− , x509
216222 -- Build-Tool-Depends: hspec-discover:hspec-discover
217223 if impl(ghc < 8)
218224 Build-Depends: semigroups
219− , transformers
220225
221226 if (os(linux) || os(freebsd) || os(darwin)) && flag(allow-sendfilefd)
222227 Cpp-Options: -DSENDFILEFD
@@ -251,7 +256,6 @@ Benchmark parser
251256 , network
252257 , time-manager
253258 , unix-compat
254− , x509
255259 if impl(ghc < 8)
256260 Build-Depends: semigroups
257261