on master help sign in
Merge pull request #426 from awpr/wai_http2

Add an HTTP/2-aware application type supporting trailers and pushed streams.

commit76457915ac7c0f8d13e06ba7a4dfe83a3260cbc2 authorKazu Yamamoto <kazu@iij.ad.jp> dateSun 13 Sep 2015, 16:53 (11 years ago) parentc10a80b72a860b
16 files changed+960−470
wai/Network/Wai/HTTP2.hs+209 wai/Network/Wai/Internal.hs+98−2 wai/wai.cabal+3 warp-tls/Network/Wai/Handler/WarpTLS.hs+28−7 warp/Network/Wai/Handler/Warp.hs+11 warp/Network/Wai/Handler/Warp/HTTP2.hs+4−2 warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hs+17−12 warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+6−6 warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+1−1 warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+221−237 warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+76−26 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+150−60 warp/Network/Wai/Handler/Warp/Internal.hs+11 warp/Network/Wai/Handler/Warp/RequestHeader.hs+2−28 warp/Network/Wai/Handler/Warp/Response.hs+10−60 warp/Network/Wai/Handler/Warp/Run.hs+113−29
wai/Network/Wai/HTTP2.hsnew file+209
@@ -0,0 +1,209 @@
1+{-# LANGUAGE OverloadedStrings, RankNTypes #-}
2+{-# LANGUAGE CPP #-}
3+
4+-- | An HTTP\/2-aware variant of the 'Network.Wai.Application' type. Compared
5+-- to the original, this exposes the new functionality of server push and
6+-- trailers, allows stream fragments to be sent in the form of file ranges, and
7+-- allows the stream body to produce a value to be used in constructing the
8+-- trailers. Existing @Applications@ can be faithfully upgraded to HTTP\/2
9+-- with 'promoteApplication' or served transparently over both protocols with
10+-- the normal Warp 'Network.Wai.Handler.Warp.run' family of functions.
11+--
12+-- An 'HTTP2Application' takes a 'Request' and a 'PushFunc' and produces a
13+-- 'Responder' that will push any associated resources and send the response
14+-- body. The response is always a stream of 'Builder's and file chunks.
15+-- Equivalents of the 'Network.Wai.responseBuilder' family of functions are
16+-- provided for creating 'Responder's conveniently.
17+--
18+-- Pushed streams are handled by an IO action that triggers a server push. It
19+-- returns @True@ if the @PUSH_PROMISE@ frame was sent, @False@ if not. Note
20+-- this means it will still return @True@ if the client reset or ignored the
21+-- stream. This gives handlers the freedom to implement their own heuristics
22+-- for whether to actually push a resource, while also allowing middleware and
23+-- frameworks to trigger server pushes automatically.
24+
25+module Network.Wai.HTTP2
26+ (
27+ -- * Applications
28+ HTTP2Application
29+ -- * Responder
30+ , Responder
31+ , Body
32+ , BodyOf
33+ , Chunk(..)
34+ , Trailers
35+ -- * Server push
36+ , PushFunc
37+ , PushPromise(..)
38+ , promiseHeaders
39+ -- * Conveniences
40+ , promoteApplication
41+ -- ** Responders
42+ , responder
43+ , respondFile
44+ , respondFilePart
45+ , respondNotFound
46+ -- ** Stream Bodies
47+ , SimpleBody
48+ , streamFilePart
49+ , streamBuilder
50+ , streamSimple
51+ ) where
52+
53+#if __GLASGOW_HASKELL__ < 710
54+import Data.Functor ((<$))
55+#endif
56+import Blaze.ByteString.Builder (Builder)
57+import Data.ByteString (ByteString)
58+import qualified Network.HTTP.Types as H
59+
60+import Network.Wai (Application)
61+import Network.Wai.Internal
62+ ( FilePart(..)
63+ , Request(requestHeaders)
64+ , Response(..)
65+ , ResponseReceived(..)
66+ , adjustForFilePart
67+ , chooseFilePart
68+ , tryGetFileSize
69+ )
70+
71+-- | Headers sent after the end of a data stream, as defined by section 4.1.2 of
72+-- the HTTP\/1.1 spec (RFC 7230), and section 8.1 of the HTTP\/2 spec.
73+type Trailers = [H.Header]
74+
75+-- | The synthesized request and headers of a pushed stream.
76+data PushPromise = PushPromise
77+ { promisedMethod :: H.Method
78+ , promisedPath :: ByteString
79+ , promisedAuthority :: ByteString
80+ , promisedScheme :: ByteString
81+ , promisedHeader :: H.RequestHeaders
82+ }
83+
84+-- | The HTTP\/2-aware equivalent of 'Network.Wai.Application'.
85+type HTTP2Application = Request -> PushFunc -> Responder
86+
87+-- | Part of a streaming response -- either a 'Builder' or a range of a file.
88+data Chunk = FileChunk FilePath FilePart | BuilderChunk Builder
89+
90+-- | The streaming body of a response. Equivalent to
91+-- 'Network.Wai.StreamingBody' except that it can also write file ranges and
92+-- return a result of type @a@.
93+type Body a = BodyOf Chunk a
94+
95+-- | Generalization of 'Body' to arbitrary chunk types.
96+--
97+-- 'Network.Wai.StreamingBody' is identical to @BodyOf Builder ()@.
98+type BodyOf c a = (c -> IO ()) -> IO () -> IO a
99+
100+-- | The result of an 'HTTP2Application'; or, alternately, an application
101+-- that's independent of the request. This is a continuation-passing style
102+-- function that first provides a response by calling the given respond
103+-- function, then returns the request's 'Trailers'.
104+--
105+-- The respond function is similar to the one in 'Network.Wai.Application', but
106+-- it only takes a streaming body, the status and headers are curried, and it
107+-- passes on any result value from the stream body.
108+type Responder = (forall a. H.Status -> H.ResponseHeaders -> Body a -> IO a)
109+ -> IO Trailers
110+
111+-- | A function given to an 'HTTP2Application' to initiate a server-pushed
112+-- stream. Its argument is the same as the result of an 'HTTP2Application', so
113+-- you can either implement the response inline, or call your own application
114+-- to create the response.
115+type PushFunc = PushPromise -> Responder -> IO Bool
116+
117+-- | Create the 'H.RequestHeaders' corresponding to the given 'PushPromise'.
118+--
119+-- This is primarily useful for Wai handlers like Warp, and application
120+-- implementers are unlikely to need it.
121+promiseHeaders :: PushPromise -> H.RequestHeaders
122+promiseHeaders p =
123+ [ (":method", promisedMethod p)
124+ , (":path", promisedPath p)
125+ , (":authority", promisedAuthority p)
126+ , (":scheme", promisedScheme p)
127+ ] ++ promisedHeader p
128+
129+-- | Create a response body consisting of a single range of a file. Does not
130+-- set Content-Length or Content-Range headers. For that, use
131+-- 'respondFilePart' or 'respondFile'.
132+streamFilePart :: FilePath -> FilePart -> Body ()
133+streamFilePart path part write _ = write $ FileChunk path part
134+
135+-- | Respond with a single range of a file, adding the Content-Length and
136+-- Content-Range headers and changing the status to 206 as appropriate.
137+--
138+-- If you want the range to be inferred automatically from the Range header,
139+-- use 'respondFile' instead.
140+respondFilePart :: H.Status -> H.ResponseHeaders -> FilePath -> FilePart -> Responder
141+respondFilePart s h path part respond = do
142+ let (s', h') = adjustForFilePart s h part
143+ respond s' h' $ streamFilePart path part
144+ return []
145+
146+-- | Serve the requested range of the specified file (based on the Range
147+-- header), using the given 'H.Status' and 'H.ResponseHeaders' as a base. If
148+-- the file is not accessible, the status will be replaced with 404 and a
149+-- default not-found message will be served. If a partial file is requested,
150+-- the status will be replaced with 206 and the Content-Range header will be
151+-- added. The Content-Length header will always be added.
152+respondFile :: H.Status -> H.ResponseHeaders -> FilePath -> H.RequestHeaders -> Responder
153+respondFile s h path req respond = do
154+ fileSize <- tryGetFileSize path
155+ case fileSize of
156+ Left _ -> respondNotFound h respond
157+ Right size -> respondFileExists s h path size req respond
158+
159+-- As 'respondFile', but with prior knowledge of the file's existence and size.
160+respondFileExists :: H.Status -> H.ResponseHeaders -> FilePath -> Integer -> H.RequestHeaders -> Responder
161+respondFileExists s h path size reqHdrs =
162+ respondFilePart s h path $ chooseFilePart size $ lookup H.hRange reqHdrs
163+
164+-- | Respond with a minimal 404 page with the given headers.
165+respondNotFound :: H.ResponseHeaders -> Responder
166+respondNotFound h respond = do
167+ respond H.notFound404 h' $ streamBuilder "File not found."
168+ return []
169+ where
170+ contentType = (H.hContentType, "text/plain; charset=utf-8")
171+ h' = contentType:filter ((/=H.hContentType) . fst) h
172+
173+-- | Create a response body consisting of a single builder.
174+streamBuilder :: Builder -> Body ()
175+streamBuilder builder write _ = write $ BuilderChunk builder
176+
177+-- | Equivalent to 'Body' but only streaming 'Builder's.
178+--
179+-- 'Network.Wai.StreamingBody' is identical to @SimpleBody ()@.
180+type SimpleBody a = BodyOf Builder a
181+
182+-- | Create a response body of a stream of 'Builder's.
183+streamSimple :: SimpleBody a -> Body a
184+streamSimple body write flush = body (write . BuilderChunk) flush
185+
186+-- | Use a normal WAI 'Response' to send the response. Useful if you're
187+-- sharing code between HTTP\/2 applications and HTTP\/1 applications.
188+--
189+-- The 'Request' is used to determine the right file range to serve for
190+-- 'ResponseFile'.
191+responder :: Request -> Response -> Responder
192+responder req response respond = case response of
193+ (ResponseBuilder s h b) -> [] <$ respond s h (streamBuilder b)
194+ (ResponseStream s h body) -> [] <$ respond s h (streamSimple body)
195+ (ResponseRaw _ fallback) -> responder req fallback respond
196+ (ResponseFile s h path mpart) -> go respond
197+ where
198+ -- Hooray, forcing things to be universally quantified.
199+ go :: Responder
200+ go = maybe
201+ (respondFile s h path $ requestHeaders req)
202+ (respondFilePart s h path)
203+ mpart
204+
205+-- | Promote a normal WAI 'Application' to an 'HTTP2Application' by ignoring
206+-- the HTTP/2-specific features.
207+promoteApplication :: Application -> HTTP2Application
208+promoteApplication app req _ respond = [] <$ app req respond'
209+ where respond' r = ResponseReceived <$ responder req r respond
wai/Network/Wai/Internal.hs+98−2
@@ -1,6 +1,7 @@
11{-# OPTIONS_HADDOCK not-home #-}
22{-# LANGUAGE DeriveDataTypeable #-}
33{-# LANGUAGE CPP #-}
4+{-# LANGUAGE OverloadedStrings #-}
45{-# LANGUAGE RankNTypes #-}
56{-# LANGUAGE RecordWildCards #-}
67-- | Internal constructors and helper functions. Note that no guarantees are
@@ -8,14 +9,25 @@
89module Network.Wai.Internal where
910
1011import Blaze.ByteString.Builder (Builder)
11−import qualified Data.ByteString as B
12+import Control.Exception (IOException, try)
13+import qualified Data.ByteString as B hiding (pack)
14+import qualified Data.ByteString.Builder as B
15+import qualified Data.ByteString.Char8 as B (pack, readInteger)
16+import qualified Data.ByteString.Lazy as L
17+#if __GLASGOW_HASKELL__ < 709
18+import Data.Functor ((<$>))
19+#endif
20+import Data.Maybe (listToMaybe)
1221import Data.Text (Text)
1322import Data.Typeable (Typeable)
14−import Data.Vault.Lazy (Vault)
23+import Data.Vault.Lazy (Vault)
1524import Data.Word (Word64)
1625import qualified Network.HTTP.Types as H
26+import qualified Network.HTTP.Types.Header as HH
1727import Network.Socket (SockAddr)
28+import Numeric (showInt)
1829import Data.List (intercalate)
30+import qualified System.PosixCompat.Files as P
1931
2032-- | Information on the request sent by the client. This abstracts away the
2133-- details of the underlying implementation.
@@ -145,3 +157,87 @@ data FilePart = FilePart
145157-- Since 3.0.0
146158data ResponseReceived = ResponseReceived
147159 deriving Typeable
160+
161+-- | Look up the size of a file in 'Right' or the 'IOException' in 'Left'.
162+tryGetFileSize :: FilePath -> IO (Either IOException Integer)
163+tryGetFileSize path =
164+ fmap (fromIntegral . P.fileSize) <$> try (P.getFileStatus path)
165+
166+-- | \"Content-Range\".
167+hContentRange :: H.HeaderName
168+hContentRange = "Content-Range"
169+
170+-- | @contentRangeHeader beg end total@ constructs a Content-Range 'H.Header'
171+-- for the range specified.
172+contentRangeHeader :: Integer -> Integer -> Integer -> H.Header
173+contentRangeHeader beg end total = (hContentRange, range)
174+ where
175+ range = B.pack
176+ -- building with ShowS
177+ $ 'b' : 'y': 't' : 'e' : 's' : ' '
178+ : (if beg > end then ('*':) else
179+ showInt beg
180+ . ('-' :)
181+ . showInt end)
182+ ( '/'
183+ : showInt total "")
184+
185+-- | Given the full size of a file and optionally a Range header value,
186+-- determine the range to serve by parsing the range header and obeying it, or
187+-- serving the whole file if it's absent or malformed.
188+chooseFilePart :: Integer -> Maybe B.ByteString -> FilePart
189+chooseFilePart size Nothing = FilePart 0 size size
190+chooseFilePart size (Just range) = case parseByteRanges range >>= listToMaybe of
191+ -- Range is broken
192+ Nothing -> FilePart 0 size size
193+ Just hrange -> checkRange hrange
194+ where
195+ checkRange (H.ByteRangeFrom beg) = fromRange beg (size - 1)
196+ checkRange (H.ByteRangeFromTo beg end) = fromRange beg (min (size - 1) end)
197+ checkRange (H.ByteRangeSuffix count) = fromRange (max 0 (size - count)) (size - 1)
198+
199+ fromRange beg end = FilePart beg (end - beg + 1) size
200+
201+-- | Adjust the given 'H.Status' and 'H.ResponseHeaders' based on the given
202+-- 'FilePart'. This means replacing the status with 206 if the response is
203+-- partial, and adding the Content-Length (always) and Content-Range (if
204+-- appropriate) headers.
205+adjustForFilePart :: H.Status -> H.ResponseHeaders -> FilePart -> (H.Status, H.ResponseHeaders)
206+adjustForFilePart s h part = (s', h'')
207+ where
208+ off = filePartOffset part
209+ len = filePartByteCount part
210+ size = filePartFileSize part
211+
212+ contentRange = contentRangeHeader off (off + len - 1) size
213+ lengthBS = L.toStrict $ B.toLazyByteString $ B.integerDec len
214+ s' = if filePartByteCount part /= size then H.partialContent206 else s
215+ h' = (H.hContentLength, lengthBS):h
216+ h'' = (if len == size then id else (contentRange:)) h'
217+
218+-- | Parse the value of a Range header into a 'HH.ByteRanges'.
219+parseByteRanges :: B.ByteString -> Maybe HH.ByteRanges
220+parseByteRanges bs1 = do
221+ bs2 <- stripPrefix "bytes=" bs1
222+ (r, bs3) <- range bs2
223+ ranges (r:) bs3
224+ where
225+ range bs2 = do
226+ (i, bs3) <- B.readInteger bs2
227+ if i < 0 -- has prefix "-" ("-0" is not valid, but here treated as "0-")
228+ then Just (HH.ByteRangeSuffix (negate i), bs3)
229+ else do
230+ bs4 <- stripPrefix "-" bs3
231+ case B.readInteger bs4 of
232+ Just (j, bs5) | j >= i -> Just (HH.ByteRangeFromTo i j, bs5)
233+ _ -> Just (HH.ByteRangeFrom i, bs4)
234+ ranges front bs3
235+ | B.null bs3 = Just (front [])
236+ | otherwise = do
237+ bs4 <- stripPrefix "," bs3
238+ (r, bs5) <- range bs4
239+ ranges (front . (r:)) bs5
240+
241+ stripPrefix x y
242+ | x `B.isPrefixOf` y = Just (B.drop (B.length x) y)
243+ | otherwise = Nothing
wai/wai.cabal+3
@@ -21,12 +21,15 @@ Source-repository head
2121Library
2222 Build-Depends: base >= 4 && < 5
2323 , bytestring >= 0.9.1.4
24+ , bytestring-builder >= 0.10.4.0 && < 0.10.7
2425 , blaze-builder >= 0.2.1.4 && < 0.5
2526 , network >= 2.2.1.5
2627 , http-types >= 0.7
2728 , text >= 0.7
29+ , unix-compat >= 0.2
2830 , vault >= 0.3 && < 0.4
2931 Exposed-modules: Network.Wai
32+ Network.Wai.HTTP2
3033 Network.Wai.Internal
3134 ghc-options: -Wall
3235
warp-tls/Network/Wai/Handler/WarpTLS.hs+28−7
@@ -35,6 +35,8 @@ module Network.Wai.Handler.WarpTLS (
3535 -- * Runner
3636 , runTLS
3737 , runTLSSocket
38+ , runHTTP2TLS
39+ , runHTTP2TLSSocket
3840 -- * Exception
3941 , WarpTLSException (..)
4042 ) where
@@ -57,6 +59,7 @@ import Network.Socket.ByteString (sendAll)
5759import qualified Network.TLS as TLS
5860import qualified Network.TLS.Extra as TLSExtra
5961import Network.Wai (Application)
62+import Network.Wai.HTTP2 (HTTP2Application)
6063import Network.Wai.Handler.Warp
6164import Network.Wai.Handler.Warp.Internal
6265import System.IO.Error (isEOFError)
@@ -217,21 +220,39 @@ tlsSettingsChainMemory cert chainCerts key = defaultTlsSettings
217220
218221----------------------------------------------------------------
219222
223+-- Composition over two arguments at once; used for runHTTP2\*.
224+(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
225+f .: g = curry $ f . uncurry g
220226
221227-- | Running 'Application' with 'TLSSettings' and 'Settings'.
222228runTLS :: TLSSettings -> Settings -> Application -> IO ()
223−runTLS tset set app = withSocketsDo $
229+runTLS tset set = runServeTLS tset set . serveDefault
230+
231+-- | Run an HTTP\/2-aware server with 'TLSSettings' and 'Settings'.
232+runHTTP2TLS :: TLSSettings -> Settings -> HTTP2Application -> Application -> IO ()
233+runHTTP2TLS tset set = runServeTLS tset set .: serveHTTP2
234+
235+runServeTLS :: TLSSettings -> Settings -> ServeConnection -> IO ()
236+runServeTLS tset set serve = withSocketsDo $
224237 bracket
225238 (bindPortTCP (getPort set) (getHost set))
226239 sClose
227− (\sock -> runTLSSocket tset set sock app)
240+ (\sock -> runServeTLSSocket tset set sock serve)
228241
229242----------------------------------------------------------------
230243
231244-- | Running 'Application' with 'TLSSettings' and 'Settings' using
232245-- specified 'Socket'.
233246runTLSSocket :: TLSSettings -> Settings -> Socket -> Application -> IO ()
234−runTLSSocket tlsset@TLSSettings{..} set sock app = do
247+runTLSSocket tset set sock = runServeTLSSocket tset set sock . serveDefault
248+
249+-- | Run an HTTP\/2-aware server with 'TLSSettings' and 'Settings' using
250+-- specified 'Socket'.
251+runHTTP2TLSSocket :: TLSSettings -> Settings -> Socket -> HTTP2Application -> Application -> IO ()
252+runHTTP2TLSSocket tset set sock = runServeTLSSocket tset set sock .: serveHTTP2
253+
254+runServeTLSSocket :: TLSSettings -> Settings -> Socket -> ServeConnection -> IO ()
255+runServeTLSSocket tlsset@TLSSettings{..} set sock serve = do
235256 credential <- case (certMemory, keyMemory) of
236257 (Nothing, Nothing) ->
237258 either error id <$>
@@ -241,11 +262,11 @@ runTLSSocket tlsset@TLSSettings{..} set sock app = do
241262 key <- maybe (S.readFile keyFile) return mkey
242263 either error return $
243264 TLS.credentialLoadX509ChainFromMemory cert chainCertsMemory key
244− runTLSSocket' tlsset set credential sock app
265+ runServeTLSSocket' tlsset set credential sock serve
245266
246−runTLSSocket' :: TLSSettings -> Settings -> TLS.Credential -> Socket -> Application -> IO ()
247−runTLSSocket' tlsset@TLSSettings{..} set credential sock app =
248− runSettingsConnectionMakerSecure set get app
267+runServeTLSSocket' :: TLSSettings -> Settings -> TLS.Credential -> Socket -> ServeConnection -> IO ()
268+runServeTLSSocket' tlsset@TLSSettings{..} set credential sock serve =
269+ runServeSettingsConnectionMakerSecure set get serve
249270 where
250271 get = getter tlsset sock params
251272 params = TLS.ServerParams {
warp/Network/Wai/Handler/Warp.hs+11
@@ -38,10 +38,21 @@
3838--
3939module Network.Wai.Handler.Warp (
4040 -- * Run a Warp server
41+ -- | All of these automatically serve the same 'Application' over HTTP\/1,
42+ -- HTTP\/1.1, and HTTP\/2.
4143 run
4244 , runEnv
4345 , runSettings
4446 , runSettingsSocket
47+ -- * Run an HTTP\/2-aware server
48+ -- | Each of these takes an HTTP\/2-aware application as well as a backup
49+ -- 'Application' to be used for HTTP\/1.1 and HTTP\/1 connections. These
50+ -- are only needed if your application needs access to HTTP\/2-specific
51+ -- features such as trailers or pushed streams.
52+ , runHTTP2
53+ , runHTTP2Env
54+ , runHTTP2Settings
55+ , runHTTP2SettingsSocket
4556 -- * Settings
4657 , Settings
4758 , defaultSettings
warp/Network/Wai/Handler/Warp/HTTP2.hs+4−2
@@ -7,9 +7,11 @@ import Control.Concurrent (forkIO, killThread)
77import qualified Control.Exception as E
88import Control.Monad (when, unless, replicateM_)
99import Data.ByteString (ByteString)
10+
1011import Network.HTTP2
1112import Network.Socket (SockAddr)
12−import Network.Wai
13+
14+import Network.Wai.HTTP2 (HTTP2Application)
1315import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
1416import Network.Wai.Handler.Warp.HTTP2.Manager
1517import Network.Wai.Handler.Warp.HTTP2.Receiver
@@ -22,7 +24,7 @@ import Network.Wai.Handler.Warp.Types
2224
2325----------------------------------------------------------------
2426
25−http2 :: Connection -> InternalInfo -> SockAddr -> Transport -> S.Settings -> (BufSize -> IO ByteString) -> Application -> IO ()
27+http2 :: Connection -> InternalInfo -> SockAddr -> Transport -> S.Settings -> (BufSize -> IO ByteString) -> HTTP2Application -> IO ()
2628http2 conn ii addr transport settings readN app = do
2729 checkTLS
2830 ok <- checkPreface
warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hs+17−12
@@ -5,6 +5,7 @@ module Network.Wai.Handler.Warp.HTTP2.HPACK where
55
66import Control.Arrow (first)
77import qualified Control.Exception as E
8+import qualified Data.ByteString as B
89import Data.ByteString.Builder (Builder)
910import qualified Data.ByteString.Char8 as B8
1011import Data.CaseInsensitive (foldedCase)
@@ -12,30 +13,34 @@ import Data.IORef (readIORef, writeIORef)
1213import Network.HPACK
1314import qualified Network.HTTP.Types as H
1415import Network.HTTP2
15−import Network.Wai
1616import Network.Wai.Handler.Warp.HTTP2.Types
1717import Network.Wai.Handler.Warp.Header
1818import Network.Wai.Handler.Warp.Response
1919import qualified Network.Wai.Handler.Warp.Settings as S
2020import Network.Wai.Handler.Warp.Types
2121
22−hpackEncodeHeader :: Context -> InternalInfo -> S.Settings -> Response
23− -> IO Builder
24−hpackEncodeHeader Context{encodeDynamicTable} ii settings rsp = do
25− hdr1 <- addServerAndDate hdr0
22+hpackEncodeHeader :: Context -> InternalInfo -> S.Settings -> H.Status
23+ -> H.ResponseHeaders -> IO Builder
24+hpackEncodeHeader ctx ii settings s h = do
25+ hdr1 <- addServerAndDate h
2626 let hdr2 = (":status", status) : map (first foldedCase) hdr1
27− ehdrtbl <- readIORef encodeDynamicTable
28− (ehdrtbl', builder) <- encodeHeaderBuilder defaultEncodeStrategy ehdrtbl hdr2
29− writeIORef encodeDynamicTable ehdrtbl'
30− return builder
27+ hpackEncodeRawHeaders ctx hdr2
3128 where
32− hdr0 = responseHeaders rsp
33− status = B8.pack $ show $ H.statusCode $ responseStatus rsp
29+ status = B8.pack $ show $ H.statusCode $ s
3430 dc = dateCacher ii
35− rspidxhdr = indexResponseHeader hdr0
31+ rspidxhdr = indexResponseHeader h
3632 defServer = S.settingsServerName settings
3733 addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr
3834
35+hpackEncodeCIHeaders :: Context -> [H.Header] -> IO Builder
36+hpackEncodeCIHeaders ctx = hpackEncodeRawHeaders ctx . map (first foldedCase)
37+
38+hpackEncodeRawHeaders :: Context -> [(B.ByteString, B.ByteString)] -> IO Builder
39+hpackEncodeRawHeaders Context{encodeDynamicTable} hdr = do
40+ ehdrtbl <- readIORef encodeDynamicTable
41+ (ehdrtbl', builder) <- encodeHeaderBuilder defaultEncodeStrategy ehdrtbl hdr
42+ writeIORef encodeDynamicTable ehdrtbl'
43+ return builder
3944
4045----------------------------------------------------------------
4146
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+6−6
@@ -56,7 +56,7 @@ frameReceiver ctx mkreq recvN = loop `E.catch` sendGoaway
5656 cont <- processStreamGuardingError $ decodeFrameHeader hd
5757 when cont loop
5858
59− processStreamGuardingError (_, FrameHeader{streamId})
59+ processStreamGuardingError (FrameHeaders, FrameHeader{streamId})
6060 | isResponse streamId = E.throwIO $ ConnectionError ProtocolError "stream id should be odd"
6161 processStreamGuardingError (FrameUnknown _, FrameHeader{payloadLength}) = do
6262 mx <- readIORef continued
@@ -161,15 +161,15 @@ frameReceiver ctx mkreq recvN = loop `E.catch` sendGoaway
161161 -- idle, so that receiving frames on it is only a
162162 -- stream error.
163163 consume payloadLength
164− strm <- newStream streamId 0
164+ strm <- newStream concurrency streamId 0
165165 writeIORef (streamState strm) $ Closed $
166166 ResetByMe $ E.toException $
167167 StreamError RefusedStream streamId
168168 insert streamTable streamId strm
169169 E.throwIO $ StreamError RefusedStream streamId
170170 ws <- initialWindowSize <$> readIORef http2settings
171− newstrm <- newStream streamId (fromIntegral ws)
172− when (ftyp == FrameHeaders) $ opened ctx newstrm
171+ newstrm <- newStream concurrency streamId (fromIntegral ws)
172+ when (ftyp == FrameHeaders) $ opened newstrm
173173 insert streamTable streamId newstrm
174174 return newstrm
175175
@@ -310,10 +310,10 @@ stream FrameWindowUpdate header@FrameHeader{streamId} bs _ s Stream{streamWindow
310310 atomically $ writeTVar streamWindow w
311311 return s
312312
313−stream FrameRSTStream header bs ctx _ strm = do
313+stream FrameRSTStream header bs _ _ strm = do
314314 RSTStreamFrame e <- guardIt $ decoderstStreamFrame header bs
315315 let cc = Reset e
316− closed ctx strm cc
316+ closed strm cc
317317 return $ Closed cc -- will be written to streamState again
318318
319319stream FramePriority header bs Context{outputQ} s Stream{streamNumber,streamPriority} = do
warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+1−1
@@ -37,7 +37,7 @@ data ValidHeaders = ValidHeaders {
3737 vhMethod :: ByteString
3838 , vhPath :: ByteString
3939 , vhAuth :: Maybe ByteString
40− , vhCL :: Maybe Int
40+ , vhCL :: Maybe Int -- ^ Content-Length
4141 , vhHeader :: RequestHeaders
4242 }
4343
warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+221−237
@@ -7,55 +7,70 @@ module Network.Wai.Handler.Warp.HTTP2.Sender (frameSender) where
77#if __GLASGOW_HASKELL__ < 709
88import Control.Applicative
99#endif
10+import Control.Concurrent.MVar (putMVar)
1011import Control.Concurrent.STM
1112import qualified Control.Exception as E
12−import Control.Monad (unless, void, when)
13+import Control.Monad (void, when)
1314import qualified Data.ByteString as BS
15+import qualified Data.ByteString.Builder as B (int32BE)
1416import qualified Data.ByteString.Builder.Extra as B
17+import Data.Monoid ((<>))
1518import Foreign.Ptr
19+import qualified Network.HTTP.Types as H
1620import Network.HTTP2
1721import Network.HTTP2.Priority
18−import Network.Wai
22+import Network.Wai (FilePart(..))
23+import Network.Wai.HTTP2 (Trailers, promiseHeaders)
1924import Network.Wai.Handler.Warp.Buffer
2025import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
2126import Network.Wai.Handler.Warp.HTTP2.HPACK
2227import Network.Wai.Handler.Warp.HTTP2.Types
2328import Network.Wai.Handler.Warp.IORef
2429import qualified Network.Wai.Handler.Warp.Settings as S
25−import qualified Network.Wai.Handler.Warp.Timeout as T
2630import Network.Wai.Handler.Warp.Types
27−import Network.Wai.Internal (Response(..))
28−import qualified System.PosixCompat.Files as P
2931
3032#ifdef WINDOWS
3133import qualified System.IO as IO
3234#else
33−import Network.Wai.Handler.Warp.FdCache
35+import Network.Wai.Handler.Warp.FdCache (getFd)
3436import Network.Wai.Handler.Warp.SendFile (positionRead)
37+import qualified Network.Wai.Handler.Warp.Timeout as T
3538import System.Posix.IO (openFd, OpenFileFlags(..), defaultFileFlags, OpenMode(ReadOnly), closeFd)
36−import System.Posix.Types
39+import System.Posix.Types (Fd)
3740#endif
3841
3942----------------------------------------------------------------
4043
44+-- | The platform-specific type of an open file to stream from. On Windows we
45+-- don't have pread, so this is just a Handle; on Unix platforms with pread,
46+-- this is a file descriptor supplied by the fd cache.
47+#ifdef WINDOWS
48+type OpenFile = IO.Handle
49+#else
50+type OpenFile = Fd
51+#endif
52+
4153data Leftover = LZero
4254 | LOne B.BufferWriter
4355 | LTwo BS.ByteString B.BufferWriter
56+ | LFile OpenFile Integer Integer (IO ())
4457
4558----------------------------------------------------------------
4659
47−unlessClosed :: Context -> Connection -> Stream -> IO () -> IO ()
48−unlessClosed ctx
49− Connection{connSendAll}
60+-- | Run the given action if the stream is not closed; handle any exceptions by
61+-- resetting the stream.
62+unlessClosed :: Connection -> Stream -> IO () -> IO Bool
63+unlessClosed Connection{connSendAll}
5064 strm@Stream{streamState,streamNumber}
5165 body = E.handle resetStream $ do
5266 state <- readIORef streamState
53− unless (isClosed state) body
67+ if (isClosed state) then return False else body >> return True
5468 where
5569 resetStream e = do
56− closed ctx strm (ResetByMe e)
70+ closed strm (ResetByMe e)
5771 let rst = resetFrame InternalError streamNumber
5872 connSendAll rst
73+ return False
5974
6075getWindowSize :: TVar WindowSize -> TVar WindowSize -> IO WindowSize
6176getWindowSize connWindow strmWindow = do
@@ -93,64 +108,98 @@ frameSender ctx@Context{outputQ,connectionWindow}
93108 switch (OFrame frame) = do
94109 connSendAll frame
95110 loop
96− switch (OResponse strm rsp aux) = do
97− unlessClosed ctx conn strm $ do
98− lim <- getWindowSize connectionWindow (streamWindow strm)
99− -- Header frame and Continuation frame
100− let sid = streamNumber strm
101− endOfStream = case aux of
102− Persist{} -> False
103− Oneshot hb -> not hb
104− len <- headerContinue sid rsp endOfStream
105− let total = len + frameHeaderLength
106− case aux of
107− Oneshot True -> do -- hasBody
108− -- Data frame payload
109− (off, _) <- sendHeadersIfNecessary total
110− let payloadOff = off + frameHeaderLength
111− Next datPayloadLen mnext <-
112− fillResponseBodyGetNext conn ii payloadOff lim rsp
113− fillDataHeaderSend strm total datPayloadLen mnext
114− maybeEnqueueNext strm mnext
115− Oneshot False -> do
116− -- "closed" must be before "connSendAll". If not,
117− -- the context would be switched to the receiver,
118− -- resulting the inconsistency of concurrency.
119− closed ctx strm Finished
120− flushN total
121− Persist sq tvar -> do
122− (off, needSend) <- sendHeadersIfNecessary total
123− let payloadOff = off + frameHeaderLength
124− Next datPayloadLen mnext <-
125− fillStreamBodyGetNext conn payloadOff lim sq tvar strm
126− -- If no data was immediately available, avoid sending an
127− -- empty data frame.
128− if datPayloadLen > 0 then
129− fillDataHeaderSend strm total datPayloadLen mnext
130− else
131− when needSend $ flushN off
132− maybeEnqueueNext strm mnext
111+ switch (OResponse strm s h aux) = do
112+ _ <- unlessClosed conn strm $
113+ getWindowSize connectionWindow (streamWindow strm) >>=
114+ sendResponse strm s h aux
133115 loop
134116 switch (ONext strm curr) = do
135− unlessClosed ctx conn strm $ do
117+ _ <- unlessClosed conn strm $ do
136118 lim <- getWindowSize connectionWindow (streamWindow strm)
137119 -- Data frame payload
138120 Next datPayloadLen mnext <- curr lim
139− fillDataHeaderSend strm 0 datPayloadLen mnext
140− maybeEnqueueNext strm mnext
121+ fillDataHeaderSend strm 0 datPayloadLen
122+ dispatchNext strm mnext
123+ loop
124+ switch (OPush oldStrm push mvar strm s h aux) = do
125+ pushed <- unlessClosed conn oldStrm $ do
126+ lim <- getWindowSize connectionWindow (streamWindow strm)
127+ -- Write and send the promise.
128+ builder <- hpackEncodeCIHeaders ctx $ promiseHeaders push
129+ off <- pushContinue (streamNumber oldStrm) (streamNumber strm) builder
130+ flushN $ off + frameHeaderLength
131+ -- TODO(awpr): refactor sendResponse to be able to handle non-zero
132+ -- initial offsets and use that to potentially avoid the extra syscall.
133+ sendResponse strm s h aux lim
134+ putMVar mvar pushed
141135 loop
142136
137+ -- Send the response headers and as much of the response as is immediately
138+ -- available; shared by normal responses and pushed streams.
139+ sendResponse :: Stream -> H.Status -> H.ResponseHeaders -> Aux -> WindowSize -> IO ()
140+ sendResponse strm s h (Persist sq tvar) lim = do
141+ -- Header frame and Continuation frame
142+ let sid = streamNumber strm
143+ builder <- hpackEncodeHeader ctx ii settings s h
144+ len <- headerContinue sid builder False
145+ let total = len + frameHeaderLength
146+ (off, needSend) <- sendHeadersIfNecessary total
147+ let payloadOff = off + frameHeaderLength
148+ Next datPayloadLen mnext <-
149+ fillStreamBodyGetNext ii conn payloadOff lim sq tvar strm
150+ -- If no data was immediately available, avoid sending an
151+ -- empty data frame.
152+ if datPayloadLen > 0 then
153+ fillDataHeaderSend strm total datPayloadLen
154+ else
155+ when needSend $ flushN off
156+ dispatchNext strm mnext
157+
158+ -- Send the stream's trailers and close the stream.
159+ sendTrailers :: Stream -> Trailers -> IO ()
160+ sendTrailers strm trailers = do
161+ -- Trailers always indicate the end of a stream; send them in
162+ -- consecutive header+continuation frames and end the stream. Some
163+ -- clients dislike empty headers frames, so end the stream with an
164+ -- empty data frame instead, as recommended by the spec.
165+ toFlush <- case trailers of
166+ [] -> frameHeaderLength <$ fillFrameHeader FrameData 0
167+ (streamNumber strm)
168+ (setEndStream defaultFlags)
169+ connWriteBuffer
170+ _ -> do
171+ builder <- hpackEncodeCIHeaders ctx trailers
172+ off <- headerContinue (streamNumber strm) builder True
173+ return (off + frameHeaderLength)
174+ -- 'closed' must be before 'flushN'. If not, the context would be
175+ -- switched to the receiver, resulting in the inconsistency of
176+ -- concurrency.
177+ closed strm Finished
178+ flushN toFlush
179+
143180 -- Flush the connection buffer to the socket, where the first 'n' bytes of
144181 -- the buffer are filled.
145182 flushN :: Int -> IO ()
146183 flushN n = bufferIO connWriteBuffer n connSendAll
147184
148− headerContinue sid rsp endOfStream = do
149− builder <- hpackEncodeHeader ctx ii settings rsp
185+ -- A flags value with the end-header flag set iff the argument is B.Done.
186+ maybeEndHeaders B.Done = setEndHeader defaultFlags
187+ maybeEndHeaders _ = defaultFlags
188+
189+ -- Write PUSH_PROMISE and possibly CONTINUATION frames into the connection
190+ -- buffer, using the given builder as their contents; flush them to the
191+ -- socket as necessary.
192+ pushContinue sid newSid builder = do
193+ let builder' = B.int32BE (fromIntegral newSid) <> builder
194+ (len, signal) <- B.runBuilder builder' bufHeaderPayload headerPayloadLim
195+ let flag = maybeEndHeaders signal
196+ fillFrameHeader FramePushPromise len sid flag connWriteBuffer
197+ continue sid len signal
198+
199+ -- Write HEADER and possibly CONTINUATION frames.
200+ headerContinue sid builder endOfStream = do
150201 (len, signal) <- B.runBuilder builder bufHeaderPayload headerPayloadLim
151− let flag0 = case signal of
152− B.Done -> setEndHeader defaultFlags
153− _ -> defaultFlags
202+ let flag0 = maybeEndHeaders signal
154203 flag = if endOfStream then setEndStream flag0 else flag0
155204 fillFrameHeader FrameHeaders len sid flag connWriteBuffer
156205 continue sid len signal
@@ -159,9 +208,7 @@ frameSender ctx@Context{outputQ,connectionWindow}
159208 continue sid len (B.More _ writer) = do
160209 flushN $ len + frameHeaderLength
161210 (len', signal') <- writer bufHeaderPayload headerPayloadLim
162− let flag = case signal' of
163− B.Done -> setEndHeader defaultFlags
164− _ -> defaultFlags
211+ let flag = maybeEndHeaders signal'
165212 fillFrameHeader FrameContinuation len' sid flag connWriteBuffer
166213 continue sid len' signal'
167214 continue sid len (B.Chunk bs writer) = do
@@ -178,16 +225,22 @@ frameSender ctx@Context{outputQ,connectionWindow}
178225 -- True if the connection buffer has room for a 1-byte data frame.
179226 canFitDataFrame total = total + frameHeaderLength < connBufferSize
180227
181− -- Re-enqueue the stream in the output queue if more output is immediately
182− -- available; do nothing otherwise.
183− maybeEnqueueNext :: Stream -> Control DynaNext -> IO ()
184− maybeEnqueueNext strm (CNext next) = do
228+ -- Take the appropriate action based on the given 'Control':
229+ -- - If more output is immediately available, re-enqueue the stream in the
230+ -- output queue.
231+ -- - If the output is over and trailers are available, send them now and
232+ -- end the stream.
233+ -- - If we've drained the queue and handed the stream back to its waiter,
234+ -- do nothing.
235+ --
236+ -- This is done after sending any part of the stream body, so it's shared
237+ -- by 'sendResponse' and @switch (ONext ...)@.
238+ dispatchNext :: Stream -> Control DynaNext -> IO ()
239+ dispatchNext _ CNone = return ()
240+ dispatchNext strm (CFinish trailers) = sendTrailers strm trailers
241+ dispatchNext strm (CNext next) = do
185242 let out = ONext strm next
186243 enqueueOrSpawnTemporaryWaiter strm outputQ out
187− -- If the streaming is not finished, it must already have been
188− -- written to the 'TVar' owned by 'waiter', which will
189− -- put it back into the queue when more output becomes available.
190− maybeEnqueueNext _ _ = return ()
191244
192245
193246 -- Send headers if there is not room for a 1-byte data frame, and return
@@ -199,21 +252,12 @@ frameSender ctx@Context{outputQ,connectionWindow}
199252 flushN total
200253 return (0, False)
201254
202− fillDataHeaderSend strm otherLen datPayloadLen mnext = do
255+ fillDataHeaderSend strm otherLen datPayloadLen = do
203256 -- Data frame header
204257 let sid = streamNumber strm
205258 buf = connWriteBuffer `plusPtr` otherLen
206259 total = otherLen + frameHeaderLength + datPayloadLen
207− flag = case mnext of
208− CFinish -> setEndStream defaultFlags
209− _ -> defaultFlags
210− fillFrameHeader FrameData datPayloadLen sid flag buf
211− -- "closed" must be before "connSendAll". If not,
212− -- the context would be switched to the receiver,
213− -- resulting the inconsistency of concurrency.
214− case mnext of
215− CFinish -> closed ctx strm Finished
216− _ -> return ()
260+ fillFrameHeader FrameData datPayloadLen sid defaultFlags buf
217261 flushN total
218262 atomically $ do
219263 modifyTVar' connectionWindow (subtract datPayloadLen)
@@ -225,130 +269,106 @@ frameSender ctx@Context{outputQ,connectionWindow}
225269
226270----------------------------------------------------------------
227271
228−{-
229−ResponseFile Status ResponseHeaders FilePath (Maybe FilePart)
230−ResponseBuilder Status ResponseHeaders Builder
231−ResponseStream Status ResponseHeaders StreamingBody
232−ResponseRaw (IO ByteString -> (ByteString -> IO ()) -> IO ()) Response
233−-}
234−
235−fillResponseBodyGetNext :: Connection -> InternalInfo -> Int -> WindowSize -> Response -> IO Next
236−fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
237− _ off lim (ResponseBuilder _ _ bb) = do
272+fillStreamBodyGetNext :: InternalInfo -> Connection -> Int -> WindowSize
273+ -> TBQueue Sequence -> TVar Sync -> Stream -> IO Next
274+fillStreamBodyGetNext ii Connection{connWriteBuffer,connBufferSize}
275+ off lim sq tvar strm = do
238276 let datBuf = connWriteBuffer `plusPtr` off
239277 room = min (connBufferSize - off) lim
240− (len, signal) <- B.runBuilder bb datBuf room
241− return $ nextForBuilder connWriteBuffer connBufferSize len signal
278+ (leftover, cont, len) <- runStreamBuilder ii datBuf room sq
279+ nextForStream ii connWriteBuffer connBufferSize sq tvar strm leftover cont len
280+
281+----------------------------------------------------------------
282+
283+runStreamBuilder :: InternalInfo -> Buffer -> BufSize -> TBQueue Sequence
284+ -> IO (Leftover, Maybe Trailers, BytesFilled)
285+runStreamBuilder ii buf0 room0 sq = loop buf0 room0 0
286+ where
287+ loop !buf !room !total = do
288+ mbuilder <- atomically $ tryReadTBQueue sq
289+ case mbuilder of
290+ Nothing -> return (LZero, Nothing, total)
291+ Just (SBuilder builder) -> do
292+ (len, signal) <- B.runBuilder builder buf room
293+ let !total' = total + len
294+ case signal of
295+ B.Done -> loop (buf `plusPtr` len) (room - len) total'
296+ B.More _ writer -> return (LOne writer, Nothing, total')
297+ B.Chunk bs writer -> return (LTwo bs writer, Nothing, total')
298+ Just (SFile path part) -> do
299+ (leftover, len) <- runStreamFile ii buf room path part
300+ let !total' = total + len
301+ return (leftover, Nothing, total')
302+ -- TODO if file part is done, go back to loop
303+ Just SFlush -> return (LZero, Nothing, total)
304+ Just (SFinish trailers) -> return (LZero, Just trailers, total)
305+
306+-- | Open the file and start reading into the send buffer.
307+runStreamFile :: InternalInfo -> Buffer -> BufSize -> FilePath -> FilePart
308+ -> IO (Leftover, BytesFilled)
309+
310+-- | Read the given (OS-specific) file representation into the buffer. On
311+-- non-Windows systems this uses pread; on Windows this ignores the position
312+-- because we use the Handle's internal read position instead (because it's not
313+-- potentially shared with other readers).
314+readOpenFile :: OpenFile -> Buffer -> BufSize -> Integer -> IO Int
242315
243316#ifdef WINDOWS
244−fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
245− _ off lim (ResponseFile _ _ path mpart) = do
246− let datBuf = connWriteBuffer `plusPtr` off
247− room = min (connBufferSize - off) lim
248− (start, bytes) <- fileStartEnd path mpart
317+runStreamFile _ buf room path part = do
318+ let start = filePartOffset part
319+ bytes = filePartByteCount part
249320 -- fixme: how to close Handle? GC does it at this moment.
250321 h <- IO.openBinaryFile path IO.ReadMode
251322 IO.hSeek h IO.AbsoluteSeek start
252− len <- IO.hGetBufSome h datBuf (mini room bytes)
253− let bytes' = bytes - fromIntegral len
254− return $ nextForFile len connWriteBuffer connBufferSize h bytes' (return ())
323+ fillBufFile buf room h start bytes (return ())
324+
325+readOpenFile h buf room _ = IO.hGetBufSome h buf room
255326#else
256−fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
257− ii off lim (ResponseFile _ _ path mpart) = do
327+runStreamFile ii buf room path part = do
328+ let start = filePartOffset part
329+ bytes = filePartByteCount part
258330 (fd, refresh) <- case fdCacher ii of
259331 Just fdcache -> getFd fdcache path
260332 Nothing -> do
261333 fd' <- openFd path ReadOnly Nothing defaultFileFlags{nonBlock=True}
262334 th <- T.register (timeoutManager ii) (closeFd fd')
263335 return (fd', T.tickle th)
264− let datBuf = connWriteBuffer `plusPtr` off
265− room = min (connBufferSize - off) lim
266− (start, bytes) <- fileStartEnd path mpart
267− len <- positionRead fd datBuf (mini room bytes) start
268− refresh
269− let len' = fromIntegral len
270− return $ nextForFile len connWriteBuffer connBufferSize fd (start + len') (bytes - len') refresh
271−#endif
272−
273−fillResponseBodyGetNext _ _ _ _ _ = error "fillResponseBodyGetNext"
336+ fillBufFile buf room fd start bytes refresh
274337
275−fileStartEnd :: FilePath -> Maybe FilePart -> IO (Integer, Integer)
276−fileStartEnd path Nothing = do
277− end <- fromIntegral . P.fileSize <$> P.getFileStatus path
278− return (0, end)
279−fileStartEnd _ (Just part) =
280− return (filePartOffset part, filePartByteCount part)
281−
282−----------------------------------------------------------------
283−
284−fillStreamBodyGetNext :: Connection -> Int -> WindowSize -> TBQueue Sequence -> TVar Sync -> Stream -> IO Next
285−fillStreamBodyGetNext Connection{connWriteBuffer,connBufferSize}
286− off lim sq tvar strm = do
287− let datBuf = connWriteBuffer `plusPtr` off
288− room = min (connBufferSize - off) lim
289− (leftover, cont, len) <- runStreamBuilder datBuf room sq
290− nextForStream connWriteBuffer connBufferSize sq tvar strm leftover cont len
291−
292−----------------------------------------------------------------
293−
294−fillBufBuilder :: Buffer -> BufSize -> Leftover -> DynaNext
295−fillBufBuilder buf0 siz0 leftover lim = do
296− let payloadBuf = buf0 `plusPtr` frameHeaderLength
297− room = min (siz0 - frameHeaderLength) lim
298− case leftover of
299− LZero -> error "fillBufBuilder: LZero"
300− LOne writer -> do
301− (len, signal) <- writer payloadBuf room
302− getNext len signal
303− LTwo bs writer
304− | BS.length bs <= room -> do
305− buf1 <- copy payloadBuf bs
306− let len1 = BS.length bs
307− (len2, signal) <- writer buf1 (room - len1)
308− getNext (len1 + len2) signal
309− | otherwise -> do
310− let (bs1,bs2) = BS.splitAt room bs
311− void $ copy payloadBuf bs1
312− getNext room (B.Chunk bs2 writer)
313− where
314− getNext l s = return $ nextForBuilder buf0 siz0 l s
315−
316−nextForBuilder :: Buffer -> BufSize -> BytesFilled -> B.Next -> Next
317−nextForBuilder _ _ len B.Done
318− = Next len CFinish
319−nextForBuilder buf siz len (B.More _ writer)
320− = Next len (CNext (fillBufBuilder buf siz (LOne writer)))
321−nextForBuilder buf siz len (B.Chunk bs writer)
322− = Next len (CNext (fillBufBuilder buf siz (LTwo bs writer)))
338+readOpenFile = positionRead
339+#endif
323340
324−----------------------------------------------------------------
341+-- | Read as much of the file as is currently available into the buffer, then
342+-- return a 'Leftover' to indicate whether this file chunk has more data to
343+-- send. If this read hit the end of the file range, return 'LZero'; otherwise
344+-- return 'LFile' so this stream will continue reading from the file the next
345+-- time it's pulled from the queue.
346+fillBufFile :: Buffer -> BufSize -> OpenFile -> Integer -> Integer -> (IO ())
347+ -> IO (Leftover, BytesFilled)
348+fillBufFile buf room f start bytes refresh = do
349+ len <- readOpenFile f buf (mini room bytes) start
350+ refresh
351+ let len' = fromIntegral len
352+ leftover = if bytes > len' then
353+ LFile f (start + len') (bytes - len') refresh
354+ else
355+ LZero
356+ return (leftover, len)
325357
326−runStreamBuilder :: Buffer -> BufSize -> TBQueue Sequence
327− -> IO (Leftover, Bool, BytesFilled)
328−runStreamBuilder buf0 room0 sq = loop buf0 room0 0
329− where
330− loop !buf !room !total = do
331− mbuilder <- atomically $ tryReadTBQueue sq
332− case mbuilder of
333− Nothing -> return (LZero, True, total)
334− Just (SBuilder builder) -> do
335− (len, signal) <- B.runBuilder builder buf room
336− let !total' = total + len
337− case signal of
338− B.Done -> loop (buf `plusPtr` len) (room - len) total'
339− B.More _ writer -> return (LOne writer, True, total')
340− B.Chunk bs writer -> return (LTwo bs writer, True, total')
341− Just SFlush -> return (LZero, True, total)
342− Just SFinish -> return (LZero, False, total)
358+mini :: Int -> Integer -> Int
359+mini i n
360+ | fromIntegral i < n = i
361+ | otherwise = fromIntegral n
343362
344−fillBufStream :: Buffer -> BufSize -> Leftover -> TBQueue Sequence -> TVar Sync -> Stream -> DynaNext
345−fillBufStream buf0 siz0 leftover0 sq tvar strm lim0 = do
363+fillBufStream :: InternalInfo -> Buffer -> BufSize -> Leftover
364+ -> TBQueue Sequence -> TVar Sync -> Stream -> DynaNext
365+fillBufStream ii buf0 siz0 leftover0 sq tvar strm lim0 = do
346366 let payloadBuf = buf0 `plusPtr` frameHeaderLength
347367 room0 = min (siz0 - frameHeaderLength) lim0
348368 case leftover0 of
349369 LZero -> do
350− (leftover, cont, len) <- runStreamBuilder payloadBuf room0 sq
351− getNext leftover cont len
370+ (leftover, end, len) <- runStreamBuilder ii payloadBuf room0 sq
371+ getNext leftover end len
352372 LOne writer -> write writer payloadBuf room0 0
353373 LTwo bs writer
354374 | BS.length bs <= room0 -> do
@@ -358,71 +378,35 @@ fillBufStream buf0 siz0 leftover0 sq tvar strm lim0 = do
358378 | otherwise -> do
359379 let (bs1,bs2) = BS.splitAt room0 bs
360380 void $ copy payloadBuf bs1
361− getNext (LTwo bs2 writer) True room0
381+ getNext (LTwo bs2 writer) Nothing room0
382+ LFile fd start bytes refresh -> do
383+ (leftover, len) <- fillBufFile payloadBuf room0 fd start bytes refresh
384+ getNext leftover Nothing len
362385 where
363− getNext = nextForStream buf0 siz0 sq tvar strm
386+ getNext = nextForStream ii buf0 siz0 sq tvar strm
364387 write writer1 buf room sofar = do
365388 (len, signal) <- writer1 buf room
366389 case signal of
367390 B.Done -> do
368− (leftover, cont, extra) <- runStreamBuilder (buf `plusPtr` len) (room - len) sq
391+ (leftover, end, extra) <- runStreamBuilder ii (buf `plusPtr` len) (room - len) sq
369392 let !total = sofar + len + extra
370− getNext leftover cont total
393+ getNext leftover end total
371394 B.More _ writer -> do
372395 let !total = sofar + len
373− getNext (LOne writer) True total
396+ getNext (LOne writer) Nothing total
374397 B.Chunk bs writer -> do
375398 let !total = sofar + len
376− getNext (LTwo bs writer) True total
377−
378−nextForStream :: Buffer -> BufSize -> TBQueue Sequence -> TVar Sync -> Stream
379− -> Leftover -> Bool -> BytesFilled
380− -> IO Next
381−nextForStream _ _ _ tvar _ _ False len = do
382− atomically $ writeTVar tvar SyncFinish
383− return $ Next len CFinish
384−nextForStream buf siz sq tvar strm LZero True len = do
385− let out = ONext strm (fillBufStream buf siz LZero sq tvar strm)
399+ getNext (LTwo bs writer) Nothing total
400+
401+nextForStream :: InternalInfo -> Buffer -> BufSize -> TBQueue Sequence
402+ -> TVar Sync -> Stream -> Leftover -> Maybe Trailers
403+ -> BytesFilled -> IO Next
404+nextForStream _ _ _ _ tvar _ _ (Just trailers) len = do
405+ atomically $ writeTVar tvar $ SyncFinish
406+ return $ Next len $ CFinish trailers
407+nextForStream ii buf siz sq tvar strm LZero Nothing len = do
408+ let out = ONext strm (fillBufStream ii buf siz LZero sq tvar strm)
386409 atomically $ writeTVar tvar $ SyncNext out
387410 return $ Next len CNone
388−nextForStream buf siz sq tvar strm leftover True len =
389− return $ Next len (CNext (fillBufStream buf siz leftover sq tvar strm))
390−
391−----------------------------------------------------------------
392−
393−#ifdef WINDOWS
394−fillBufFile :: Buffer -> BufSize -> IO.Handle -> Integer -> IO () -> DynaNext
395−fillBufFile buf siz h bytes refresh lim = do
396− let payloadBuf = buf `plusPtr` frameHeaderLength
397− room = min (siz - frameHeaderLength) lim
398− len <- IO.hGetBufSome h payloadBuf room
399− refresh
400− let bytes' = bytes - fromIntegral len
401− return $ nextForFile len buf siz h bytes' refresh
402−
403−nextForFile :: BytesFilled -> Buffer -> BufSize -> IO.Handle -> Integer -> IO () -> Next
404−nextForFile 0 _ _ _ _ _ = Next 0 CFinish
405−nextForFile len _ _ _ 0 _ = Next len CFinish
406−nextForFile len buf siz h bytes refresh =
407− Next len (CNext (fillBufFile buf siz h bytes refresh))
408−#else
409−fillBufFile :: Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> DynaNext
410−fillBufFile buf siz fd start bytes refresh lim = do
411− let payloadBuf = buf `plusPtr` frameHeaderLength
412− room = min (siz - frameHeaderLength) lim
413− len <- positionRead fd payloadBuf (mini room bytes) start
414− let len' = fromIntegral len
415− refresh
416− return $ nextForFile len buf siz fd (start + len') (bytes - len') refresh
417−
418−nextForFile :: BytesFilled -> Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> Next
419−nextForFile 0 _ _ _ _ _ _ = Next 0 CFinish
420−nextForFile len _ _ _ _ 0 _ = Next len CFinish
421−nextForFile len buf siz fd start bytes refresh =
422− Next len (CNext (fillBufFile buf siz fd start bytes refresh))
423−#endif
424−
425−mini :: Int -> Integer -> Int
426−mini i n
427− | fromIntegral i < n = i
428− | otherwise = fromIntegral n
411+nextForStream ii buf siz sq tvar strm leftover Nothing len =
412+ return $ Next len (CNext (fillBufStream ii buf siz leftover sq tvar strm))
warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+76−26
@@ -5,9 +5,10 @@ module Network.Wai.Handler.Warp.HTTP2.Types where
55
66import Data.ByteString.Builder (Builder)
77#if __GLASGOW_HASKELL__ < 709
8−import Control.Applicative ((<$>),(<*>))
8+import Control.Applicative ((<$>), (<*>), pure)
99#endif
1010import Control.Concurrent (forkIO)
11+import Control.Concurrent.MVar (MVar)
1112import Control.Concurrent.STM
1213import Control.Exception (SomeException)
1314import Control.Monad (void)
@@ -17,7 +18,8 @@ import qualified Data.ByteString as BS
1718import Data.IntMap.Strict (IntMap, IntMap)
1819import qualified Data.IntMap.Strict as M
1920import qualified Network.HTTP.Types as H
20−import Network.Wai (Request, Response)
21+import Network.Wai (Request, FilePart)
22+import Network.Wai.HTTP2 (PushPromise, Trailers)
2123import Network.Wai.Handler.Warp.IORef
2224import Network.Wai.Handler.Warp.Types
2325
@@ -36,7 +38,7 @@ isHTTP2 tls = useHTTP2
3638 where
3739 useHTTP2 = case tlsNegotiatedProtocol tls of
3840 Nothing -> False
39− Just proto -> "h2-" `BS.isPrefixOf` proto
41+ Just proto -> "h2-" `BS.isPrefixOf` proto || proto == "h2"
4042
4143----------------------------------------------------------------
4244
@@ -44,14 +46,21 @@ data Input = Input Stream Request
4446
4547----------------------------------------------------------------
4648
47−data Control a = CFinish
49+-- | The result of writing data from a stream's queue into the buffer.
50+data Control a = CFinish Trailers
51+ -- ^ The stream has ended, and the trailers should be sent.
4852 | CNext a
53+ -- ^ The stream has more data immediately available, and we
54+ -- should re-enqueue it when the stream window becomes open.
4955 | CNone
56+ -- ^ The stream queue has been drained and we've handed it off
57+ -- to its dedicated waiter thread, which will re-enqueue it when
58+ -- more data is available.
5059
5160instance Show (Control a) where
52− show CFinish = "CFinish"
53− show (CNext _) = "CNext"
54− show CNone = "CNone"
61+ show (CFinish _) = "CFinish"
62+ show (CNext _) = "CNext"
63+ show CNone = "CNone"
5564
5665type DynaNext = WindowSize -> IO Next
5766
@@ -60,28 +69,52 @@ type BytesFilled = Int
6069data Next = Next BytesFilled (Control DynaNext)
6170
6271data Output = OFinish
72+ -- ^ Terminate the connection.
6373 | OGoaway ByteString
74+ -- ^ Send a goaway frame and terminate the connection.
6475 | OFrame ByteString
65− | OResponse Stream Response Aux
76+ -- ^ Send an entire pre-encoded frame.
77+ | OResponse Stream H.Status H.ResponseHeaders Aux
78+ -- ^ Send the headers and as much of the response as is immediately
79+ -- available.
80+ | OPush Stream PushPromise (MVar Bool) Stream H.Status H.ResponseHeaders Aux
81+ -- ^ Send a PUSH_PROMISE frame, then act like OResponse; signal the
82+ -- MVar whether the promise has been sent.
6683 | ONext Stream DynaNext
84+ -- ^ Send a chunk of the response.
6785
6886outputStream :: Output -> Stream
69−outputStream (OResponse strm _ _) = strm
70−outputStream (ONext strm _) = strm
71−outputStream _ = error "outputStream"
87+outputStream (OResponse strm _ _ _) = strm
88+outputStream (ONext strm _) = strm
89+outputStream (OPush strm _ _ _ _ _ _) = strm
90+outputStream _ = error "outputStream"
7291
7392----------------------------------------------------------------
7493
75−data Sequence = SFinish
94+-- | An element on the queue between a running stream and the sender; the order
95+-- should consist of any number of 'SFile', 'SBuilder', and 'SFlush', followed
96+-- by a single 'SFinish'.
97+data Sequence = SFinish Trailers
98+ -- ^ The stream is over; its trailers are provided.
7699 | SFlush
100+ -- ^ Any buffered data should be sent immediately.
77101 | SBuilder Builder
102+ -- ^ Append a chunk of data to the stream.
103+ | SFile FilePath FilePart
104+ -- ^ Append a chunk of a file's contents to the stream.
78105
106+-- | A message from the sender to a stream's dedicated waiter thread.
79107data Sync = SyncNone
108+ -- ^ Nothing interesting has happened. Go back to sleep.
80109 | SyncFinish
110+ -- ^ The stream has ended.
81111 | SyncNext Output
112+ -- ^ The stream's queue has been drained; wait for more to be
113+ -- available and re-enqueue the given 'Output'.
82114
83−data Aux = Oneshot Bool
84− | Persist (TBQueue Sequence) (TVar Sync)
115+-- | Auxiliary information needed to communicate with a running stream: a queue
116+-- of stream elements ('Sequence') and a 'TVar' connected to its waiter thread.
117+data Aux = Persist (TBQueue Sequence) (TVar Sync)
85118
86119----------------------------------------------------------------
87120
@@ -89,13 +122,21 @@ data Aux = Oneshot Bool
89122data Context = Context {
90123 http2settings :: IORef Settings
91124 , streamTable :: StreamTable
125+ -- | Number of active streams initiated by the client; for enforcing our own
126+ -- max concurrency setting.
92127 , concurrency :: IORef Int
128+ -- | Number of active streams initiated by the server; for respecting the
129+ -- client's max concurrency setting.
130+ , pushConcurrency :: IORef Int
93131 -- | RFC 7540 says "Other frames (from any stream) MUST NOT
94132 -- occur between the HEADERS frame and any CONTINUATION
95133 -- frames that might follow". This field is used to implement
96134 -- this requirement.
97135 , continued :: IORef (Maybe StreamId)
98136 , currentStreamId :: IORef StreamId
137+ -- ^ Last client-initiated stream ID we've handled.
138+ , nextPushStreamId :: IORef StreamId
139+ -- ^ Next available server-initiated stream ID.
99140 , inputQ :: TQueue Input
100141 , outputQ :: PriorityTree Output
101142 , encodeDynamicTable :: IORef DynamicTable
@@ -109,8 +150,10 @@ newContext :: IO Context
109150newContext = Context <$> newIORef defaultSettings
110151 <*> initialize 10 -- fixme: hard coding: 10
111152 <*> newIORef 0
153+ <*> newIORef 0
112154 <*> newIORef Nothing
113155 <*> newIORef 0
156+ <*> newIORef 2 -- first server push stream; 0 is reserved
114157 <*> newTQueueIO
115158 <*> newPriorityTree
116159 <*> (newDynamicTableForEncoding defaultDynamicTableSize >>= newIORef)
@@ -177,28 +220,35 @@ data Stream = Stream {
177220 , streamBodyLength :: IORef Int
178221 , streamWindow :: TVar WindowSize
179222 , streamPriority :: IORef Priority
223+ -- | The concurrency IORef in which this stream has been counted. The client
224+ -- and server each have separate concurrency values to respect, so pushed
225+ -- streams need to decrement a different count when they're closed. This
226+ -- should be either @concurrency ctx@ or @pushConcurrency ctx@.
227+ , concurrencyRef :: IORef Int
180228 }
181229
182230instance Show Stream where
183231 show s = show (streamNumber s)
184232
185−newStream :: StreamId -> WindowSize -> IO Stream
186−newStream sid win = Stream sid <$> newIORef Idle
187− <*> newIORef Nothing
188− <*> newIORef 0
189− <*> newTVarIO win
190− <*> newIORef defaultPriority
233+newStream :: IORef Int -> StreamId -> WindowSize -> IO Stream
234+newStream ref sid win =
235+ Stream sid <$> newIORef Idle
236+ <*> newIORef Nothing
237+ <*> newIORef 0
238+ <*> newTVarIO win
239+ <*> newIORef defaultPriority
240+ <*> pure ref
191241
192242----------------------------------------------------------------
193243
194−opened :: Context -> Stream -> IO ()
195−opened Context{concurrency} Stream{streamState} = do
196− atomicModifyIORef' concurrency (\x -> (x+1,()))
244+opened :: Stream -> IO ()
245+opened Stream{concurrencyRef,streamState} = do
246+ atomicModifyIORef' concurrencyRef (\x -> (x+1,()))
197247 writeIORef streamState (Open JustOpened)
198248
199−closed :: Context -> Stream -> ClosedCode -> IO ()
200−closed Context{concurrency} Stream{streamState} cc = do
201− atomicModifyIORef' concurrency (\x -> (x-1,()))
249+closed :: Stream -> ClosedCode -> IO ()
250+closed Stream{concurrencyRef,streamState} cc = do
251+ atomicModifyIORef' concurrencyRef (\x -> (x-1,()))
202252 writeIORef streamState (Closed cc)
203253
204254----------------------------------------------------------------
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+150−60
@@ -1,10 +1,11 @@
11{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE RankNTypes #-}
23{-# LANGUAGE NamedFieldPuns #-}
34{-# LANGUAGE PatternGuards #-}
45{-# LANGUAGE CPP #-}
56
67module Network.Wai.Handler.Warp.HTTP2.Worker (
7− Responder
8+ Respond
89 , response
910 , worker
1011 ) where
@@ -26,65 +27,152 @@ import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
2627import Network.Wai.Handler.Warp.HTTP2.Manager
2728import Network.Wai.Handler.Warp.HTTP2.Types
2829import Network.Wai.Handler.Warp.IORef
29−import qualified Network.Wai.Handler.Warp.Response as R
30+import Network.Wai.HTTP2
31+ ( Body
32+ , Chunk(..)
33+ , HTTP2Application
34+ , PushPromise
35+ , Responder
36+ )
3037import qualified Network.Wai.Handler.Warp.Settings as S
3138import qualified Network.Wai.Handler.Warp.Timeout as T
32−import Network.Wai.Internal (Response(..), ResponseReceived(..), ResponseReceived(..))
3339
3440----------------------------------------------------------------
3541
36−-- | The wai definition is 'type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived'.
37−-- This type implements the second argument (Response -> IO ResponseReceived)
38−-- with extra arguments.
39−type Responder = ThreadContinue -> T.Handle -> Stream -> Request ->
40− Response -> IO ResponseReceived
42+-- | An 'HTTP2Application' takes a function of status, headers, and body; this
43+-- type implements that by currying some internal arguments.
44+--
45+-- This is the argument to a 'Responder'.
46+type Respond = forall a. IO () -> Stream -> TBQueue Sequence
47+ -> H.Status -> H.ResponseHeaders -> Body a -> IO a
4148
4249-- | This function is passed to workers.
43−-- They also pass 'Response's from 'Application's to this function.
50+-- They also pass responses from 'HTTP2Application's to this function.
4451-- This function enqueues commands for the HTTP/2 sender.
45−response :: Context -> Manager -> Responder
46−response Context{outputQ} mgr tconf th strm req rsp = do
47− case rsp of
48− ResponseStream _ _ strmbdy -> do
49− -- We must not exit this WAI application.
50− -- If the application exits, streaming would be also closed.
51− -- So, this work occupies this thread.
52− --
53− -- We need to increase the number of workers.
54− myThreadId >>= replaceWithAction mgr
55− -- After this work, this thread stops to decease
56− -- the number of workers.
57− setThreadContinue tconf False
58− -- Since 'StreamingBody' is loop, we cannot control it.
59− -- So, let's serialize 'Builder' with a designated queue.
60− sq <- newTBQueueIO 10 -- fixme: hard coding: 10
61− tvar <- newTVarIO SyncNone
62− let out = OResponse strm rsp (Persist sq tvar)
63− -- Since we must not enqueue an empty queue to the priority
64− -- queue, we spawn a thread to ensure that the designated
65− -- queue is not empty.
66− void $ forkIO $ waiter tvar sq outputQ
67− atomically $ writeTVar tvar (SyncNext out)
68− let push b = do
69− atomically $ writeTBQueue sq (SBuilder b)
70− T.tickle th
71− flush = atomically $ writeTBQueue sq SFlush
72− strmbdy push flush
73− atomically $ writeTBQueue sq SFinish
74− _ -> do
75− setThreadContinue tconf True
76− let hasBody = requestMethod req /= H.methodHead
77− && R.hasBody (responseStatus rsp)
78− out = OResponse strm rsp (Oneshot hasBody)
79− enqueueOrSpawnTemporaryWaiter strm outputQ out
80− return ResponseReceived
52+response :: Context -> Manager -> ThreadContinue -> Respond
53+response ctx mgr tconf tickle strm sq s h strmbdy = do
54+ -- TODO(awpr) HEAD requests will still stream.
55+
56+ -- We must not exit this WAI application.
57+ -- If the application exits, streaming would be also closed.
58+ -- So, this work occupies this thread.
59+ --
60+ -- We need to increase the number of workers.
61+ myThreadId >>= replaceWithAction mgr
62+ -- After this work, this thread stops to decrease the number of workers.
63+ setThreadContinue tconf False
64+
65+ runStream ctx OResponse tickle strm sq s h strmbdy
66+
67+-- | Set up a waiter thread and run the stream body with functions to enqueue
68+-- 'Sequence's on the stream's queue.
69+runStream :: Context
70+ -> (Stream -> H.Status -> H.ResponseHeaders -> Aux -> Output)
71+ -> Respond
72+runStream Context{outputQ} mkOutput tickle strm sq s h strmbdy = do
73+ tvar <- newTVarIO SyncNone
74+ let out = mkOutput strm s h (Persist sq tvar)
75+ -- Since we must not enqueue an empty queue to the priority
76+ -- queue, we spawn a thread to ensure that the designated
77+ -- queue is not empty.
78+ void $ forkIO $ waiter tvar sq strm outputQ
79+ atomically $ writeTVar tvar (SyncNext out)
80+ let write chunk = do
81+ atomically $ writeTBQueue sq $ case chunk of
82+ BuilderChunk b -> SBuilder b
83+ FileChunk path part -> SFile path part
84+ tickle
85+ flush = atomically $ writeTBQueue sq SFlush
86+ strmbdy write flush
87+
88+-- | Set up a queue for the stream and use the given 'Respond' to actually run
89+-- it. This will be 'response' for client-initiated streams and just plain
90+-- 'runStream' for pushed streams.
91+runResponder :: Responder -> Respond -> IO () -> Stream -> IO ()
92+runResponder responder respond tickle strm = do
93+ -- Since 'Body' is loop, we cannot control it.
94+ -- So, let's serialize 'Builder' with a designated queue.
95+ sq <- newTBQueueIO 10 -- fixme: hard coding: 10
96+ trailers <- responder $ respond tickle strm sq
97+ atomically $ writeTBQueue sq $ SFinish trailers
98+
99+-- | Handle abnormal termination of a stream: mark it as closed, send a reset
100+-- frame, and call the user's 'settingsOnException' handler if applicable.
101+cleanupStream :: Context -> S.Settings -> Stream -> Maybe Request -> Maybe SomeException -> IO ()
102+cleanupStream Context{outputQ} set strm req me = do
103+ closed strm Killed
104+ let frame = resetFrame InternalError (streamNumber strm)
105+ enqueue outputQ (OFrame frame) highestPriority
106+ case me of
107+ Nothing -> return ()
108+ Just e -> S.settingsOnException set req e
109+
110+-- | Push the given 'Responder' to the client if the settings allow it
111+-- (specifically 'enablePush' and 'maxConcurrentStreams'). Returns 'True' if
112+-- the stream was actually pushed.
113+--
114+-- This is the push function given to an 'HTTP2Application'.
115+pushResponder :: Context -> S.Settings -> Stream -> PushPromise -> Responder -> IO Bool
116+pushResponder ctx set strm promise responder = do
117+ let Context{ http2settings
118+ , pushConcurrency
119+ } = ctx
120+ cnt <- readIORef pushConcurrency
121+ settings <- readIORef http2settings
122+ let enabled = enablePush settings
123+ fits = maybe True (cnt <) $ maxConcurrentStreams settings
124+ canPush = fits && enabled
125+ if canPush then
126+ actuallyPushResponder ctx set strm promise responder
127+ else
128+ return False
129+
130+-- | Set up a pushed stream and run the 'Responder' in its own thread. Waits
131+-- for the sender thread to handle the push request. This can fail to push the
132+-- stream and return 'False' if the sender dequeued the push request after the
133+-- associated stream was closed.
134+actuallyPushResponder :: Context -> S.Settings -> Stream -> PushPromise -> Responder -> IO Bool
135+actuallyPushResponder ctx set strm promise responder = do
136+ let Context{ http2settings
137+ , nextPushStreamId
138+ , pushConcurrency
139+ , streamTable
140+ } = ctx
141+ -- Claim the next outgoing stream.
142+ newSid <- atomicModifyIORef nextPushStreamId $ \sid -> (sid+2, sid)
143+ ws <- initialWindowSize <$> readIORef http2settings
144+
145+ newStrm <- newStream pushConcurrency newSid ws
146+ writeIORef (streamPriority newStrm) $
147+ Priority False (streamNumber strm) 16
148+ opened newStrm
149+ insert streamTable newSid newStrm
150+
151+ -- Set up a channel for the sender to report back whether it pushed the
152+ -- stream.
153+ mvar <- newEmptyMVar
154+
155+ let mkOutput = OPush strm promise mvar
156+ tickle = return ()
157+ respond = runStream ctx mkOutput
158+
159+ -- TODO(awpr): synthesize a Request for 'settingsOnException'?
160+ _ <- forkIO $ runResponder responder respond tickle newStrm `E.catch`
161+ (cleanupStream ctx set strm Nothing . Just)
162+
163+ takeMVar mvar
81164
82165data Break = Break deriving (Show, Typeable)
83166
84167instance Exception Break
85168
86−worker :: Context -> S.Settings -> T.Manager -> Application -> Responder -> IO ()
87−worker ctx@Context{inputQ,outputQ} set tm app responder = do
169+worker :: Context
170+ -> S.Settings
171+ -> T.Manager
172+ -> HTTP2Application
173+ -> (ThreadContinue -> Respond)
174+ -> IO ()
175+worker ctx@Context{inputQ} set tm app respond = do
88176 tid <- myThreadId
89177 sinfo <- newStreamInfo
90178 tcont <- newThreadContinue
@@ -93,15 +181,17 @@ worker ctx@Context{inputQ,outputQ} set tm app responder = do
93181 where
94182 go sinfo tcont th = do
95183 setThreadContinue tcont True
184+
96185 ex <- E.try $ do
97186 T.pause th
98187 Input strm req <- atomically $ readTQueue inputQ
99188 setStreamInfo sinfo strm req
100189 T.resume th
101190 T.tickle th
102− app req $ responder tcont th strm req
191+ let responder = app req $ pushResponder ctx set strm
192+ runResponder responder (respond tcont) (T.tickle th) strm
103193 cont1 <- case ex of
104− Right ResponseReceived -> return True
194+ Right () -> return True
105195 Left e@(SomeException _)
106196 | Just Break <- E.fromException e -> do
107197 cleanup sinfo Nothing
@@ -120,16 +210,16 @@ worker ctx@Context{inputQ,outputQ} set tm app responder = do
120210 case m of
121211 Nothing -> return ()
122212 Just (strm,req) -> do
123− closed ctx strm Killed
124− let frame = resetFrame InternalError (streamNumber strm)
125− enqueue outputQ (OFrame frame) highestPriority
126− case me of
127− Nothing -> return ()
128− Just e -> S.settingsOnException set (Just req) e
213+ cleanupStream ctx set strm (Just req) me
129214 clearStreamInfo sinfo
130215
131−waiter :: TVar Sync -> TBQueue Sequence -> PriorityTree Output -> IO ()
132−waiter tvar sq outQ = do
216+-- | A dedicated waiter thread to re-enqueue the stream in the priority tree
217+-- whenever output becomes available. When the sender drains the queue and
218+-- moves on to another stream, it drops a message in the 'TVar', and this
219+-- thread wakes up, waits for more output to become available, and re-enqueues
220+-- the stream.
221+waiter :: TVar Sync -> TBQueue Sequence -> Stream -> PriorityTree Output -> IO ()
222+waiter tvar sq strm outQ = do
133223 -- waiting for actions other than SyncNone
134224 mx <- atomically $ do
135225 mout <- readTVar tvar
@@ -140,7 +230,7 @@ waiter tvar sq outQ = do
140230 return $ Just out
141231 SyncFinish -> return Nothing
142232 case mx of
143− Nothing -> return ()
233+ Nothing -> return ()
144234 Just out -> do
145235 -- ensuring that the streaming queue is not empty.
146236 atomically $ do
@@ -148,14 +238,14 @@ waiter tvar sq outQ = do
148238 when isEmpty retry
149239 -- ensuring that stream window is greater than 0.
150240 enqueueWhenWindowIsOpen outQ out
151− waiter tvar sq outQ
241+ waiter tvar sq strm outQ
152242
153243----------------------------------------------------------------
154244
155245-- | It would nice if responders could return values to workers.
156246-- Unfortunately, 'ResponseReceived' is already defined in WAI 2.0.
157247-- It is not wise to change this type.
158−-- So, a reference is shared by a responder and its worker.
248+-- So, a reference is shared by a 'Respond' and its worker.
159249-- The reference refers a value of this type as a return value.
160250-- If 'True', the worker continue to serve requests.
161251-- Otherwise, the worker get finished.
warp/Network/Wai/Handler/Warp/Internal.hs+11
@@ -8,7 +8,18 @@ module Network.Wai.Handler.Warp.Internal (
88 , runSettingsConnection
99 , runSettingsConnectionMaker
1010 , runSettingsConnectionMakerSecure
11+ , runServe
12+ , runServeEnv
13+ , runServeSettings
14+ , runServeSettingsSocket
15+ , runServeSettingsConnection
16+ , runServeSettingsConnectionMaker
17+ , runServeSettingsConnectionMakerSecure
1118 , Transport (..)
19+ -- * ServeConnection
20+ , ServeConnection
21+ , serveDefault
22+ , serveHTTP2
1223 -- * Connection
1324 , Connection (..)
1425 , socketConnection
warp/Network/Wai/Handler/Warp/RequestHeader.hs+2−28
@@ -9,7 +9,7 @@ module Network.Wai.Handler.Warp.RequestHeader (
99import Control.Exception (throwIO)
1010import Control.Monad (when)
1111import qualified Data.ByteString as S
12−import qualified Data.ByteString.Char8 as B (unpack, readInteger)
12+import qualified Data.ByteString.Char8 as B (unpack)
1313import Data.ByteString.Internal (ByteString(..), memchr)
1414import qualified Data.CaseInsensitive as CI
1515import Data.Word (Word8)
@@ -18,7 +18,7 @@ import Foreign.Ptr (Ptr, plusPtr, minusPtr, nullPtr)
1818import Foreign.Storable (peek)
1919import qualified Network.HTTP.Types as H
2020import Network.Wai.Handler.Warp.Types
21−import qualified Network.HTTP.Types.Header as HH
21+import Network.Wai.Internal (parseByteRanges)
2222-- $setup
2323-- >>> :set -XOverloadedStrings
2424
@@ -133,29 +133,3 @@ parseHeader s =
133133 let (k, rest) = S.break (== 58) s -- ':'
134134 rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
135135 in (CI.mk k, rest')
136−
137−parseByteRanges :: S.ByteString -> Maybe HH.ByteRanges
138−parseByteRanges bs1 = do
139− bs2 <- stripPrefix "bytes=" bs1
140− (r, bs3) <- range bs2
141− ranges (r:) bs3
142− where
143− range bs2 = do
144− (i, bs3) <- B.readInteger bs2
145− if i < 0 -- has prefix "-" ("-0" is not valid, but here treated as "0-")
146− then Just (HH.ByteRangeSuffix (negate i), bs3)
147− else do
148− bs4 <- stripPrefix "-" bs3
149− case B.readInteger bs4 of
150− Just (j, bs5) | j >= i -> Just (HH.ByteRangeFromTo i j, bs5)
151− _ -> Just (HH.ByteRangeFrom i, bs4)
152− ranges front bs3
153− | S.null bs3 = Just (front [])
154− | otherwise = do
155− bs4 <- stripPrefix "," bs3
156− (r, bs5) <- range bs4
157− ranges (front . (r:)) bs5
158−
159− stripPrefix x y
160− | x `S.isPrefixOf` y = Just (S.drop (S.length x) y)
161− | otherwise = Nothing
warp/Network/Wai/Handler/Warp/Response.hs+10−60
@@ -33,7 +33,7 @@ import qualified Data.ByteString.Char8 as B (pack)
3333import qualified Data.CaseInsensitive as CI
3434import Data.Function (on)
3535import Data.List (deleteBy)
36−import Data.Maybe (isJust, listToMaybe)
36+import Data.Maybe (isJust, fromMaybe)
3737#if MIN_VERSION_base(4,5,0)
3838# if __GLASGOW_HASKELL__ < 709
3939import Data.Monoid (mempty)
@@ -51,14 +51,11 @@ import qualified Network.Wai.Handler.Warp.Date as D
5151import qualified Network.Wai.Handler.Warp.FdCache as F
5252import Network.Wai.Handler.Warp.Header
5353import Network.Wai.Handler.Warp.IO (toBufIOWith)
54−import Network.Wai.Handler.Warp.RequestHeader (parseByteRanges)
5554import Network.Wai.Handler.Warp.ResponseHeader
5655import qualified Network.Wai.Handler.Warp.Timeout as T
5756import Network.Wai.Handler.Warp.Types
5857import Network.Wai.Internal
59−import Numeric (showInt)
6058import qualified Paths_warp
61−import qualified System.PosixCompat.Files as P
6259
6360#if !MIN_VERSION_base(4,5,0)
6461(<>) :: Monoid m => m -> m -> m
@@ -68,11 +65,6 @@ import qualified System.PosixCompat.Files as P
6865-- $setup
6966-- >>> :set -XOverloadedStrings
7067
71−mapRight :: (b -> c) -> Either a b -> Either a c
72−mapRight f eith = case eith of
73− Right x -> Right (f x)
74− Left l -> Left l
75−
7668----------------------------------------------------------------
7769
7870fileRange :: H.Status -> H.ResponseHeaders -> FilePath
@@ -80,8 +72,7 @@ fileRange :: H.Status -> H.ResponseHeaders -> FilePath
8072 -> IO (Either IOException
8173 (H.Status, H.ResponseHeaders, Integer, Integer))
8274fileRange s0 hs0 path Nothing mRange =
83− mapRight (fileRangeSized s0 hs0 Nothing mRange . fromIntegral . P.fileSize) <$>
84− try (P.getFileStatus path)
75+ fmap (fileRangeSized s0 hs0 Nothing mRange) <$> tryGetFileSize path
8576fileRange s0 hs0 _ mPart@(Just part) mRange =
8677 return . Right $ fileRangeSized s0 hs0 mPart mRange size
8778 where
@@ -92,40 +83,18 @@ fileRangeSized :: H.Status -> H.ResponseHeaders
9283 -> (H.Status, H.ResponseHeaders, Integer, Integer)
9384fileRangeSized s0 hs0 mPart mRange fileSize = (s, hs, beg, len)
9485 where
95− (beg, end, len, isEntire) = checkPartRange fileSize mPart mRange
96− hs1 = addContentLength len hs0
97− hs | isEntire = hs1
98− | otherwise = addContentRange beg end fileSize hs1
99− s | isEntire = s0
100− | otherwise = H.status206
86+ (beg, _end, len, _entire) = checkPartRange fileSize mPart mRange
87+ (s, hs) = adjustForFilePart s0 hs0 $ FilePart beg len fileSize
10188
10289checkPartRange :: Integer -> Maybe FilePart -> Maybe HeaderValue
10390 -> (Integer, Integer, Integer, Bool)
104−checkPartRange fileSize = checkPart
91+checkPartRange fileSize mpart mrange = (beg, end, len, isEntire)
10592 where
106− checkPart Nothing Nothing = (0, fileSize - 1, fileSize, True)
107− checkPart Nothing (Just range) = case parseByteRanges range >>= listToMaybe of
108− -- Range is broken
109− Nothing -> (0, fileSize - 1, fileSize, True)
110− Just hrange -> checkRange hrange
111− -- Ignore Range if FilePart is specified.
112− -- We assume that an application handled Range and specified
113− -- FilePart.
114− checkPart (Just part) _ = (beg, end, len, isEntire)
115− where
116− beg = filePartOffset part
117− len = filePartByteCount part
118− end = beg + len - 1
119− isEntire = beg == 0 && len == fileSize
120−
121− checkRange (H.ByteRangeFrom beg) = fromRange beg (fileSize - 1)
122− checkRange (H.ByteRangeFromTo beg end) = fromRange beg (min (fileSize - 1) end)
123− checkRange (H.ByteRangeSuffix count) = fromRange (max 0 (fileSize - count)) (fileSize - 1)
124−
125− fromRange beg end = (beg, end, len, isEntire)
126− where
127− len = end - beg + 1
128− isEntire = beg == 0 && len == fileSize
93+ part = fromMaybe (chooseFilePart fileSize mrange) mpart
94+ beg = filePartOffset part
95+ len = filePartByteCount part
96+ end = beg + len - 1
97+ isEntire = beg == 0 && len == fileSize
12998
13099----------------------------------------------------------------
131100
@@ -396,25 +365,6 @@ addAcceptRanges hdrs = (hAcceptRanges, "bytes") : hdrs
396365addTransferEncoding :: H.ResponseHeaders -> H.ResponseHeaders
397366addTransferEncoding hdrs = (hTransferEncoding, "chunked") : hdrs
398367
399−addContentLength :: Integer -> H.ResponseHeaders -> H.ResponseHeaders
400−addContentLength cl hdrs = (H.hContentLength, len) : hdrs
401− where
402− len = B.pack $ show cl
403−
404−addContentRange :: Integer -> Integer -> Integer
405− -> H.ResponseHeaders -> H.ResponseHeaders
406−addContentRange beg end total hdrs = (hContentRange, range) : hdrs
407− where
408− range = B.pack
409− -- building with ShowS
410− $ 'b' : 'y': 't' : 'e' : 's' : ' '
411− : (if beg > end then ('*':) else
412− showInt beg
413− . ('-' :)
414− . showInt end)
415− ( '/'
416− : showInt total "")
417−
418368addDate :: D.DateCache -> IndexedHeader -> H.ResponseHeaders -> IO H.ResponseHeaders
419369addDate dc rspidxhdr hdrs = case rspidxhdr ! idxDate of
420370 Nothing -> do
warp/Network/Wai/Handler/Warp/Run.hs+113−29
@@ -6,6 +6,9 @@
66
77module Network.Wai.Handler.Warp.Run where
88
9+#if __GLASGOW_HASKELL__ < 709
10+import Control.Applicative ((<*>))
11+#endif
912import Control.Arrow (first)
1013import Control.Concurrent (threadDelay)
1114import qualified Control.Concurrent as Conc (yield)
@@ -21,11 +24,12 @@ import Network (sClose, Socket)
2124import Network.Socket (accept, withSocketsDo, SockAddr(SockAddrInet, SockAddrInet6))
2225import qualified Network.Socket.ByteString as Sock
2326import Network.Wai
27+import Network.Wai.HTTP2 (HTTP2Application, promoteApplication)
2428import Network.Wai.Handler.Warp.Buffer
2529import Network.Wai.Handler.Warp.Counter
2630import qualified Network.Wai.Handler.Warp.Date as D
2731import qualified Network.Wai.Handler.Warp.FdCache as F
28−import Network.Wai.Handler.Warp.HTTP2
32+import Network.Wai.Handler.Warp.HTTP2 (http2, isHTTP2)
2933import Network.Wai.Handler.Warp.Header
3034import Network.Wai.Handler.Warp.ReadInt
3135import Network.Wai.Handler.Warp.Recv
@@ -68,10 +72,23 @@ allowInterrupt :: IO ()
6872allowInterrupt = unblock $ return ()
6973#endif
7074
75+-- Composition over two arguments at once; used for runHTTP2\*.
76+(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
77+f .: g = curry $ f . uncurry g
78+
7179-- | Run an 'Application' on the given port.
7280-- This calls 'runSettings' with 'defaultSettings'.
7381run :: Port -> Application -> IO ()
74−run p = runSettings defaultSettings { settingsPort = p }
82+run port = runServe port . serveDefault
83+
84+-- | Serve an 'HTTP2Application' and an 'Application' together on the given
85+-- port.
86+runHTTP2 :: Port -> HTTP2Application -> Application -> IO ()
87+runHTTP2 port = runServe port .: serveHTTP2
88+
89+-- | The generalized form of 'run'.
90+runServe :: Port -> ServeConnection -> IO ()
91+runServe p = runServeSettings defaultSettings { settingsPort = p }
7592
7693-- | Run an 'Application' on the port present in the @PORT@
7794-- environment variable. Uses the 'Port' given when the variable is unset.
@@ -79,28 +96,44 @@ run p = runSettings defaultSettings { settingsPort = p }
7996--
8097-- Since 3.0.9
8198runEnv :: Port -> Application -> IO ()
82−runEnv p app = do
99+runEnv port = runServeEnv port . serveDefault
100+
101+-- | The HTTP\/2-aware form of 'runEnv'.
102+runHTTP2Env :: Port -> HTTP2Application -> Application -> IO ()
103+runHTTP2Env port = runServeEnv port .: serveHTTP2
104+
105+-- | The generalized form of 'runEnv'.
106+runServeEnv :: Port -> ServeConnection -> IO ()
107+runServeEnv p serveConn = do
83108 mp <- fmap (lookup "PORT") getEnvironment
84109
85− maybe (run p app) runReadPort mp
110+ maybe (runServe p serveConn) runReadPort mp
86111
87112 where
88113 runReadPort :: String -> IO ()
89114 runReadPort sp = case reads sp of
90− ((p', _):_) -> run p' app
115+ ((p', _):_) -> runServe p' serveConn
91116 _ -> fail $ "Invalid value in $PORT: " ++ sp
92117
93118-- | Run an 'Application' with the given 'Settings'.
94119-- This opens a listen socket on the port defined in 'Settings' and
95120-- calls 'runSettingsSocket'.
96121runSettings :: Settings -> Application -> IO ()
97−runSettings set app = withSocketsDo $
122+runSettings set = runServeSettings set . serveDefault
123+
124+-- | The HTTP\/2-aware form of 'runSettings'.
125+runHTTP2Settings :: Settings -> HTTP2Application -> Application -> IO ()
126+runHTTP2Settings set = runServeSettings set .: serveHTTP2
127+
128+-- | The generalized form of 'runSettings'.
129+runServeSettings :: Settings -> ServeConnection -> IO ()
130+runServeSettings set serveConn = withSocketsDo $
98131 bracket
99132 (bindPortTCP (settingsPort set) (settingsHost set))
100133 sClose
101134 (\socket -> do
102135 setSocketCloseOnExec socket
103− runSettingsSocket set socket app)
136+ runServeSettingsSocket set socket serveConn)
104137
105138-- | This installs a shutdown handler for the given socket and
106139-- calls 'runSettingsConnection' with the default connection setup action
@@ -114,9 +147,22 @@ runSettings set app = withSocketsDo $
114147-- Note that the 'settingsPort' will still be passed to 'Application's via the
115148-- 'serverPort' record.
116149runSettingsSocket :: Settings -> Socket -> Application -> IO ()
117−runSettingsSocket set socket app = do
150+runSettingsSocket set socket = runServeSettingsSocket set socket . serveDefault
151+
152+-- | The HTTP\/2-aware form of 'runSettingsSocket'.
153+runHTTP2SettingsSocket :: Settings
154+ -> Socket
155+ -> HTTP2Application
156+ -> Application
157+ -> IO ()
158+runHTTP2SettingsSocket set socket =
159+ runServeSettingsSocket set socket .: serveHTTP2
160+
161+-- | The generalized form of 'runSettingsSocket'.
162+runServeSettingsSocket :: Settings -> Socket -> ServeConnection -> IO ()
163+runServeSettingsSocket set socket serveConn = do
118164 settingsInstallShutdownHandler set closeListenSocket
119− runSettingsConnection set getConn app
165+ runServeSettingsConnection set getConn serveConn
120166 where
121167 getConn = do
122168#if WINDOWS
@@ -140,7 +186,16 @@ runSettingsSocket set socket app = do
140186--
141187-- Since 1.3.5
142188runSettingsConnection :: Settings -> IO (Connection, SockAddr) -> Application -> IO ()
143−runSettingsConnection set getConn app = runSettingsConnectionMaker set getConnMaker app
189+runSettingsConnection set getConn =
190+ runServeSettingsConnection set getConn . serveDefault
191+
192+-- | The generalized form of 'runSettingsConnection'.
193+runServeSettingsConnection :: Settings
194+ -> IO (Connection, SockAddr)
195+ -> ServeConnection
196+ -> IO ()
197+runServeSettingsConnection set getConn serveConn =
198+ runServeSettingsConnectionMaker set getConnMaker serveConn
144199 where
145200 getConnMaker = do
146201 (conn, sa) <- getConn
@@ -149,8 +204,16 @@ runSettingsConnection set getConn app = runSettingsConnectionMaker set getConnMa
149204-- | This modifies the connection maker so that it returns 'TCP' for 'Transport'
150205-- (i.e. plain HTTP) then calls 'runSettingsConnectionMakerSecure'.
151206runSettingsConnectionMaker :: Settings -> IO (IO Connection, SockAddr) -> Application -> IO ()
152−runSettingsConnectionMaker x y =
153− runSettingsConnectionMakerSecure x (go y)
207+runSettingsConnectionMaker set getConnMaker =
208+ runServeSettingsConnectionMaker set getConnMaker . serveDefault
209+
210+-- | The generalized form of 'runSettingsConnectionMaker'.
211+runServeSettingsConnectionMaker :: Settings
212+ -> IO (IO Connection, SockAddr)
213+ -> ServeConnection
214+ -> IO ()
215+runServeSettingsConnectionMaker x y =
216+ runServeSettingsConnectionMakerSecure x (go y)
154217 where
155218 go = fmap (first (fmap (, TCP)))
156219
@@ -163,14 +226,22 @@ runSettingsConnectionMaker x y =
163226--
164227-- Since 2.1.4
165228runSettingsConnectionMakerSecure :: Settings -> IO (IO (Connection, Transport), SockAddr) -> Application -> IO ()
166−runSettingsConnectionMakerSecure set getConnMaker app = do
229+runSettingsConnectionMakerSecure set getConnMaker =
230+ runServeSettingsConnectionMakerSecure set getConnMaker . serveDefault
231+
232+-- | The generalized form of 'runSettingsConnectionMakerSecure'.
233+runServeSettingsConnectionMakerSecure :: Settings
234+ -> IO (IO (Connection, Transport), SockAddr)
235+ -> ServeConnection
236+ -> IO ()
237+runServeSettingsConnectionMakerSecure set getConnMaker serveConn = do
167238 settingsBeforeMainLoop set
168239 counter <- newCounter
169240
170241 D.withDateCache $ \dc ->
171242 F.withFdCache fdCacheDurationInSeconds $ \fc ->
172243 withTimeoutManager $ \tm ->
173− acceptConnection set getConnMaker app dc fc tm counter
244+ acceptConnection set getConnMaker serveConn dc fc tm counter
174245 where
175246 fdCacheDurationInSeconds = settingsFdCacheDuration set * 1000000
176247 withTimeoutManager f = case settingsManager set of
@@ -195,13 +266,13 @@ runSettingsConnectionMakerSecure set getConnMaker app = do
195266-- Our approach is explained in the comments below.
196267acceptConnection :: Settings
197268 -> IO (IO (Connection, Transport), SockAddr)
198− -> Application
269+ -> ServeConnection
199270 -> D.DateCache
200271 -> Maybe F.MutableFdCache
201272 -> T.Manager
202273 -> Counter
203274 -> IO ()
204−acceptConnection set getConnMaker app dc fc tm counter = do
275+acceptConnection set getConnMaker serveConn dc fc tm counter = do
205276 -- First mask all exceptions in acceptLoop. This is necessary to
206277 -- ensure that no async exception is throw between the call to
207278 -- acceptNewConnection and the registering of connClose.
@@ -223,7 +294,7 @@ acceptConnection set getConnMaker app dc fc tm counter = do
223294 case mx of
224295 Nothing -> return ()
225296 Just (mkConn, addr) -> do
226− fork set mkConn addr app dc fc tm counter
297+ fork set mkConn addr serveConn dc fc tm counter
227298 acceptLoop
228299
229300 acceptNewConnection = do
@@ -247,13 +318,13 @@ acceptConnection set getConnMaker app dc fc tm counter = do
247318fork :: Settings
248319 -> IO (Connection, Transport)
249320 -> SockAddr
250− -> Application
321+ -> ServeConnection
251322 -> D.DateCache
252323 -> Maybe F.MutableFdCache
253324 -> T.Manager
254325 -> Counter
255326 -> IO ()
256−fork set mkConn addr app dc fc tm counter = settingsFork set $ \ unmask ->
327+fork set mkConn addr serveConn dc fc tm counter = settingsFork set $ \ unmask ->
257328 -- Run the connection maker to get a new connection, and ensure
258329 -- that the connection is closed. If the mkConn call throws an
259330 -- exception, we will leak the connection. If the mkConn call is
@@ -284,21 +355,34 @@ fork set mkConn addr app dc fc tm counter = settingsFork set $ \ unmask ->
284355
285356 -- Actually serve this connection.
286357 -- bracket with closeConn above ensures the connection is closed.
287− when goingon $ serveConnection conn ii addr transport set app
358+ when goingon $ serveConn conn ii addr transport set
288359 where
289360 closeConn (conn, _transport) = connClose conn
290361
291362 onOpen adr = increase counter >> settingsOnOpen set adr
292363 onClose adr _ = decrease counter >> settingsOnClose set adr
293364
294−serveConnection :: Connection
295− -> InternalInfo
296− -> SockAddr
297− -> Transport
298− -> Settings
299− -> Application
300− -> IO ()
301−serveConnection conn ii origAddr transport settings app = do
365+-- The type of a function to serve a fully-prepared connection.
366+type ServeConnection = Connection
367+ -> InternalInfo
368+ -> SockAddr
369+ -> Transport
370+ -> Settings
371+ -> IO ()
372+
373+-- Serve an HTTP\/2-aware application, rejecting clients that attempt to use an
374+-- older protocol version.
375+serveHTTP2Only :: HTTP2Application -> ServeConnection
376+serveHTTP2Only = error "serveHTTP2Only not implemented"
377+
378+-- Serve an HTTP\/2-unaware Application to a connection over any HTTP version.
379+serveDefault :: Application -> ServeConnection
380+serveDefault = flip serveHTTP2 <*> promoteApplication
381+
382+-- Serve an HTTP\/2-aware application over HTTP\/2 or a backup 'Application'
383+-- over HTTP\/1.1 or HTTP\/1.
384+serveHTTP2 :: HTTP2Application -> Application -> ServeConnection
385+serveHTTP2 app2 app conn ii origAddr transport settings = do
302386 -- fixme: Upgrading to HTTP/2 should be supported.
303387 (h2,bs) <- if isHTTP2 transport then
304388 return (True, "")
@@ -311,7 +395,7 @@ serveConnection conn ii origAddr transport settings app = do
311395 if h2 then do
312396 recvN <- makeReceiveN bs (connRecv conn) (connRecvBuf conn)
313397 -- fixme: origAddr
314− http2 conn ii origAddr transport settings recvN app
398+ http2 conn ii origAddr transport settings recvN app2
315399 else do
316400 istatus <- newIORef False
317401 src <- mkSource (wrappedRecv conn th istatus (settingsSlowlorisSize settings))