on master help sign in
Merge pull request #399 from yesodweb/http2

supporting HTTP/2.

commitc5001a0fb8100a425e68b09a38c32b83bd69384f authorKazu Yamamoto <kazu@iij.ad.jp> dateFri 17 Jul 2015, 08:08 (11 years ago) parentf199705f0b14dc
16 files changed+1508−40
warp-tls/Network/Wai/Handler/WarpTLS.hs+64−5 warp/Network/Wai/Handler/Warp/HTTP2.hs+61 warp/Network/Wai/Handler/Warp/HTTP2/EncodeFrame.hs+35 warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hs+49 warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+319 warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+141 warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+387 warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+243 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+123 warp/Network/Wai/Handler/Warp/Recv.hs+23−15 warp/Network/Wai/Handler/Warp/RequestHeader.hs+9−6 warp/Network/Wai/Handler/Warp/Response.hs+3 warp/Network/Wai/Handler/Warp/Run.hs+27−10 warp/Network/Wai/Handler/Warp/SendFile.hs+6−4 warp/Network/Wai/Handler/Warp/Types.hs+4 warp/warp.cabal+14
warp-tls/Network/Wai/Handler/WarpTLS.hs+64−5
@@ -239,13 +239,27 @@ runTLSSocket' tlsset@TLSSettings{..} set credential sock app =
239239 , TLS.serverSupported = def {
240240 TLS.supportedVersions = tlsAllowedVersions
241241 , TLS.supportedCiphers = tlsCiphers
242+ , TLS.supportedHashSignatures = [
243+ (TLS.HashSHA256, TLS.SignatureRSA)
244+ , (TLS.HashSHA1, TLS.SignatureRSA)
245+ ]
242246 }
243247 , TLS.serverShared = def {
244248 TLS.sharedCredentials = TLS.Credentials [credential]
245249 }
246− , TLS.serverHooks = tlsServerHooks
250+ , TLS.serverHooks = tlsServerHooks {
251+ TLS.onALPNClientSuggest = Just alpn
252+ }
247253 }
248254
255+alpn :: [S.ByteString] -> IO S.ByteString
256+alpn xs
257+ | "h2" `elem` xs = return "h2"
258+ | "h2-16" `elem` xs = return "h2-16"
259+ | "h2-15" `elem` xs = return "h2-15"
260+ | "h2-14" `elem` xs = return "h2-14"
261+ | otherwise = return "http/1.1"
262+
249263----------------------------------------------------------------
250264
251265getter :: TLS.TLSParams params => TLSSettings -> Socket -> params -> IO (IO (Connection, Transport), SockAddr)
@@ -275,8 +289,10 @@ httpOverTls TLSSettings{..} s bs0 params = do
275289 TLS.contextHookSetLogging ctx tlsLogging
276290 TLS.handshake ctx
277291 writeBuf <- allocateBuffer bufferSize
292+ -- Creating a cache for leftover input data.
293+ ref <- I.newIORef ""
278294 tls <- getTLSinfo ctx
279− return (conn ctx writeBuf, tls)
295+ return (conn ctx writeBuf ref, tls)
280296 where
281297 backend recvN = TLS.Backend {
282298 TLS.backendFlush = return ()
@@ -284,12 +300,13 @@ httpOverTls TLSSettings{..} s bs0 params = do
284300 , TLS.backendSend = sendAll s
285301 , TLS.backendRecv = recvN
286302 }
287− conn ctx writeBuf = Connection {
303+ conn ctx writeBuf ref = Connection {
288304 connSendMany = TLS.sendData ctx . L.fromChunks
289305 , connSendAll = sendall
290306 , connSendFile = sendfile
291307 , connClose = close
292− , connRecv = recv
308+ , connRecv = recv ref
309+ , connRecvBuf = recvBuf ref
293310 , connWriteBuffer = writeBuf
294311 , connBufferSize = bufferSize
295312 }
@@ -302,7 +319,18 @@ httpOverTls TLSSettings{..} s bs0 params = do
302319 void (tryIO $ TLS.bye ctx) `finally`
303320 TLS.contextClose ctx
304321
305− recv = handle onEOF go
322+ -- TLS version of recv with a cache for leftover input data.
323+ -- The cache is shared with recvBuf.
324+ recv cref = do
325+ cached <- I.readIORef cref
326+ if cached /= "" then do
327+ I.writeIORef cref ""
328+ return cached
329+ else
330+ recv'
331+
332+ -- TLS version of recv (decrypting) without a cache.
333+ recv' = handle onEOF go
306334 where
307335 onEOF e
308336 | Just TLS.Error_EOF <- fromException e = return S.empty
@@ -314,6 +342,37 @@ httpOverTls TLSSettings{..} s bs0 params = do
314342 else
315343 return x
316344
345+ -- TLS version of recvBuf with a cache for leftover input data.
346+ recvBuf cref buf siz = do
347+ cached <- I.readIORef cref
348+ (ret, leftover) <- fill cached buf siz recv'
349+ I.writeIORef cref leftover
350+ return ret
351+
352+fill :: S.ByteString -> Buffer -> BufSize -> Recv -> IO (Bool,S.ByteString)
353+fill bs0 buf0 siz0 recv
354+ | siz0 <= len0 = do
355+ let (bs, leftover) = S.splitAt siz0 bs0
356+ void $ copy buf0 bs
357+ return (True, leftover)
358+ | otherwise = do
359+ buf <- copy buf0 bs0
360+ loop buf (siz0 - len0)
361+ where
362+ len0 = S.length bs0
363+ loop _ 0 = return (True, "")
364+ loop buf siz = do
365+ bs <- recv
366+ let len = S.length bs
367+ if len == 0 then return (False, "")
368+ else if (len <= siz) then do
369+ buf' <- copy buf bs
370+ loop buf' (siz - len)
371+ else do
372+ let (bs1,bs2) = S.splitAt siz bs
373+ void $ copy buf bs1
374+ return (True, bs2)
375+
317376getTLSinfo :: TLS.Context -> IO Transport
318377getTLSinfo ctx = do
319378 proto <- TLS.getNegotiatedProtocol ctx
warp/Network/Wai/Handler/Warp/HTTP2.hsnew file+61
@@ -0,0 +1,61 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE RecordWildCards #-}
3+
4+module Network.Wai.Handler.Warp.HTTP2 (isHTTP2, http2) where
5+
6+import Control.Concurrent (forkIO, killThread)
7+import qualified Control.Exception as E
8+import Control.Monad (when, unless, replicateM)
9+import Data.ByteString (ByteString)
10+import Network.HTTP2
11+import Network.Socket (SockAddr)
12+import Network.Wai
13+import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
14+import Network.Wai.Handler.Warp.HTTP2.Receiver
15+import Network.Wai.Handler.Warp.HTTP2.Request
16+import Network.Wai.Handler.Warp.HTTP2.Sender
17+import Network.Wai.Handler.Warp.HTTP2.Types
18+import Network.Wai.Handler.Warp.HTTP2.Worker
19+import qualified Network.Wai.Handler.Warp.Settings as S (Settings)
20+import Network.Wai.Handler.Warp.Types
21+
22+----------------------------------------------------------------
23+
24+http2 :: Connection -> InternalInfo -> SockAddr -> Transport -> S.Settings -> (BufSize -> IO ByteString) -> Application -> IO ()
25+http2 conn ii addr transport settings readN app = do
26+ checkTLS
27+ ok <- checkPreface
28+ when ok $ do
29+ ctx <- newContext
30+ let responder = response ctx
31+ mkreq = mkRequest settings addr
32+ tid <- forkIO $ frameReceiver ctx mkreq readN
33+ -- fixme: 10 is hard-coded
34+ -- To prevent thread-leak, we executed the fixed number of threads
35+ -- statically at this moment. But ResponseStream occupies one
36+ -- worker thread. So, this should be dynamic.
37+ tids <- replicateM 10 $ forkIO $ worker ctx settings tm app responder
38+ -- frameSender is the main thread because it ensures to send
39+ -- a goway frame.
40+ frameSender ctx conn ii settings `E.finally` do
41+ clearContext ctx
42+ mapM_ killThread (tid:tids)
43+ where
44+ tm = timeoutManager ii
45+ checkTLS = case transport of
46+ TCP -> return () -- direct
47+ tls -> unless (tls12orLater tls) $ goaway conn InadequateSecurity "Weak TLS"
48+ tls12orLater tls = tlsMajorVersion tls == 3 && tlsMinorVersion tls >= 3
49+ checkPreface = do
50+ preface <- readN connectionPrefaceLength
51+ if connectionPreface /= preface then do
52+ goaway conn ProtocolError "Preface mismatch"
53+ return False
54+ else
55+ return True
56+
57+-- connClose must not be called here since Run:fork calls it
58+goaway :: Connection -> ErrorCodeId -> ByteString -> IO ()
59+goaway Connection{..} etype debugmsg = connSendAll bytestream
60+ where
61+ bytestream = goawayFrame 0 etype debugmsg
warp/Network/Wai/Handler/Warp/HTTP2/EncodeFrame.hsnew file+35
@@ -0,0 +1,35 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module Network.Wai.Handler.Warp.HTTP2.EncodeFrame where
4+
5+import Data.ByteString (ByteString)
6+import Network.HTTP2
7+
8+----------------------------------------------------------------
9+
10+goawayFrame :: StreamId -> ErrorCodeId -> ByteString -> ByteString
11+goawayFrame sid etype debugmsg = encodeFrame einfo frame
12+ where
13+ einfo = encodeInfo id 0
14+ frame = GoAwayFrame sid etype debugmsg
15+
16+resetFrame :: ErrorCodeId -> StreamId -> ByteString
17+resetFrame etype sid = encodeFrame einfo frame
18+ where
19+ einfo = encodeInfo id sid
20+ frame = RSTStreamFrame etype
21+
22+settingsFrame :: (FrameFlags -> FrameFlags) -> SettingsList -> ByteString
23+settingsFrame func alist = encodeFrame einfo $ SettingsFrame alist
24+ where
25+ einfo = encodeInfo func 0
26+
27+pingFrame :: ByteString -> ByteString
28+pingFrame bs = encodeFrame einfo $ PingFrame bs
29+ where
30+ einfo = encodeInfo setAck 0
31+
32+windowUpdateFrame :: StreamId -> WindowSize -> ByteString
33+windowUpdateFrame sid winsiz = encodeFrame einfo $ WindowUpdateFrame winsiz
34+ where
35+ einfo = encodeInfo id sid
warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hsnew file+49
@@ -0,0 +1,49 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE RecordWildCards, NamedFieldPuns #-}
3+
4+module Network.Wai.Handler.Warp.HTTP2.HPACK where
5+
6+import Control.Arrow (first)
7+import qualified Control.Exception as E
8+import Data.ByteString.Builder (Builder)
9+import qualified Data.ByteString.Char8 as B8
10+import Data.CaseInsensitive (foldedCase)
11+import Data.IORef (readIORef, writeIORef)
12+import Network.HPACK
13+import qualified Network.HTTP.Types as H
14+import Network.HTTP2
15+import Network.Wai
16+import Network.Wai.Handler.Warp.HTTP2.Types
17+import Network.Wai.Handler.Warp.Header
18+import Network.Wai.Handler.Warp.Response
19+import qualified Network.Wai.Handler.Warp.Settings as S
20+import Network.Wai.Handler.Warp.Types
21+
22+hpackEncodeHeader :: Context -> InternalInfo -> S.Settings -> Response
23+ -> IO Builder
24+hpackEncodeHeader Context{encodeDynamicTable} ii settings rsp = do
25+ hdr1 <- addServerAndDate hdr0
26+ 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
31+ where
32+ hdr0 = responseHeaders rsp
33+ status = B8.pack $ show $ H.statusCode $ responseStatus rsp
34+ dc = dateCacher ii
35+ rspidxhdr = indexResponseHeader hdr0
36+ defServer = S.settingsServerName settings
37+ addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr
38+
39+
40+----------------------------------------------------------------
41+
42+hpackDecodeHeader :: HeaderBlockFragment -> Context -> IO HeaderList
43+hpackDecodeHeader hdrblk Context{decodeDynamicTable} = do
44+ hdrtbl <- readIORef decodeDynamicTable
45+ (hdrtbl', hdr) <- decodeHeader hdrtbl hdrblk `E.onException` cleanup
46+ writeIORef decodeDynamicTable hdrtbl'
47+ return hdr
48+ where
49+ cleanup = E.throwIO $ ConnectionError CompressionError "cannot decompress the header"
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hsnew file+319
@@ -0,0 +1,319 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE RecordWildCards, NamedFieldPuns #-}
3+{-# LANGUAGE BangPatterns #-}
4+{-# LANGUAGE CPP #-}
5+
6+module Network.Wai.Handler.Warp.HTTP2.Receiver (frameReceiver) where
7+
8+#if __GLASGOW_HASKELL__ < 709
9+import Control.Applicative
10+#endif
11+import Control.Concurrent (takeMVar)
12+import Control.Concurrent.STM
13+import qualified Control.Exception as E
14+import Control.Monad (when, unless, void)
15+import Data.ByteString (ByteString)
16+import qualified Data.ByteString as BS
17+import Data.Maybe (isJust)
18+import Network.HTTP2
19+import Network.HTTP2.Priority
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.IORef
25+import Network.Wai.Handler.Warp.Types
26+
27+----------------------------------------------------------------
28+
29+frameReceiver :: Context -> MkReq -> (BufSize -> IO ByteString) -> IO ()
30+frameReceiver ctx mkreq recvN =
31+ E.handle sendGoaway loop `E.finally` takeMVar wait
32+ where
33+ Context{ http2settings
34+ , streamTable
35+ , concurrency
36+ , continued
37+ , currentStreamId
38+ , inputQ
39+ , outputQ
40+ , wait} = ctx
41+ sendGoaway (ConnectionError err msg) = do
42+ csid <- readIORef currentStreamId
43+ let frame = goawayFrame csid err msg
44+ enqueue outputQ (OGoaway frame) highestPriority
45+ sendGoaway _ = return ()
46+
47+ sendReset err sid = do
48+ let frame = resetFrame err sid
49+ enqueue outputQ (OFrame frame) highestPriority
50+
51+ loop = do
52+ hd <- recvN frameHeaderLength
53+ if BS.null hd then
54+ enqueue outputQ OFinish highestPriority
55+ else do
56+ cont <- processStreamGuardingError $ decodeFrameHeader hd
57+ when cont loop
58+
59+ processStreamGuardingError (_, FrameHeader{streamId})
60+ | isResponse streamId = E.throwIO $ ConnectionError ProtocolError "stream id should be odd"
61+ processStreamGuardingError (FrameUnknown _, FrameHeader{payloadLength}) = do
62+ mx <- readIORef continued
63+ case mx of
64+ Nothing -> do
65+ -- ignoring unknown frame
66+ consume payloadLength
67+ return True
68+ Just _ -> E.throwIO $ ConnectionError ProtocolError "unknown frame"
69+ processStreamGuardingError (FramePushPromise, _) =
70+ E.throwIO $ ConnectionError ProtocolError "push promise is not allowed"
71+ processStreamGuardingError typhdr@(ftyp, header@FrameHeader{payloadLength}) = do
72+ settings <- readIORef http2settings
73+ case checkFrameHeader settings typhdr of
74+ Left h2err -> case h2err of
75+ StreamError err sid -> do
76+ sendReset err sid
77+ consume payloadLength
78+ return True
79+ connErr -> E.throwIO connErr
80+ Right _ -> do
81+ ex <- E.try $ controlOrStream ftyp header
82+ case ex of
83+ Left (StreamError err sid) -> do
84+ sendReset err sid
85+ return True
86+ Left connErr -> E.throw connErr
87+ Right cont -> return cont
88+
89+ controlOrStream ftyp header@FrameHeader{streamId, payloadLength}
90+ | isControl streamId = do
91+ pl <- recvN payloadLength
92+ control ftyp header pl ctx
93+ | otherwise = do
94+ checkContinued
95+ strm@Stream{streamState,streamContentLength} <- getStream
96+ pl <- recvN payloadLength
97+ state <- readIORef streamState
98+ state' <- stream ftyp header pl ctx state strm
99+ case state' of
100+ Open (NoBody hdr pri) -> do
101+ resetContinued
102+ case validateHeaders hdr of
103+ Just vh -> do
104+ when (isJust (vhCL vh) && vhCL vh /= Just 0) $
105+ E.throwIO $ StreamError ProtocolError streamId
106+ writeIORef streamState HalfClosed
107+ let req = mkreq vh (return "")
108+ atomically $ writeTQueue inputQ $ Input strm req pri
109+ Nothing -> E.throwIO $ StreamError ProtocolError streamId
110+ Open (HasBody hdr pri) -> do
111+ resetContinued
112+ case validateHeaders hdr of
113+ Just vh -> do
114+ q <- newTQueueIO
115+ writeIORef streamState (Open (Body q))
116+ writeIORef streamContentLength $ vhCL vh
117+ readQ <- newReadBody q
118+ bodySource <- mkSource readQ
119+ let req = mkreq vh (readSource bodySource)
120+ atomically $ writeTQueue inputQ $ Input strm req pri
121+ Nothing -> E.throwIO $ StreamError ProtocolError streamId
122+ s@(Open Continued{}) -> do
123+ setContinued
124+ writeIORef streamState s
125+ s -> do -- Idle, Open Body, HalfClosed, Closed
126+ resetContinued
127+ writeIORef streamState s
128+ return True
129+ where
130+ setContinued = writeIORef continued (Just streamId)
131+ resetContinued = writeIORef continued Nothing
132+ checkContinued = do
133+ mx <- readIORef continued
134+ case mx of
135+ Nothing -> return ()
136+ Just sid
137+ | sid == streamId && ftyp == FrameContinuation -> return ()
138+ | otherwise -> E.throwIO $ ConnectionError ProtocolError "continuation frame must follow"
139+ getStream = do
140+ mstrm0 <- search streamTable streamId
141+ case mstrm0 of
142+ Just strm0 -> do
143+ when (ftyp == FrameHeaders) $ do
144+ st <- readIORef $ streamState strm0
145+ when (isHalfClosed st) $ E.throwIO $ ConnectionError ProtocolError "header must not be sent to half closed"
146+ return strm0
147+ Nothing -> do
148+ when (ftyp `notElem` [FrameHeaders,FramePriority]) $
149+ E.throwIO $ ConnectionError ProtocolError "this frame is not allowed in an idel stream"
150+ when (ftyp == FrameHeaders) $ do
151+ csid <- readIORef currentStreamId
152+ if streamId <= csid then
153+ E.throwIO $ ConnectionError ProtocolError "stream identifier must not decrease"
154+ else
155+ writeIORef currentStreamId streamId
156+ cnt <- readIORef concurrency
157+ when (cnt >= recommendedConcurrency) $
158+ E.throwIO $ ConnectionError RefusedStream "over max concurrency"
159+ ws <- initialWindowSize <$> readIORef http2settings
160+ newstrm <- newStream streamId (fromIntegral ws)
161+ when (ftyp == FrameHeaders) $ opened ctx newstrm
162+ insert streamTable streamId newstrm
163+ return newstrm
164+
165+ consume = void . recvN
166+
167+----------------------------------------------------------------
168+
169+control :: FrameTypeId -> FrameHeader -> ByteString -> Context -> IO Bool
170+control FrameSettings header@FrameHeader{flags} bs Context{http2settings, outputQ} = do
171+ SettingsFrame alist <- guardIt $ decodeSettingsFrame header bs
172+ case checkSettingsList alist of
173+ Just x -> E.throwIO x
174+ Nothing -> return ()
175+ unless (testAck flags) $ do
176+ modifyIORef http2settings $ \old -> updateSettings old alist
177+ let frame = settingsFrame setAck []
178+ enqueue outputQ (OFrame frame) highestPriority
179+ return True
180+
181+control FramePing FrameHeader{flags} bs Context{outputQ} =
182+ if testAck flags then
183+ E.throwIO $ ConnectionError ProtocolError "the ack flag of this ping frame must not be set"
184+ else do
185+ let frame = pingFrame bs
186+ enqueue outputQ (OFrame frame) defaultPriority
187+ return True
188+
189+control FrameGoAway _ _ Context{outputQ} = do
190+ enqueue outputQ OFinish highestPriority
191+ return False
192+
193+control FrameWindowUpdate header bs Context{connectionWindow} = do
194+ WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
195+ w <- (n +) <$> atomically (readTVar connectionWindow)
196+ when (isWindowOverflow w) $ E.throwIO $ ConnectionError FlowControlError "control window should be less than 2^31"
197+ atomically $ writeTVar connectionWindow w
198+ return True
199+
200+control _ _ _ _ =
201+ -- must not reach here
202+ return False
203+
204+----------------------------------------------------------------
205+
206+guardIt :: Either HTTP2Error a -> IO a
207+guardIt x = case x of
208+ Left err -> E.throwIO err
209+ Right frame -> return frame
210+
211+checkPriority :: Priority -> StreamId -> IO ()
212+checkPriority p me
213+ | dep == me = E.throwIO $ StreamError ProtocolError me
214+ | otherwise = return ()
215+ where
216+ dep = streamDependency p
217+
218+stream :: FrameTypeId -> FrameHeader -> ByteString -> Context -> StreamState -> Stream -> IO StreamState
219+stream FrameHeaders header@FrameHeader{flags} bs ctx (Open JustOpened) Stream{streamNumber} = do
220+ HeadersFrame mp frag <- guardIt $ decodeHeadersFrame header bs
221+ pri <- case mp of
222+ Nothing -> return defaultPriority
223+ Just p -> do
224+ checkPriority p streamNumber
225+ return p
226+ let endOfStream = testEndStream flags
227+ endOfHeader = testEndHeader flags
228+ if endOfHeader then do
229+ hdr <- hpackDecodeHeader frag ctx
230+ return $ if endOfStream then
231+ Open (NoBody hdr pri)
232+ else
233+ Open (HasBody hdr pri)
234+ else do
235+ let !siz = BS.length frag
236+ return $ Open $ Continued [frag] siz 1 endOfStream pri
237+
238+stream FrameHeaders header@FrameHeader{flags} bs _ s@(Open (Body q)) _ = do
239+ -- trailer is not supported.
240+ -- let's read and ignore it.
241+ HeadersFrame _ _ <- guardIt $ decodeHeadersFrame header bs
242+ let endOfStream = testEndStream flags
243+ if endOfStream then do
244+ atomically $ writeTQueue q ""
245+ return HalfClosed
246+ else
247+ return s
248+
249+stream FrameData
250+ header@FrameHeader{flags,payloadLength,streamId}
251+ bs
252+ Context{outputQ} s@(Open (Body q))
253+ Stream{streamNumber,streamBodyLength,streamContentLength} = do
254+ DataFrame body <- guardIt $ decodeDataFrame header bs
255+ let endOfStream = testEndStream flags
256+ len0 <- readIORef streamBodyLength
257+ let !len = len0 + payloadLength
258+ writeIORef streamBodyLength len
259+ when (payloadLength /= 0) $ do
260+ let frame1 = windowUpdateFrame 0 payloadLength
261+ frame2 = windowUpdateFrame streamNumber payloadLength
262+ frame = frame1 `BS.append` frame2
263+ enqueue outputQ (OFrame frame) highestPriority
264+ atomically $ writeTQueue q body
265+ if endOfStream then do
266+ mcl <- readIORef streamContentLength
267+ case mcl of
268+ Nothing -> return ()
269+ Just cl -> when (cl /= len) $ E.throwIO $ StreamError ProtocolError streamId
270+ atomically $ writeTQueue q ""
271+ return HalfClosed
272+ else
273+ return s
274+
275+stream FrameContinuation FrameHeader{flags} frag ctx (Open (Continued rfrags siz n endOfStream pri)) _ = do
276+ let endOfHeader = testEndHeader flags
277+ rfrags' = frag : rfrags
278+ siz' = siz + BS.length frag
279+ n' = n + 1
280+ when (siz' > 51200) $ -- fixme: hard coding: 50K
281+ E.throwIO $ ConnectionError EnhanceYourCalm "Header is too big"
282+ when (n' > 10) $ -- fixme: hard coding
283+ E.throwIO $ ConnectionError EnhanceYourCalm "Header is too fragmented"
284+ if endOfHeader then do
285+ let hdrblk = BS.concat $ reverse rfrags'
286+ hdr <- hpackDecodeHeader hdrblk ctx
287+ return $ if endOfStream then
288+ Open (NoBody hdr pri)
289+ else
290+ Open (HasBody hdr pri)
291+ else
292+ return $ Open $ Continued rfrags' siz' n' endOfStream pri
293+
294+stream FrameContinuation _ _ _ _ _ = E.throwIO $ ConnectionError ProtocolError "continue frame cannot come here"
295+
296+stream FrameWindowUpdate header@FrameHeader{streamId} bs _ s Stream{streamWindow} = do
297+ WindowUpdateFrame n <- guardIt $ decodeWindowUpdateFrame header bs
298+ w <- (n +) <$> atomically (readTVar streamWindow)
299+ when (isWindowOverflow w) $
300+ E.throwIO $ StreamError FlowControlError streamId
301+ atomically $ writeTVar streamWindow w
302+ return s
303+
304+stream FrameRSTStream header bs ctx _ strm = do
305+ RSTStreamFrame e <- guardIt $ decoderstStreamFrame header bs
306+ let cc = Reset e
307+ closed ctx strm cc
308+ return $ Closed cc -- will be written to streamState again
309+
310+stream FramePriority header bs Context{outputQ} s Stream{streamNumber} = do
311+ PriorityFrame p <- guardIt $ decodePriorityFrame header bs
312+ checkPriority p streamNumber
313+ prepare outputQ streamNumber p
314+ return s
315+
316+-- this ordering is important
317+stream _ _ _ _ (Open Continued{}) _ = E.throwIO $ ConnectionError ProtocolError "an illegal frame follows header/continuation frames"
318+stream FrameData FrameHeader{streamId} _ _ _ _ = E.throwIO $ StreamError StreamClosed streamId
319+stream _ FrameHeader{streamId} _ _ _ _ = E.throwIO $ StreamError ProtocolError streamId
warp/Network/Wai/Handler/Warp/HTTP2/Request.hsnew file+141
@@ -0,0 +1,141 @@
1+{-# LANGUAGE OverloadedStrings, CPP #-}
2+
3+module Network.Wai.Handler.Warp.HTTP2.Request (
4+ mkRequest
5+ , newReadBody
6+ , MkReq
7+ , ValidHeaders(..)
8+ , validateHeaders
9+ ) where
10+
11+#if __GLASGOW_HASKELL__ < 709
12+import Control.Applicative ((<$>))
13+#endif
14+import Control.Concurrent.STM
15+import Control.Monad (when)
16+import Data.ByteString (ByteString)
17+import qualified Data.ByteString as BS
18+import qualified Data.ByteString.Char8 as B8
19+import Data.CaseInsensitive (mk)
20+import Data.IORef (IORef, readIORef, newIORef, writeIORef)
21+import Data.Maybe (isJust)
22+#if __GLASGOW_HASKELL__ < 709
23+import Data.Monoid (mempty)
24+#endif
25+import Data.Word8 (isUpper,_colon)
26+import Network.HPACK
27+import Network.HTTP.Types (RequestHeaders,hRange)
28+import qualified Network.HTTP.Types as H
29+import Network.Socket (SockAddr)
30+import Network.Wai
31+import Network.Wai.Handler.Warp.HTTP2.Types
32+import Network.Wai.Handler.Warp.ReadInt
33+import qualified Network.Wai.Handler.Warp.Settings as S (Settings, settingsNoParsePath)
34+import Network.Wai.Internal (Request(..))
35+
36+data ValidHeaders = ValidHeaders {
37+ vhMethod :: ByteString
38+ , vhPath :: ByteString
39+ , vhAuth :: Maybe ByteString
40+ , vhCL :: Maybe Int
41+ , vhHeader :: RequestHeaders
42+ }
43+
44+type MkReq = ValidHeaders -> IO ByteString -> Request
45+
46+mkRequest :: S.Settings -> SockAddr -> MkReq
47+mkRequest settings addr (ValidHeaders m p ma _ hdr) body = req
48+ where
49+ (unparsedPath,query) = B8.break (=='?') p
50+ path = H.extractPath unparsedPath
51+ req = Request {
52+ requestMethod = m
53+ , httpVersion = http2ver
54+ , rawPathInfo = if S.settingsNoParsePath settings then unparsedPath else path
55+ , pathInfo = H.decodePathSegments path
56+ , rawQueryString = query
57+ , queryString = H.parseQuery query
58+ , requestHeaders = hdr
59+ , isSecure = True
60+ , remoteHost = addr
61+ , requestBody = body
62+ , vault = mempty
63+ , requestBodyLength = ChunkedBody -- fixme
64+ , requestHeaderHost = ma
65+ , requestHeaderRange = lookup hRange hdr
66+ }
67+
68+----------------------------------------------------------------
69+
70+data Pseudo = Pseudo {
71+ colonMethod :: !(Maybe ByteString)
72+ , colonPath :: !(Maybe ByteString)
73+ , colonAuth :: !(Maybe ByteString)
74+ , contentLen :: !(Maybe ByteString)
75+ }
76+
77+emptyPseudo :: Pseudo
78+emptyPseudo = Pseudo Nothing Nothing Nothing Nothing
79+
80+validateHeaders :: HeaderList -> Maybe ValidHeaders
81+validateHeaders hs = case pseudo hs (emptyPseudo,id) of
82+ Just (Pseudo (Just m) (Just p) ma mcl, h)
83+ -> Just $ ValidHeaders m p ma (readInt <$> mcl) h
84+ _ -> Nothing
85+ where
86+ pseudo [] (p,b) = Just (p,b [])
87+ pseudo h@((k,v):kvs) (p,b)
88+ | k == ":method" = if isJust (colonMethod p) then
89+ Nothing
90+ else
91+ pseudo kvs (p { colonMethod = Just v },b)
92+ | k == ":path" = if isJust (colonPath p) then
93+ Nothing
94+ else
95+ pseudo kvs (p { colonPath = Just v },b)
96+ | k == ":authority" = if isJust (colonAuth p) then
97+ Nothing
98+ else
99+ pseudo kvs (p { colonAuth = Just v },b)
100+ | k == ":scheme" = pseudo kvs (p,b) -- fixme: how to store :scheme?
101+ | isPseudo k = Nothing
102+ | otherwise = normal h (p,b)
103+
104+ normal [] (p,b) = Just (p,b [])
105+ normal ((k,v):kvs) (p,b)
106+ | isPseudo k = Nothing
107+ | k == "connection" = Nothing
108+ | k == "te" = if v == "trailers" then
109+ normal kvs (p, b . ((mk k,v) :))
110+ else
111+ Nothing
112+ | k == "content-length"
113+ = normal kvs (p { contentLen = Just v },b)
114+ | k == "host" = if isJust (colonAuth p) then
115+ normal kvs (p,b)
116+ else
117+ normal kvs (p { colonAuth = Just v },b)
118+ | otherwise = case BS.find isUpper k of
119+ Nothing -> normal kvs (p, b . ((mk k,v) :))
120+ Just _ -> Nothing
121+
122+ isPseudo "" = False
123+ isPseudo k = BS.head k == _colon
124+
125+
126+----------------------------------------------------------------
127+
128+newReadBody :: TQueue ByteString -> IO (IO ByteString)
129+newReadBody q = do
130+ ref <- newIORef False
131+ return $ readBody q ref
132+
133+readBody :: TQueue ByteString -> IORef Bool -> IO ByteString
134+readBody q ref = do
135+ eof <- readIORef ref
136+ if eof then
137+ return ""
138+ else do
139+ bs <- atomically $ readTQueue q
140+ when (bs == "") $ writeIORef ref True
141+ return bs
warp/Network/Wai/Handler/Warp/HTTP2/Sender.hsnew file+387
@@ -0,0 +1,387 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE BangPatterns, CPP #-}
3+{-# LANGUAGE RecordWildCards, NamedFieldPuns #-}
4+
5+module Network.Wai.Handler.Warp.HTTP2.Sender (frameSender) where
6+
7+#if __GLASGOW_HASKELL__ < 709
8+import Control.Applicative
9+#endif
10+import Control.Concurrent (putMVar, forkIO)
11+import Control.Concurrent.STM
12+import qualified Control.Exception as E
13+import Control.Monad (void, unless)
14+import qualified Data.ByteString as BS
15+import qualified Data.ByteString.Builder.Extra as B
16+import Foreign.Ptr
17+import Network.HTTP2
18+import Network.HTTP2.Priority
19+import Network.Wai
20+import Network.Wai.Handler.Warp.Buffer
21+import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
22+import Network.Wai.Handler.Warp.HTTP2.HPACK
23+import Network.Wai.Handler.Warp.HTTP2.Types
24+import Network.Wai.Handler.Warp.IORef
25+import qualified Network.Wai.Handler.Warp.Settings as S
26+import Network.Wai.Handler.Warp.Types
27+import Network.Wai.Internal (Response(..))
28+import qualified System.PosixCompat.Files as P
29+
30+#ifdef WINDOWS
31+import qualified System.IO as IO
32+#else
33+import Network.Wai.Handler.Warp.FdCache
34+import Network.Wai.Handler.Warp.SendFile (positionRead)
35+import System.Posix.Types
36+#endif
37+
38+----------------------------------------------------------------
39+
40+unlessClosed :: Context -> Connection -> Stream -> IO () -> IO ()
41+unlessClosed ctx
42+ Connection{connSendAll}
43+ strm@Stream{streamState,streamNumber}
44+ body = E.handle resetStream $ do
45+ state <- readIORef streamState
46+ unless (isClosed state) body
47+ where
48+ resetStream e = do
49+ closed ctx strm (ResetByMe e)
50+ let rst = resetFrame InternalError streamNumber
51+ connSendAll rst
52+
53+checkWindowSize :: TVar WindowSize -> TVar WindowSize -> PriorityTree Output -> Output -> Priority -> (WindowSize -> IO ()) -> IO ()
54+checkWindowSize connWindow strmWindow outQ out pri body = do
55+ cw <- atomically $ do
56+ w <- readTVar connWindow
57+ check (w > 0)
58+ return w
59+ sw <- atomically $ readTVar strmWindow
60+ if sw == 0 then
61+ void $ forkIO $ do
62+ -- checkme: if connection is closed? GC throws dead lock error?
63+ atomically $ do
64+ x <- readTVar strmWindow
65+ check (x > 0)
66+ enqueue outQ out pri
67+ else
68+ body (min cw sw)
69+
70+frameSender :: Context -> Connection -> InternalInfo -> S.Settings -> IO ()
71+frameSender ctx@Context{outputQ,connectionWindow,wait}
72+ conn@Connection{connWriteBuffer,connBufferSize,connSendAll}
73+ ii settings = do
74+ connSendAll initialFrame
75+ loop `E.finally` putMVar wait ()
76+ where
77+ initialSettings = [(SettingsMaxConcurrentStreams,recommendedConcurrency)]
78+ initialFrame = settingsFrame id initialSettings
79+ bufHeaderPayload = connWriteBuffer `plusPtr` frameHeaderLength
80+ headerPayloadLim = connBufferSize - frameHeaderLength
81+
82+ loop = dequeue outputQ >>= \(out, pri) -> switch out pri
83+
84+ switch OFinish _ = return ()
85+ switch (OGoaway frame) _ = connSendAll frame
86+ switch (OFrame frame) _ = do
87+ connSendAll frame
88+ loop
89+ switch out@(OResponse strm rsp aux) pri = unlessClosed ctx conn strm $ do
90+ checkWindowSize connectionWindow (streamWindow strm) outputQ out pri $ \lim -> do
91+ -- Header frame and Continuation frame
92+ let sid = streamNumber strm
93+ endOfStream = case aux of
94+ Persist{} -> False
95+ Oneshot hb -> not hb
96+ len <- headerContinue sid rsp endOfStream
97+ let total = len + frameHeaderLength
98+ case aux of
99+ Oneshot True -> do -- hasBody
100+ -- Data frame payload
101+ off <- sendHeadersIfNecessary total
102+ Next datPayloadLen mnext <- fillResponseBodyGetNext conn ii off lim rsp
103+ fillDataHeaderSend strm total datPayloadLen mnext pri
104+ Oneshot False -> do
105+ -- "closed" must be before "connSendAll". If not,
106+ -- the context would be switched to the receiver,
107+ -- resulting the inconsistency of concurrency.
108+ closed ctx strm Finished
109+ bufferIO connWriteBuffer total connSendAll
110+ Persist sq tvar -> do
111+ off <- sendHeadersIfNecessary total
112+ Next datPayloadLen mnext <- fillStreamBodyGetNext conn off lim sq tvar
113+ fillDataHeaderSend strm total datPayloadLen mnext pri
114+ loop
115+ switch out@(ONext strm curr) pri = unlessClosed ctx conn strm $ do
116+ checkWindowSize connectionWindow (streamWindow strm) outputQ out pri $ \lim -> do
117+ -- Data frame payload
118+ Next datPayloadLen mnext <- curr lim
119+ fillDataHeaderSend strm 0 datPayloadLen mnext pri
120+ loop
121+
122+ headerContinue sid rsp endOfStream = do
123+ builder <- hpackEncodeHeader ctx ii settings rsp
124+ (len, signal) <- B.runBuilder builder bufHeaderPayload headerPayloadLim
125+ let flag0 = case signal of
126+ B.Done -> setEndHeader defaultFlags
127+ _ -> defaultFlags
128+ flag = if endOfStream then setEndStream flag0 else flag0
129+ fillFrameHeader FrameHeaders len sid flag connWriteBuffer
130+ continue sid len signal
131+
132+ continue _ len B.Done = return len
133+ continue sid len (B.More _ writer) = do
134+ let total = len + frameHeaderLength
135+ bufferIO connWriteBuffer total connSendAll
136+ (len', signal') <- writer bufHeaderPayload headerPayloadLim
137+ let flag = case signal' of
138+ B.Done -> setEndHeader defaultFlags
139+ _ -> defaultFlags
140+ fillFrameHeader FrameContinuation len' sid flag connWriteBuffer
141+ continue sid len' signal'
142+ continue sid len (B.Chunk bs writer) = do
143+ let total = len + frameHeaderLength
144+ bufferIO connWriteBuffer total connSendAll
145+ let (bs1,bs2) = BS.splitAt headerPayloadLim bs
146+ len' = BS.length bs1
147+ void $ copy bufHeaderPayload bs1
148+ fillFrameHeader FrameContinuation len' sid defaultFlags connWriteBuffer
149+ if bs2 == "" then
150+ continue sid len' (B.More 0 writer)
151+ else
152+ continue sid len' (B.Chunk bs2 writer)
153+
154+ sendHeadersIfNecessary total = do
155+ let datPayloadOff = total + frameHeaderLength
156+ if datPayloadOff < connBufferSize then
157+ return datPayloadOff
158+ else do
159+ bufferIO connWriteBuffer total connSendAll
160+ return frameHeaderLength
161+
162+ fillDataHeaderSend strm otherLen datPayloadLen mnext pri = do
163+ -- Data frame header
164+ let sid = streamNumber strm
165+ buf = connWriteBuffer `plusPtr` otherLen
166+ total = otherLen + frameHeaderLength + datPayloadLen
167+ flag = case mnext of
168+ CFinish -> setEndStream defaultFlags
169+ _ -> defaultFlags
170+ fillFrameHeader FrameData datPayloadLen sid flag buf
171+ -- "closed" must be before "connSendAll". If not,
172+ -- the context would be switched to the receiver,
173+ -- resulting the inconsistency of concurrency.
174+ case mnext of
175+ CFinish -> closed ctx strm Finished
176+ _ -> return ()
177+ bufferIO connWriteBuffer total connSendAll
178+ atomically $ do
179+ modifyTVar' connectionWindow (subtract datPayloadLen)
180+ modifyTVar' (streamWindow strm) (subtract datPayloadLen)
181+ case mnext of
182+ CFinish -> return ()
183+ CNext next -> enqueue outputQ (ONext strm next) pri
184+ CNone -> return ()
185+
186+ fillFrameHeader ftyp len sid flag buf = encodeFrameHeaderBuf ftyp hinfo buf
187+ where
188+ hinfo = FrameHeader len flag sid
189+
190+----------------------------------------------------------------
191+
192+{-
193+ResponseFile Status ResponseHeaders FilePath (Maybe FilePart)
194+ResponseBuilder Status ResponseHeaders Builder
195+ResponseStream Status ResponseHeaders StreamingBody
196+ResponseRaw (IO ByteString -> (ByteString -> IO ()) -> IO ()) Response
197+-}
198+
199+fillResponseBodyGetNext :: Connection -> InternalInfo -> Int -> WindowSize -> Response -> IO Next
200+fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
201+ _ off lim (ResponseBuilder _ _ bb) = do
202+ let datBuf = connWriteBuffer `plusPtr` off
203+ room = min (connBufferSize - off) lim
204+ (len, signal) <- B.runBuilder bb datBuf room
205+ return $ nextForBuilder connWriteBuffer connBufferSize len signal
206+
207+#ifdef WINDOWS
208+fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
209+ _ off lim (ResponseFile _ _ path mpart) = do
210+ let datBuf = connWriteBuffer `plusPtr` off
211+ room = min (connBufferSize - off) lim
212+ (start, bytes) <- fileStartEnd path mpart
213+ -- fixme: how to close Handle? GC does it at this moment.
214+ h <- IO.openBinaryFile path IO.ReadMode
215+ IO.hSeek h IO.AbsoluteSeek start
216+ len <- IO.hGetBufSome h datBuf (mini room bytes)
217+ let bytes' = bytes - fromIntegral len
218+ return $ nextForFile len connWriteBuffer connBufferSize h bytes' (return ())
219+#else
220+fillResponseBodyGetNext Connection{connWriteBuffer,connBufferSize}
221+ ii off lim (ResponseFile _ _ path mpart) = do
222+ let Just fdcache = fdCacher ii
223+ (fd, refresh) <- getFd fdcache path
224+ let datBuf = connWriteBuffer `plusPtr` off
225+ room = min (connBufferSize - off) lim
226+ (start, bytes) <- fileStartEnd path mpart
227+ len <- positionRead fd datBuf (mini room bytes) start
228+ refresh
229+ let len' = fromIntegral len
230+ return $ nextForFile len connWriteBuffer connBufferSize fd (start + len') (bytes - len') refresh
231+#endif
232+
233+fillResponseBodyGetNext _ _ _ _ _ = error "fillResponseBodyGetNext"
234+
235+fileStartEnd :: FilePath -> Maybe FilePart -> IO (Integer, Integer)
236+fileStartEnd path Nothing = do
237+ end <- fromIntegral . P.fileSize <$> P.getFileStatus path
238+ return (0, end)
239+fileStartEnd _ (Just part) =
240+ return (filePartOffset part, filePartByteCount part)
241+
242+----------------------------------------------------------------
243+
244+fillStreamBodyGetNext :: Connection -> Int -> WindowSize -> TBQueue Sequence -> TVar Sync -> IO Next
245+fillStreamBodyGetNext Connection{connWriteBuffer,connBufferSize}
246+ off lim sq tvar = do
247+ let datBuf = connWriteBuffer `plusPtr` off
248+ room = min (connBufferSize - off) lim
249+ (leftover, cont, len) <- runStreamBuilder datBuf room sq
250+ nextForStream connWriteBuffer connBufferSize sq tvar leftover cont len
251+
252+----------------------------------------------------------------
253+
254+fillBufBuilder :: Buffer -> BufSize -> Leftover -> DynaNext
255+fillBufBuilder buf0 siz0 leftover lim = do
256+ let payloadBuf = buf0 `plusPtr` frameHeaderLength
257+ room = min (siz0 - frameHeaderLength) lim
258+ case leftover of
259+ LZero -> error "fillBufBuilder: LZero"
260+ LOne writer -> do
261+ (len, signal) <- writer payloadBuf room
262+ getNext len signal
263+ LTwo bs writer
264+ | BS.length bs <= room -> do
265+ buf1 <- copy payloadBuf bs
266+ let len1 = BS.length bs
267+ (len2, signal) <- writer buf1 (room - len1)
268+ getNext (len1 + len2) signal
269+ | otherwise -> do
270+ let (bs1,bs2) = BS.splitAt room bs
271+ void $ copy payloadBuf bs1
272+ getNext room (B.Chunk bs2 writer)
273+ where
274+ getNext l s = return $ nextForBuilder buf0 siz0 l s
275+
276+nextForBuilder :: Buffer -> BufSize -> BytesFilled -> B.Next -> Next
277+nextForBuilder _ _ len B.Done
278+ = Next len CFinish
279+nextForBuilder buf siz len (B.More _ writer)
280+ = Next len (CNext (fillBufBuilder buf siz (LOne writer)))
281+nextForBuilder buf siz len (B.Chunk bs writer)
282+ = Next len (CNext (fillBufBuilder buf siz (LTwo bs writer)))
283+
284+----------------------------------------------------------------
285+
286+runStreamBuilder :: Buffer -> BufSize -> TBQueue Sequence
287+ -> IO (Leftover, Bool, BytesFilled)
288+runStreamBuilder buf0 room0 sq = loop buf0 room0 0
289+ where
290+ loop !buf !room !total = do
291+ mbuilder <- atomically $ tryReadTBQueue sq
292+ case mbuilder of
293+ Nothing -> return (LZero, True, total)
294+ Just (SBuilder builder) -> do
295+ (len, signal) <- B.runBuilder builder buf room
296+ let !total' = total + len
297+ case signal of
298+ B.Done -> loop (buf `plusPtr` len) (room - len) total'
299+ B.More _ writer -> return (LOne writer, True, total')
300+ B.Chunk bs writer -> return (LTwo bs writer, True, total')
301+ Just SFlush -> return (LZero, True, total)
302+ Just SFinish -> return (LZero, False, total)
303+
304+fillBufStream :: Buffer -> BufSize -> Leftover -> TBQueue Sequence -> TVar Sync -> DynaNext
305+fillBufStream buf0 siz0 leftover0 sq tvar lim0 = do
306+ let payloadBuf = buf0 `plusPtr` frameHeaderLength
307+ room0 = min (siz0 - frameHeaderLength) lim0
308+ case leftover0 of
309+ LZero -> do
310+ (leftover, cont, len) <- runStreamBuilder payloadBuf room0 sq
311+ getNext leftover cont len
312+ LOne writer -> write writer payloadBuf room0 0
313+ LTwo bs writer
314+ | BS.length bs <= room0 -> do
315+ buf1 <- copy payloadBuf bs
316+ let len = BS.length bs
317+ write writer buf1 (room0 - len) len
318+ | otherwise -> do
319+ let (bs1,bs2) = BS.splitAt room0 bs
320+ void $ copy payloadBuf bs1
321+ getNext (LTwo bs2 writer) True room0
322+ where
323+ getNext = nextForStream buf0 siz0 sq tvar
324+ write writer1 buf room sofar = do
325+ (len, signal) <- writer1 buf room
326+ case signal of
327+ B.Done -> do
328+ (leftover, cont, extra) <- runStreamBuilder (buf `plusPtr` len) (room - len) sq
329+ let !total = sofar + len + extra
330+ getNext leftover cont total
331+ B.More _ writer -> do
332+ let !total = sofar + len
333+ getNext (LOne writer) True total
334+ B.Chunk bs writer -> do
335+ let !total = sofar + len
336+ getNext (LTwo bs writer) True total
337+
338+nextForStream :: Buffer -> BufSize -> TBQueue Sequence -> TVar Sync
339+ -> Leftover -> Bool -> BytesFilled
340+ -> IO Next
341+nextForStream _ _ _ tvar _ False len = do
342+ atomically $ writeTVar tvar SyncFinish
343+ return $ Next len CFinish
344+nextForStream buf siz sq tvar LZero True len = do
345+ atomically $ writeTVar tvar $ SyncNext (fillBufStream buf siz LZero sq tvar)
346+ return $ Next len CNone
347+nextForStream buf siz sq tvar leftover True len =
348+ return $ Next len (CNext (fillBufStream buf siz leftover sq tvar))
349+
350+----------------------------------------------------------------
351+
352+#ifdef WINDOWS
353+fillBufFile :: Buffer -> BufSize -> IO.Handle -> Integer -> IO () -> DynaNext
354+fillBufFile buf siz h bytes refresh lim = do
355+ let payloadBuf = buf `plusPtr` frameHeaderLength
356+ room = min (siz - frameHeaderLength) lim
357+ len <- IO.hGetBufSome h payloadBuf room
358+ refresh
359+ let bytes' = bytes - fromIntegral len
360+ return $ nextForFile len buf siz h bytes' refresh
361+
362+nextForFile :: BytesFilled -> Buffer -> BufSize -> IO.Handle -> Integer -> IO () -> Next
363+nextForFile 0 _ _ _ _ _ = Next 0 CFinish
364+nextForFile len _ _ _ 0 _ = Next len CFinish
365+nextForFile len buf siz h bytes refresh =
366+ Next len (CNext (fillBufFile buf siz h bytes refresh))
367+#else
368+fillBufFile :: Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> DynaNext
369+fillBufFile buf siz fd start bytes refresh lim = do
370+ let payloadBuf = buf `plusPtr` frameHeaderLength
371+ room = min (siz - frameHeaderLength) lim
372+ len <- positionRead fd payloadBuf (mini room bytes) start
373+ let len' = fromIntegral len
374+ refresh
375+ return $ nextForFile len buf siz fd (start + len') (bytes - len') refresh
376+
377+nextForFile :: BytesFilled -> Buffer -> BufSize -> Fd -> Integer -> Integer -> IO () -> Next
378+nextForFile 0 _ _ _ _ _ _ = Next 0 CFinish
379+nextForFile len _ _ _ _ 0 _ = Next len CFinish
380+nextForFile len buf siz fd start bytes refresh =
381+ Next len (CNext (fillBufFile buf siz fd start bytes refresh))
382+#endif
383+
384+mini :: Int -> Integer -> Int
385+mini i n
386+ | fromIntegral i < n = i
387+ | otherwise = fromIntegral n
warp/Network/Wai/Handler/Warp/HTTP2/Types.hsnew file+243
@@ -0,0 +1,243 @@
1+{-# LANGUAGE OverloadedStrings, CPP #-}
2+{-# LANGUAGE RecordWildCards, NamedFieldPuns #-}
3+
4+module Network.Wai.Handler.Warp.HTTP2.Types where
5+
6+import Blaze.ByteString.Builder (Builder)
7+#if __GLASGOW_HASKELL__ < 709
8+import Control.Applicative ((<$>),(<*>))
9+#endif
10+import Control.Concurrent
11+import Control.Concurrent.STM
12+import Control.Exception (SomeException)
13+import Control.Monad (void)
14+import Control.Reaper
15+import Data.ByteString (ByteString)
16+import qualified Data.ByteString as BS
17+import qualified Data.ByteString.Builder.Extra as B
18+import Data.IntMap.Strict (IntMap, IntMap)
19+import qualified Data.IntMap.Strict as M
20+import qualified Network.HTTP.Types as H
21+import Network.Wai (Request, Response)
22+import Network.Wai.Handler.Warp.IORef
23+import Network.Wai.Handler.Warp.Types
24+
25+import Network.HTTP2
26+import Network.HTTP2.Priority
27+import Network.HPACK
28+
29+----------------------------------------------------------------
30+
31+http2ver :: H.HttpVersion
32+http2ver = H.HttpVersion 2 0
33+
34+isHTTP2 :: Transport -> Bool
35+isHTTP2 TCP = False
36+isHTTP2 tls = useHTTP2
37+ where
38+ useHTTP2 = case tlsNegotiatedProtocol tls of
39+ Nothing -> False
40+ Just proto -> "h2-" `BS.isPrefixOf` proto
41+
42+----------------------------------------------------------------
43+
44+data Input = Input Stream Request Priority
45+
46+----------------------------------------------------------------
47+
48+data Control a = CFinish
49+ | CNext a
50+ | CNone
51+
52+instance Show (Control a) where
53+ show CFinish = "CFinish"
54+ show (CNext _) = "CNext"
55+ show CNone = "CNone"
56+
57+type DynaNext = WindowSize -> IO Next
58+
59+type BytesFilled = Int
60+
61+data Next = Next BytesFilled (Control DynaNext)
62+
63+data Output = OFinish
64+ | OGoaway ByteString
65+ | OFrame ByteString
66+ | OResponse Stream Response Aux
67+ | ONext Stream DynaNext
68+
69+----------------------------------------------------------------
70+
71+data Sequence = SFinish
72+ | SFlush
73+ | SBuilder Builder
74+
75+data Sync = SyncNone
76+ | SyncFinish
77+ | SyncNext DynaNext
78+
79+data Aux = Oneshot Bool
80+ | Persist (TBQueue Sequence) (TVar Sync)
81+
82+data Leftover = LZero
83+ | LOne B.BufferWriter
84+ | LTwo ByteString B.BufferWriter
85+
86+----------------------------------------------------------------
87+
88+-- | The context for HTTP/2 connection.
89+data Context = Context {
90+ http2settings :: IORef Settings
91+ , streamTable :: StreamTable
92+ , concurrency :: IORef Int
93+ -- | RFC 7540 says "Other frames (from any stream) MUST NOT
94+ -- occur between the HEADERS frame and any CONTINUATION
95+ -- frames that might follow". This field is used to implement
96+ -- this requirement.
97+ , continued :: IORef (Maybe StreamId)
98+ , currentStreamId :: IORef StreamId
99+ , inputQ :: TQueue Input
100+ , outputQ :: PriorityTree Output
101+ , encodeDynamicTable :: IORef DynamicTable
102+ , decodeDynamicTable :: IORef DynamicTable
103+ -- | This is used to synchronization of sender and receiver
104+ -- when the HTTP/2 connection is terminated.
105+ , wait :: MVar ()
106+ , connectionWindow :: TVar WindowSize
107+ }
108+
109+----------------------------------------------------------------
110+
111+newContext :: IO Context
112+newContext = Context <$> newIORef defaultSettings
113+ <*> initialize 10 -- fixme: hard coding: 10
114+ <*> newIORef 0
115+ <*> newIORef Nothing
116+ <*> newIORef 0
117+ <*> newTQueueIO
118+ <*> newPriorityTree
119+ <*> (newDynamicTableForEncoding defaultDynamicTableSize >>= newIORef)
120+ <*> (newDynamicTableForDecoding defaultDynamicTableSize >>= newIORef)
121+ <*> newEmptyMVar
122+ <*> newTVarIO defaultInitialWindowSize
123+
124+clearContext :: Context -> IO ()
125+clearContext ctx = stop $ streamTable ctx
126+
127+----------------------------------------------------------------
128+
129+data OpenState =
130+ JustOpened
131+ | Continued [HeaderBlockFragment]
132+ Int -- Total size
133+ Int -- The number of continuation frames
134+ Bool -- End of stream
135+ Priority
136+ | NoBody HeaderList Priority
137+ | HasBody HeaderList Priority
138+ | Body (TQueue ByteString)
139+
140+data ClosedCode = Finished
141+ | Killed
142+ | Reset ErrorCodeId
143+ | ResetByMe SomeException
144+ deriving Show
145+
146+data StreamState =
147+ Idle
148+ | Open OpenState
149+ | HalfClosed
150+ | Closed ClosedCode
151+
152+isIdle :: StreamState -> Bool
153+isIdle Idle = True
154+isIdle _ = False
155+
156+isOpen :: StreamState -> Bool
157+isOpen Open{} = True
158+isOpen _ = False
159+
160+isHalfClosed :: StreamState -> Bool
161+isHalfClosed HalfClosed = True
162+isHalfClosed _ = False
163+
164+isClosed :: StreamState -> Bool
165+isClosed Closed{} = True
166+isClosed _ = False
167+
168+instance Show StreamState where
169+ show Idle = "Idle"
170+ show Open{} = "Open"
171+ show HalfClosed = "HalfClosed"
172+ show (Closed e) = "Closed: " ++ show e
173+
174+----------------------------------------------------------------
175+
176+data Stream = Stream {
177+ streamNumber :: StreamId
178+ , streamState :: IORef StreamState
179+ -- Next two fields are for error checking.
180+ , streamContentLength :: IORef (Maybe Int)
181+ , streamBodyLength :: IORef Int
182+ , streamWindow :: TVar WindowSize
183+ }
184+
185+instance Show Stream where
186+ show s = show (streamNumber s)
187+
188+newStream :: StreamId -> WindowSize -> IO Stream
189+newStream sid win = Stream sid <$> newIORef Idle
190+ <*> newIORef Nothing
191+ <*> newIORef 0
192+ <*> newTVarIO win
193+
194+----------------------------------------------------------------
195+
196+opened :: Context -> Stream -> IO ()
197+opened Context{concurrency} Stream{streamState} = do
198+ atomicModifyIORef' concurrency (\x -> ((x+1),()))
199+ writeIORef streamState (Open JustOpened)
200+
201+closed :: Context -> Stream -> ClosedCode -> IO ()
202+closed Context{concurrency} Stream{streamState} cc = do
203+ atomicModifyIORef' concurrency (\x -> ((x-1),()))
204+ writeIORef streamState (Closed cc)
205+
206+----------------------------------------------------------------
207+
208+type StreamTable = Reaper (IntMap Stream) (M.Key, Stream)
209+
210+initialize :: Int -> IO StreamTable
211+initialize duration = mkReaper settings
212+ where
213+ settings = defaultReaperSettings {
214+ reaperAction = clean
215+ , reaperDelay = duration * 1000000
216+ , reaperCons = uncurry M.insert
217+ , reaperNull = M.null
218+ , reaperEmpty = M.empty
219+ }
220+
221+clean :: IntMap Stream -> IO (IntMap Stream -> IntMap Stream)
222+clean old = do
223+ new <- M.fromAscList <$> prune oldlist []
224+ return $ M.union new
225+ where
226+ oldlist = M.toDescList old
227+ prune [] lst = return lst
228+ prune (x@(_,s):xs) lst = do
229+ st <- readIORef (streamState s)
230+ if isClosed st then
231+ prune xs lst
232+ else
233+ prune xs (x:lst)
234+
235+insert :: StreamTable -> M.Key -> Stream -> IO ()
236+insert strmtbl k v = reaperAdd strmtbl (k,v)
237+
238+search :: StreamTable -> M.Key -> IO (Maybe Stream)
239+search strmtbl k = M.lookup k <$> reaperRead strmtbl
240+
241+stop :: StreamTable -> IO ()
242+stop strmtbl = void $ reaperStop strmtbl
243+
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hsnew file+123
@@ -0,0 +1,123 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE RecordWildCards, NamedFieldPuns #-}
3+{-# LANGUAGE PatternGuards #-}
4+
5+module Network.Wai.Handler.Warp.HTTP2.Worker where
6+
7+import Control.Concurrent
8+import Control.Concurrent.STM
9+import Control.Exception as E
10+import Control.Monad (void, when)
11+import Data.Typeable
12+import qualified Network.HTTP.Types as H
13+import Network.HTTP2
14+import Network.HTTP2.Priority
15+import Network.Wai
16+import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
17+import Network.Wai.Handler.Warp.HTTP2.Types
18+import Network.Wai.Handler.Warp.IORef
19+import qualified Network.Wai.Handler.Warp.Response as R
20+import qualified Network.Wai.Handler.Warp.Settings as S
21+import qualified Network.Wai.Handler.Warp.Timeout as T
22+import Network.Wai.Internal (Response(..), ResponseReceived(..), ResponseReceived(..))
23+
24+----------------------------------------------------------------
25+
26+type Responder = T.Handle -> Stream -> Priority -> Request -> Response -> IO ResponseReceived
27+
28+response :: Context -> Responder
29+response Context{outputQ} th strm pri req rsp = do
30+ case rsp of
31+ ResponseStream _ _ strmbdy -> do
32+ -- fixme: spawn a new worker thread.
33+ --
34+ -- We must not exit this WAI application.
35+ -- If the application exits, streaming would be also closed.
36+ -- Since 'StreamingBody' is loop, we cannot control it.
37+ -- So, let's serialize 'Builder' with a designated queue.
38+ sq <- newTBQueueIO 10
39+ tvar <- newTVarIO SyncNone
40+ enqueue outputQ (OResponse strm rsp (Persist sq tvar)) pri
41+ let push b = do
42+ atomically $ writeTBQueue sq (SBuilder b)
43+ T.tickle th
44+ flush = atomically $ writeTBQueue sq SFlush
45+ -- Since we must not enqueue an empty queue to the priority
46+ -- queue, we spawn a thread to ensure that the designated
47+ -- queue is not empty.
48+ void $ forkIO $ waiter tvar sq (enqueue outputQ) strm pri
49+ strmbdy push flush
50+ atomically $ writeTBQueue sq SFinish
51+ _ -> do
52+ let hasBody = requestMethod req /= H.methodHead
53+ || R.hasBody (responseStatus rsp)
54+ enqueue outputQ (OResponse strm rsp (Oneshot hasBody)) pri
55+ return ResponseReceived
56+
57+data Break = Break deriving (Show, Typeable)
58+
59+instance Exception Break
60+
61+worker :: Context -> S.Settings -> T.Manager -> Application -> Responder -> IO ()
62+worker ctx@Context{inputQ,outputQ} set tm app responder = do
63+ tid <- myThreadId
64+ ref <- newIORef Nothing
65+ let setup = T.register tm $ E.throwTo tid Break
66+ bracket setup T.cancel $ go ref
67+ where
68+ go ref th = do
69+ ex <- E.try $ do
70+ T.pause th
71+ Input strm req pri <- atomically $ readTQueue inputQ
72+ writeIORef ref $ Just (strm,req)
73+ T.resume th
74+ T.tickle th
75+ app req $ responder th strm pri req
76+ cont <- case ex of
77+ Right ResponseReceived -> return True
78+ Left e@(SomeException _)
79+ | Just Break <- fromException e -> do
80+ cleanup ref Nothing
81+ return True
82+ -- killed by the sender
83+ | Just ThreadKilled <- fromException e -> do
84+ cleanup ref Nothing
85+ return False
86+ | otherwise -> do
87+ cleanup ref (Just e)
88+ return True
89+ when cont $ go ref th
90+ cleanup ref me = do
91+ m <- readIORef ref
92+ case m of
93+ Nothing -> return ()
94+ Just (strm,req) -> do
95+ closed ctx strm Killed
96+ let frame = resetFrame InternalError (streamNumber strm)
97+ enqueue outputQ (OFrame frame) highestPriority
98+ case me of
99+ Nothing -> return ()
100+ Just e -> S.settingsOnException set (Just req) e
101+ writeIORef ref Nothing
102+
103+waiter :: TVar Sync -> TBQueue Sequence
104+ -> (Output -> Priority -> IO ()) -> Stream -> Priority
105+ -> IO ()
106+waiter tvar sq enq strm pri = do
107+ mx <- atomically $ do
108+ mout <- readTVar tvar
109+ case mout of
110+ SyncNone -> retry
111+ SyncNext nxt -> do
112+ writeTVar tvar SyncNone
113+ return $ Just nxt
114+ SyncFinish -> return Nothing
115+ case mx of
116+ Nothing -> return ()
117+ Just next -> do
118+ atomically $ do
119+ isEmpty <- isEmptyTBQueue sq
120+ when isEmpty retry
121+ enq (ONext strm next) pri
122+ waiter tvar sq enq strm pri
123+
warp/Network/Wai/Handler/Warp/Recv.hs+23−15
@@ -59,26 +59,34 @@ receiveN ref recv recvBuf size = do
5959----------------------------------------------------------------
6060
6161spell :: ByteString -> BufSize -> IO ByteString -> RecvBuf -> IO (ByteString, ByteString)
62−spell initial siz recv recvBuf
63− | siz <= len0 = return $ BS.splitAt siz initial
64− | siz <= 4096 = do
65− bs <- recv
66− if bs == "" then
67− return ("", "")
68− else do
69− let (bs1, leftover) = BS.splitAt (siz - len0) bs
70− return (BS.append initial bs1, leftover)
71− | otherwise = do
72− bs@(PS fptr _ _) <- mallocBS siz
62+spell init0 siz0 recv recvBuf
63+ | siz0 <= len0 = return $ BS.splitAt siz0 init0
64+ -- fixme: hard coding 4096
65+ | siz0 <= 4096 = loop [init0] (siz0 - len0)
66+ | otherwise = do
67+ bs@(PS fptr _ _) <- mallocBS siz0
7368 withForeignPtr fptr $ \ptr -> do
74− ptr' <- copy ptr initial
75− full <- recvBuf ptr' (siz - len0)
69+ ptr' <- copy ptr init0
70+ full <- recvBuf ptr' (siz0 - len0)
7671 if full then
7772 return (bs, "")
78− else
73+ else
7974 return ("", "") -- fixme
8075 where
81− len0 = BS.length initial
76+ len0 = BS.length init0
77+ loop bss siz = do
78+ bs <- recv
79+ let len = BS.length bs
80+ if len == 0 then
81+ return ("", "")
82+ else if len >= siz then do
83+ let (consume, leftover) = BS.splitAt siz bs
84+ ret = BS.concat $ reverse (consume : bss)
85+ return (ret, leftover)
86+ else do
87+ let bss' = bs : bss
88+ siz' = siz - len
89+ loop bss' siz'
8290
8391receive :: Socket -> BufferPool -> Recv
8492receive sock pool = withBufferPool pool $ \ (ptr, size) -> do
warp/Network/Wai/Handler/Warp/RequestHeader.hs+9−6
@@ -51,6 +51,8 @@ parseHeaderLines (firstLine:otherLines) = do
5151-- *** Exception: Warp: Invalid first line of request: "GET "
5252-- >>> parseRequestLine "GET /NotHTTP UNKNOWN/1.1"
5353-- *** Exception: Warp: Request line specified a non-HTTP request
54+-- >>> parseRequestLine "PRI * HTTP/2.0"
55+-- ("PRI","*","",HTTP/2.0)
5456parseRequestLine :: ByteString
5557 -> IO (H.Method
5658 ,ByteString -- Path
@@ -101,12 +103,13 @@ parseRequestLine requestLine@(PS fptr off len) = withForeignPtr fptr $ \ptr -> d
101103 check httpptr 4 47 -- '/'
102104 check httpptr 6 46 -- '.'
103105 httpVersion httpptr = do
104− major <- peek $ httpptr `plusPtr` 5
105− minor <- peek $ httpptr `plusPtr` 7
106− return $ if major == (49 :: Word8) && minor == (49 :: Word8) then
107− H.http11
108− else
109− H.http10
106+ major <- peek (httpptr `plusPtr` 5) :: IO Word8
107+ minor <- peek (httpptr `plusPtr` 7) :: IO Word8
108+ let version
109+ | major == 49 = if minor == 49 then H.http11 else H.http10
110+ | major == 50 && minor == 48 = H.HttpVersion 2 0
111+ | otherwise = H.http10
112+ return version
110113 bs ptr p0 p1 = PS fptr o l
111114 where
112115 o = p0 `minusPtr` ptr
warp/Network/Wai/Handler/Warp/Response.hs+3
@@ -9,6 +9,9 @@ module Network.Wai.Handler.Warp.Response (
99 , fileRange -- for testing
1010 , warpVersion
1111 , defaultServerValue
12+ , addDate
13+ , addServer
14+ , hasBody
1215 ) where
1316
1417#ifndef MIN_VERSION_base
warp/Network/Wai/Handler/Warp/Run.hs+27−10
@@ -25,6 +25,7 @@ import Network.Wai.Handler.Warp.Buffer
2525import Network.Wai.Handler.Warp.Counter
2626import qualified Network.Wai.Handler.Warp.Date as D
2727import qualified Network.Wai.Handler.Warp.FdCache as F
28+import Network.Wai.Handler.Warp.HTTP2
2829import Network.Wai.Handler.Warp.Header
2930import Network.Wai.Handler.Warp.ReadInt
3031import Network.Wai.Handler.Warp.Recv
@@ -57,6 +58,7 @@ socketConnection s = do
5758 , connSendFile = sendFile s writeBuf bufferSize sendall
5859 , connClose = sClose s >> freeBuffer writeBuf
5960 , connRecv = receive s bufferPool
61+ , connRecvBuf = receiveBuf s
6062 , connWriteBuffer = writeBuf
6163 , connBufferSize = bufferSize
6264 }
@@ -268,7 +270,7 @@ fork set mkConn addr app dc fc tm counter = settingsFork set $ \ unmask ->
268270 -- cancel that handler as soon as we exit.
269271 bracket (T.registerKillThread tm) T.cancel $ \th ->
270272
271− let ii = InternalInfo th fc dc
273+ let ii = InternalInfo th tm fc dc
272274 -- We now have fully registered a connection close handler
273275 -- in the case of all exceptions, so it is safe to one
274276 -- again allow async exceptions.
@@ -297,13 +299,28 @@ serveConnection :: Connection
297299 -> Application
298300 -> IO ()
299301serveConnection conn ii origAddr transport settings app = do
300− istatus <- newIORef False
301− src <- mkSource (connSource conn th istatus)
302− addr <- getProxyProtocolAddr src
303− http1 addr istatus src `E.catch` \e -> do
304− sendErrorResponse addr istatus e
305− throwIO (e :: SomeException)
306−
302+ -- fixme: Upgrading to HTTP/2 should be supported.
303+ (h2,bs) <- if isHTTP2 transport then
304+ return (True, "")
305+ else do
306+ bs0 <- connRecv conn
307+ if S.length bs0 >= 4 && "PRI " `S.isPrefixOf` bs0 then
308+ return (True, bs0)
309+ else
310+ return (False, bs0)
311+ if h2 then do
312+ recvN <- makeReceiveN bs (connRecv conn) (connRecvBuf conn)
313+ -- fixme: origAddr
314+ http2 conn ii origAddr transport settings recvN app
315+ else do
316+ istatus <- newIORef False
317+ src <- mkSource (wrappedRecv conn th istatus)
318+ writeIORef istatus True
319+ leftoverSource src bs
320+ addr <- getProxyProtocolAddr src
321+ http1 addr istatus src `E.catch` \e -> do
322+ sendErrorResponse addr istatus e
323+ throwIO (e :: SomeException)
307324 where
308325 getProxyProtocolAddr src =
309326 case settingsProxyProtocol settings of
@@ -453,8 +470,8 @@ flushBody src =
453470 | toRead' >= 0 -> loop toRead'
454471 | otherwise -> return False
455472
456−connSource :: Connection -> T.Handle -> IORef Bool -> IO ByteString
457−connSource Connection { connRecv = recv } th istatus = do
473+wrappedRecv :: Connection -> T.Handle -> IORef Bool -> IO ByteString
474+wrappedRecv Connection { connRecv = recv } th istatus = do
458475 bs <- recv
459476 unless (S.null bs) $ do
460477 writeIORef istatus True
warp/Network/Wai/Handler/Warp/SendFile.hs+6−4
@@ -4,6 +4,9 @@ module Network.Wai.Handler.Warp.SendFile (
44 sendFile
55 , readSendFile
66 , packHeader -- for testing
7+#ifndef WINDOWS
8+ , positionRead
9+#endif
710 ) where
811
912import Control.Monad (void)
@@ -23,7 +26,6 @@ import qualified System.IO as IO
2326# if __GLASGOW_HASKELL__ < 709
2427import Control.Applicative ((<$>))
2528# endif
26−import Data.Word (Word8)
2729import Control.Exception
2830import Foreign.C.Types
2931import Foreign.Ptr (Ptr, castPtr, plusPtr)
@@ -140,10 +142,10 @@ readSendFile buf siz send fid off0 len0 hook headers =
140142 hook
141143 loop fd (len - n') (off + n')
142144
143−positionRead :: Fd -> Ptr Word8 -> Int -> Integer -> IO Int
144−positionRead (Fd fd) buf siz off =
145+positionRead :: Fd -> Buffer -> BufSize -> Integer -> IO Int
146+positionRead fd buf siz off =
145147 fromIntegral <$> c_pread fd (castPtr buf) (fromIntegral siz) (fromIntegral off)
146148
147149foreign import ccall unsafe "pread"
148− c_pread :: CInt -> Ptr CChar -> ByteCount -> FileOffset -> IO ByteCount -- fixme
150+ c_pread :: Fd -> Ptr CChar -> ByteCount -> FileOffset -> IO CSsize
149151#endif
warp/Network/Wai/Handler/Warp/Types.hs+4
@@ -115,6 +115,9 @@ data Connection = Connection {
115115 , connClose :: IO ()
116116 -- | The connection receiving function. This returns "" for EOF.
117117 , connRecv :: Recv
118+ -- | The connection receiving function. This tries to fill the buffer.
119+ -- This returns when the buffer is filled or reaches EOF.
120+ , connRecvBuf :: RecvBuf
118121 -- | The write buffer.
119122 , connWriteBuffer :: Buffer
120123 -- | The size of the write buffer.
@@ -126,6 +129,7 @@ data Connection = Connection {
126129-- | Internal information.
127130data InternalInfo = InternalInfo {
128131 threadHandle :: T.Handle
132+ , timeoutManager :: T.Manager
129133 , fdCacher :: Maybe F.MutableFdCache
130134 , dateCacher :: D.DateCache
131135 }
warp/warp.cabal+14
@@ -37,9 +37,11 @@ Library
3737 , blaze-builder >= 0.3.3 && < 0.5
3838 , bytestring >= 0.9.1.4
3939 , case-insensitive >= 0.2
40+ , containers
4041 , ghc-prim
4142 , http-types >= 0.8.5
4243 , iproute >= 1.3.1
44+ , http2 >= 1.0.2
4345 , simple-sendfile >= 0.2.7 && < 0.3
4446 , unix-compat >= 0.2
4547 , wai >= 3.0 && < 3.1
@@ -47,6 +49,7 @@ Library
4749 , streaming-commons >= 0.1.10
4850 , vault >= 0.3
4951 , stm >= 2.3
52+ , word8
5053 if flag(network-bytestring)
5154 Build-Depends: network >= 2.2.1.5 && < 2.2.3
5255 , network-bytestring >= 0.1.3 && < 0.1.4
@@ -64,6 +67,14 @@ Library
6467 Network.Wai.Handler.Warp.Counter
6568 Network.Wai.Handler.Warp.Date
6669 Network.Wai.Handler.Warp.FdCache
70+ Network.Wai.Handler.Warp.HTTP2
71+ Network.Wai.Handler.Warp.HTTP2.EncodeFrame
72+ Network.Wai.Handler.Warp.HTTP2.HPACK
73+ Network.Wai.Handler.Warp.HTTP2.Receiver
74+ Network.Wai.Handler.Warp.HTTP2.Request
75+ Network.Wai.Handler.Warp.HTTP2.Sender
76+ Network.Wai.Handler.Warp.HTTP2.Types
77+ Network.Wai.Handler.Warp.HTTP2.Worker
6778 Network.Wai.Handler.Warp.Header
6879 Network.Wai.Handler.Warp.IO
6980 Network.Wai.Handler.Warp.IORef
@@ -148,6 +159,9 @@ Test-Suite spec
148159 , stm >= 2.3
149160 , directory
150161 , process
162+ , containers
163+ , http2 >= 1.0.2
164+ , word8
151165 if flag(use-bytestring-builder)
152166 Build-Depends: bytestring < 0.10.2.0
153167 , bytestring-builder