on master help sign in
Merge pull request #432 from yesodweb/http2-push

Commits lost in the confusion of #426

commitac96dc569708a5a3c7df9906ddd2a9f58ff30a2d authorKazu Yamamoto <kazu@iij.ad.jp> dateMon 14 Sep 2015, 14:09 (11 years ago) parent764579149e7ac4
7 files changed+168−116
wai/Network/Wai/HTTP2.hs+117−55 wai/Network/Wai/Internal.hs+7−3 wai/wai.cabal+1 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+28−28 warp/Network/Wai/Handler/Warp/Response.hs+7−19 warp/Network/Wai/Handler/Warp/Run.hs+1−4 warp/test/ResponseSpec.hs+7−7
wai/Network/Wai/HTTP2.hs+117−55
@@ -1,5 +1,6 @@
11{-# LANGUAGE OverloadedStrings, RankNTypes #-}
22{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DeriveDataTypeable #-}
34
45-- | An HTTP\/2-aware variant of the 'Network.Wai.Application' type. Compared
56-- to the original, this exposes the new functionality of server push and
@@ -27,9 +28,9 @@ module Network.Wai.HTTP2
2728 -- * Applications
2829 HTTP2Application
2930 -- * Responder
30− , Responder
31+ , Responder(..)
32+ , RespondFunc
3133 , Body
32− , BodyOf
3334 , Chunk(..)
3435 , Trailers
3536 -- * Server push
@@ -39,22 +40,31 @@ module Network.Wai.HTTP2
3940 -- * Conveniences
4041 , promoteApplication
4142 -- ** Responders
42− , responder
43+ , respond
44+ , respondCont
45+ , respondIO
4346 , respondFile
4447 , respondFilePart
4548 , respondNotFound
49+ , respondWith
4650 -- ** Stream Bodies
47− , SimpleBody
4851 , streamFilePart
4952 , streamBuilder
5053 , streamSimple
5154 ) where
5255
53−#if __GLASGOW_HASKELL__ < 710
54−import Data.Functor ((<$))
55−#endif
5656import Blaze.ByteString.Builder (Builder)
57+import Control.Exception (Exception, throwIO)
58+import Control.Monad.Trans.Cont (ContT(..))
5759import Data.ByteString (ByteString)
60+#if __GLASGOW_HASKELL__ < 709
61+import Data.Functor ((<$>))
62+#endif
63+import Data.IORef (newIORef, readIORef, writeIORef)
64+#if __GLASGOW_HASKELL__ < 709
65+import Data.Monoid (mempty)
66+#endif
67+import Data.Typeable (Typeable)
5868import qualified Network.HTTP.Types as H
5969
6070import Network.Wai (Application)
@@ -63,6 +73,7 @@ import Network.Wai.Internal
6373 , Request(requestHeaders)
6474 , Response(..)
6575 , ResponseReceived(..)
76+ , StreamingBody
6677 , adjustForFilePart
6778 , chooseFilePart
6879 , tryGetFileSize
@@ -89,13 +100,12 @@ data Chunk = FileChunk FilePath FilePart | BuilderChunk Builder
89100
90101-- | The streaming body of a response. Equivalent to
91102-- '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
103+-- return the stream's trailers.
104+type Body = (Chunk -> IO ()) -> IO () -> IO Trailers
94105
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
106+-- | Given to 'Responders'; provide a status, headers, and a stream body, and
107+-- we'll give you a token proving you called the 'RespondFunc'.
108+type RespondFunc s = H.Status -> H.ResponseHeaders -> Body -> IO s
99109
100110-- | The result of an 'HTTP2Application'; or, alternately, an application
101111-- that's independent of the request. This is a continuation-passing style
@@ -104,20 +114,30 @@ type BodyOf c a = (c -> IO ()) -> IO () -> IO a
104114--
105115-- The respond function is similar to the one in 'Network.Wai.Application', but
106116-- 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
117+-- also produces trailers for the stream.
118+newtype Responder = Responder
119+ { runResponder :: forall s. RespondFunc s -> IO s }
110120
111121-- | A function given to an 'HTTP2Application' to initiate a server-pushed
112122-- stream. Its argument is the same as the result of an 'HTTP2Application', so
113123-- you can either implement the response inline, or call your own application
114124-- to create the response.
125+--
126+-- The result is 'True' if the @PUSH_PROMISE@ frame will be sent, or 'False' if
127+-- it will not. This can happen if server push is disabled, the concurrency
128+-- limit of server-initiated streams is reached, or the associated stream has
129+-- already been closed.
130+--
131+-- This function shall ensure that stream data provided after it returns will
132+-- be sent after the @PUSH_PROMISE@ frame, so that servers can implement the
133+-- requirement that any pushed stream for a resource be initiated before
134+-- sending DATA frames that reference it.
115135type PushFunc = PushPromise -> Responder -> IO Bool
116136
117137-- | Create the 'H.RequestHeaders' corresponding to the given 'PushPromise'.
118138--
119−-- This is primarily useful for Wai handlers like Warp, and application
120−-- implementers are unlikely to need it.
139+-- This is primarily useful for WAI handlers like Warp, and application
140+-- implementers are unlikely to use it directly.
121141promiseHeaders :: PushPromise -> H.RequestHeaders
122142promiseHeaders p =
123143 [ (":method", promisedMethod p)
@@ -129,19 +149,21 @@ promiseHeaders p =
129149-- | Create a response body consisting of a single range of a file. Does not
130150-- set Content-Length or Content-Range headers. For that, use
131151-- 'respondFilePart' or 'respondFile'.
132−streamFilePart :: FilePath -> FilePart -> Body ()
133−streamFilePart path part write _ = write $ FileChunk path part
152+streamFilePart :: FilePath -> FilePart -> Body
153+streamFilePart path part write _ = write (FileChunk path part) >> return []
134154
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.
155+-- | Respond with a single range of a file, adding the Accept-Ranges,
156+-- Content-Length and Content-Range headers and changing the status to 206 as
157+-- appropriate.
137158--
138159-- If you want the range to be inferred automatically from the Range header,
139−-- use 'respondFile' instead.
160+-- use 'respondFile' instead. On the other hand, if you want to avoid the
161+-- automatic header and status adjustments, use 'respond' and 'streamFilePart'
162+-- directly.
140163respondFilePart :: H.Status -> H.ResponseHeaders -> FilePath -> FilePart -> Responder
141−respondFilePart s h path part respond = do
164+respondFilePart s h path part = Responder $ \k -> do
142165 let (s', h') = adjustForFilePart s h part
143− respond s' h' $ streamFilePart path part
144− return []
166+ k s' h' $ streamFilePart path part
145167
146168-- | Serve the requested range of the specified file (based on the Range
147169-- header), using the given 'H.Status' and 'H.ResponseHeaders' as a base. If
@@ -150,11 +172,11 @@ respondFilePart s h path part respond = do
150172-- the status will be replaced with 206 and the Content-Range header will be
151173-- added. The Content-Length header will always be added.
152174respondFile :: H.Status -> H.ResponseHeaders -> FilePath -> H.RequestHeaders -> Responder
153−respondFile s h path req respond = do
175+respondFile s h path reqHdrs = Responder $ \k -> do
154176 fileSize <- tryGetFileSize path
155177 case fileSize of
156− Left _ -> respondNotFound h respond
157− Right size -> respondFileExists s h path size req respond
178+ Left _ -> runResponder (respondNotFound h) k
179+ Right size -> runResponder (respondFileExists s h path size reqHdrs) k
158180
159181-- As 'respondFile', but with prior knowledge of the file's existence and size.
160182respondFileExists :: H.Status -> H.ResponseHeaders -> FilePath -> Integer -> H.RequestHeaders -> Responder
@@ -163,47 +185,87 @@ respondFileExists s h path size reqHdrs =
163185
164186-- | Respond with a minimal 404 page with the given headers.
165187respondNotFound :: H.ResponseHeaders -> Responder
166−respondNotFound h respond = do
167− respond H.notFound404 h' $ streamBuilder "File not found."
168− return []
188+respondNotFound h = Responder $ \k -> k H.notFound404 h' $
189+ streamBuilder "File not found."
169190 where
170191 contentType = (H.hContentType, "text/plain; charset=utf-8")
171192 h' = contentType:filter ((/=H.hContentType) . fst) h
172193
173−-- | Create a response body consisting of a single builder.
174−streamBuilder :: Builder -> Body ()
175−streamBuilder builder write _ = write $ BuilderChunk builder
194+-- | Construct a 'Responder' that will just call the 'RespondFunc' with the
195+-- given arguments.
196+respond :: H.Status -> H.ResponseHeaders -> Body -> Responder
197+respond s h b = Responder $ \k -> k s h b
176198
177−-- | Equivalent to 'Body' but only streaming 'Builder's.
199+-- | Fold the given bracketing action into a 'Responder'. Note the first
200+-- argument is isomorphic to @Codensity IO a@ or @forall s. ContT s IO a@, and
201+-- is the type of a partially-applied 'Control.Exception.bracket' or
202+-- @with@-style function.
203+--
204+-- > respondWith (bracket acquire release) $
205+-- > \x -> respondNotFound [("x", show x)]
178206--
179−-- 'Network.Wai.StreamingBody' is identical to @SimpleBody ()@.
180−type SimpleBody a = BodyOf Builder a
207+-- is equivalent to
208+--
209+-- > Responder $ \k -> bracket acquire release $
210+-- > \x -> runResponder (respondNotFound [("x", show x)] k
211+--
212+-- This is morally equivalent to ('>>=') on 'Codensity' 'IO'.
213+respondWith :: (forall s. (a -> IO s) -> IO s) -> (a -> Responder) -> Responder
214+respondWith with f = respondCont $ f <$> ContT with
215+
216+-- | Fold the 'ContT' into the contained 'Responder'.
217+respondCont :: (forall r. ContT r IO Responder) -> Responder
218+respondCont cont = Responder $ \k -> runContT cont $ \r -> runResponder r k
219+
220+-- | Fold the 'IO' into the contained 'Responder'.
221+respondIO :: IO Responder -> Responder
222+respondIO io = Responder $ \k -> io >>= \r -> runResponder r k
223+
224+-- | Create a response body consisting of a single builder.
225+streamBuilder :: Builder -> Body
226+streamBuilder builder write _ = write (BuilderChunk builder) >> return []
181227
182228-- | 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
229+streamSimple :: StreamingBody -> Body
230+streamSimple body write flush = body (write . BuilderChunk) flush >> return []
185231
186232-- | Use a normal WAI 'Response' to send the response. Useful if you're
187233-- sharing code between HTTP\/2 applications and HTTP\/1 applications.
188234--
189235-- The 'Request' is used to determine the right file range to serve for
190236-- '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
237+promoteResponse :: Request -> Response -> Responder
238+promoteResponse req response = case response of
239+ (ResponseBuilder s h b) ->
240+ Responder $ \k -> k s h (streamBuilder b)
241+ (ResponseStream s h body) ->
242+ Responder $ \k -> k s h (streamSimple body)
243+ (ResponseRaw _ fallback) -> promoteResponse req fallback
244+ (ResponseFile s h path mpart) -> maybe
245+ (respondFile s h path $ requestHeaders req)
246+ (respondFilePart s h path)
247+ mpart
248+
249+-- | An 'Network.Wai.Application' we tried to promote neither called its
250+-- respond action nor raised; this is only possible if it imported the
251+-- 'ResponseReceived' constructor and used it to lie about having called the
252+-- action.
253+data RespondNeverCalled = RespondNeverCalled deriving (Show, Typeable)
254+
255+instance Exception RespondNeverCalled
204256
205257-- | Promote a normal WAI 'Application' to an 'HTTP2Application' by ignoring
206258-- the HTTP/2-specific features.
207259promoteApplication :: Application -> HTTP2Application
208−promoteApplication app req _ respond = [] <$ app req respond'
209− where respond' r = ResponseReceived <$ responder req r respond
260+promoteApplication app req _ = Responder $ \k -> do
261+ -- In HTTP2Applications, the Responder is required to ferry a value of
262+ -- arbitrary type from the RespondFunc back to the caller of the
263+ -- application, but in Application the type is fixed to ResponseReceived.
264+ -- To add this extra power to an Application, we have to squirrel it away
265+ -- in an IORef as a hack.
266+ ref <- newIORef Nothing
267+ let k' r = do
268+ writeIORef ref . Just =<< runResponder (promoteResponse req r) k
269+ return ResponseReceived
270+ ResponseReceived <- app req k'
271+ readIORef ref >>= maybe (throwIO RespondNeverCalled) return
wai/Network/Wai/Internal.hs+7−3
@@ -167,6 +167,10 @@ tryGetFileSize path =
167167hContentRange :: H.HeaderName
168168hContentRange = "Content-Range"
169169
170+-- | \"Accept-Ranges\".
171+hAcceptRanges :: H.HeaderName
172+hAcceptRanges = "Accept-Ranges"
173+
170174-- | @contentRangeHeader beg end total@ constructs a Content-Range 'H.Header'
171175-- for the range specified.
172176contentRangeHeader :: Integer -> Integer -> Integer -> H.Header
@@ -200,8 +204,8 @@ chooseFilePart size (Just range) = case parseByteRanges range >>= listToMaybe of
200204
201205-- | Adjust the given 'H.Status' and 'H.ResponseHeaders' based on the given
202206-- '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.
207+-- partial, and adding the Content-Length and Accept-Ranges (always) and
208+-- Content-Range (if appropriate) headers.
205209adjustForFilePart :: H.Status -> H.ResponseHeaders -> FilePart -> (H.Status, H.ResponseHeaders)
206210adjustForFilePart s h part = (s', h'')
207211 where
@@ -212,7 +216,7 @@ adjustForFilePart s h part = (s', h'')
212216 contentRange = contentRangeHeader off (off + len - 1) size
213217 lengthBS = L.toStrict $ B.toLazyByteString $ B.integerDec len
214218 s' = if filePartByteCount part /= size then H.partialContent206 else s
215− h' = (H.hContentLength, lengthBS):h
219+ h' = (H.hContentLength, lengthBS):(hAcceptRanges, "bytes"):h
216220 h'' = (if len == size then id else (contentRange:)) h'
217221
218222-- | Parse the value of a Range header into a 'HH.ByteRanges'.
wai/wai.cabal+1
@@ -26,6 +26,7 @@ Library
2626 , network >= 2.2.1.5
2727 , http-types >= 0.7
2828 , text >= 0.7
29+ , transformers >= 0.0
2930 , unix-compat >= 0.2
3031 , vault >= 0.3 && < 0.4
3132 Exposed-modules: Network.Wai
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+28−28
@@ -28,29 +28,33 @@ import Network.Wai.Handler.Warp.HTTP2.Manager
2828import Network.Wai.Handler.Warp.HTTP2.Types
2929import Network.Wai.Handler.Warp.IORef
3030import Network.Wai.HTTP2
31− ( Body
32− , Chunk(..)
31+ ( Chunk(..)
3332 , HTTP2Application
3433 , PushPromise
35− , Responder
34+ , Responder(runResponder)
35+ , RespondFunc
3636 )
3737import qualified Network.Wai.Handler.Warp.Settings as S
3838import qualified Network.Wai.Handler.Warp.Timeout as T
3939
4040----------------------------------------------------------------
4141
42−-- | An 'HTTP2Application' takes a function of status, headers, and body; this
43−-- type implements that by currying some internal arguments.
42+-- | An 'HTTP2Application' takes a function of status, headers, trailers, and
43+-- body; this type implements that by currying some internal arguments.
4444--
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
45+-- The token type of the RespondFunc is set to be (). This is a bit
46+-- anti-climactic, but the real benefit of the token type is that the
47+-- application is forced to call the responder, and making it a boring type
48+-- doesn't break that property.
49+--
50+-- This is the argument to a 'Responder'.
51+type Respond = IO () -> Stream -> RespondFunc ()
4852
49−-- | This function is passed to workers.
50−-- They also pass responses from 'HTTP2Application's to this function.
51−-- This function enqueues commands for the HTTP/2 sender.
53+-- | This function is passed to workers. They also pass responses from
54+-- 'HTTP2Application's to this function. This function enqueues commands for
55+-- the HTTP/2 sender.
5256response :: Context -> Manager -> ThreadContinue -> Respond
53−response ctx mgr tconf tickle strm sq s h strmbdy = do
57+response ctx mgr tconf tickle strm s h strmbdy = do
5458 -- TODO(awpr) HEAD requests will still stream.
5559
5660 -- We must not exit this WAI application.
@@ -62,14 +66,17 @@ response ctx mgr tconf tickle strm sq s h strmbdy = do
6266 -- After this work, this thread stops to decrease the number of workers.
6367 setThreadContinue tconf False
6468
65− runStream ctx OResponse tickle strm sq s h strmbdy
69+ runStream ctx OResponse tickle strm s h strmbdy
6670
6771-- | Set up a waiter thread and run the stream body with functions to enqueue
6872-- 'Sequence's on the stream's queue.
6973runStream :: Context
7074 -> (Stream -> H.Status -> H.ResponseHeaders -> Aux -> Output)
7175 -> Respond
72−runStream Context{outputQ} mkOutput tickle strm sq s h strmbdy = do
76+runStream Context{outputQ} mkOutput tickle strm s h strmbdy = do
77+ -- Since 'Body' is loop, we cannot control it.
78+ -- So, let's serialize 'Builder' with a designated queue.
79+ sq <- newTBQueueIO 10 -- fixme: hard coding: 10
7380 tvar <- newTVarIO SyncNone
7481 let out = mkOutput strm s h (Persist sq tvar)
7582 -- Since we must not enqueue an empty queue to the priority
@@ -83,17 +90,7 @@ runStream Context{outputQ} mkOutput tickle strm sq s h strmbdy = do
8390 FileChunk path part -> SFile path part
8491 tickle
8592 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
93+ trailers <- strmbdy write flush
9794 atomically $ writeTBQueue sq $ SFinish trailers
9895
9996-- | Handle abnormal termination of a stream: mark it as closed, send a reset
@@ -143,8 +140,11 @@ actuallyPushResponder ctx set strm promise responder = do
143140 ws <- initialWindowSize <$> readIORef http2settings
144141
145142 newStrm <- newStream pushConcurrency newSid ws
143+ -- Section 5.3.5 of RFC 7540 defines the weight of push promise is 16.
144+ -- But we need not to follow the spec. So, this value would change
145+ -- if necessary.
146146 writeIORef (streamPriority newStrm) $
147− Priority False (streamNumber strm) 16
147+ defaultPriority { streamDependency = streamNumber strm }
148148 opened newStrm
149149 insert streamTable newSid newStrm
150150
@@ -157,7 +157,7 @@ actuallyPushResponder ctx set strm promise responder = do
157157 respond = runStream ctx mkOutput
158158
159159 -- TODO(awpr): synthesize a Request for 'settingsOnException'?
160− _ <- forkIO $ runResponder responder respond tickle newStrm `E.catch`
160+ _ <- forkIO $ runResponder responder (respond tickle newStrm) `E.catch`
161161 (cleanupStream ctx set strm Nothing . Just)
162162
163163 takeMVar mvar
@@ -189,7 +189,7 @@ worker ctx@Context{inputQ} set tm app respond = do
189189 T.resume th
190190 T.tickle th
191191 let responder = app req $ pushResponder ctx set strm
192− runResponder responder (respond tcont) (T.tickle th) strm
192+ runResponder responder $ respond tcont (T.tickle th) strm
193193 cont1 <- case ex of
194194 Right () -> return True
195195 Left e@(SomeException _)
warp/Network/Wai/Handler/Warp/Response.hs+7−19
@@ -83,18 +83,10 @@ fileRangeSized :: H.Status -> H.ResponseHeaders
8383 -> (H.Status, H.ResponseHeaders, Integer, Integer)
8484fileRangeSized s0 hs0 mPart mRange fileSize = (s, hs, beg, len)
8585 where
86− (beg, _end, len, _entire) = checkPartRange fileSize mPart mRange
87− (s, hs) = adjustForFilePart s0 hs0 $ FilePart beg len fileSize
88−
89−checkPartRange :: Integer -> Maybe FilePart -> Maybe HeaderValue
90− -> (Integer, Integer, Integer, Bool)
91−checkPartRange fileSize mpart mrange = (beg, end, len, isEntire)
92− where
93− part = fromMaybe (chooseFilePart fileSize mrange) mpart
86+ part = fromMaybe (chooseFilePart fileSize mRange) mPart
9487 beg = filePartOffset part
9588 len = filePartByteCount part
96− end = beg + len - 1
97− isEntire = beg == 0 && len == fileSize
89+ (s, hs) = adjustForFilePart s0 hs0 $ FilePart beg len fileSize
9890
9991----------------------------------------------------------------
10092
@@ -204,19 +196,19 @@ sendRsp :: Connection
204196 -> Rsp
205197 -> IO ()
206198sendRsp conn mfdc ver s0 hs0 (RspFile path mPart mRange isHead hook) = do
207− ex <- fileRange s0 hs path mPart mRange
199+ ex <- fileRange s0 hs0 path mPart mRange
208200 case ex of
209201 Left _ex ->
210202#ifdef WARP_DEBUG
211203 print _ex >>
212204#endif
213205 sendRsp conn mfdc ver s2 hs2 (RspBuilder body True)
214− Right (s, hs1, beg, len)
206+ Right (s, hs, beg, len)
215207 | len >= 0 ->
216208 if isHead then
217− sendRsp conn mfdc ver s hs1 (RspBuilder mempty False)
209+ sendRsp conn mfdc ver s hs (RspBuilder mempty False)
218210 else do
219− lheader <- composeHeader ver s hs1
211+ lheader <- composeHeader ver s hs
220212#ifdef WINDOWS
221213 let fid = FileId path Nothing
222214 hook' = hook
@@ -232,10 +224,9 @@ sendRsp conn mfdc ver s0 hs0 (RspFile path mPart mRange isHead hook) = do
232224 connSendFile conn fid beg len hook' [lheader]
233225 | otherwise ->
234226 sendRsp conn mfdc ver H.status416
235− (filter (\(k, _) -> k /= "content-length") hs1)
227+ (filter (\(k, _) -> k /= "content-length") hs)
236228 (RspBuilder mempty True)
237229 where
238− hs = addAcceptRanges hs0
239230 s2 = H.status404
240231 hs2 = replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
241232 body = byteString "File not found"
@@ -359,9 +350,6 @@ hasBody s = sc /= 204
359350
360351----------------------------------------------------------------
361352
362−addAcceptRanges :: H.ResponseHeaders -> H.ResponseHeaders
363−addAcceptRanges hdrs = (hAcceptRanges, "bytes") : hdrs
364−
365353addTransferEncoding :: H.ResponseHeaders -> H.ResponseHeaders
366354addTransferEncoding hdrs = (hTransferEncoding, "chunked") : hdrs
367355
warp/Network/Wai/Handler/Warp/Run.hs+1−4
@@ -6,9 +6,6 @@
66
77module Network.Wai.Handler.Warp.Run where
88
9−#if __GLASGOW_HASKELL__ < 709
10−import Control.Applicative ((<*>))
11−#endif
129import Control.Arrow (first)
1310import Control.Concurrent (threadDelay)
1411import qualified Control.Concurrent as Conc (yield)
@@ -377,7 +374,7 @@ serveHTTP2Only = error "serveHTTP2Only not implemented"
377374
378375-- Serve an HTTP\/2-unaware Application to a connection over any HTTP version.
379376serveDefault :: Application -> ServeConnection
380−serveDefault = flip serveHTTP2 <*> promoteApplication
377+serveDefault app = serveHTTP2 (promoteApplication app) app
381378
382379-- Serve an HTTP\/2-aware application over HTTP\/2 or a backup 'Application'
383380-- over HTTP\/1.1 or HTTP\/1.
warp/test/ResponseSpec.hs+7−7
@@ -99,7 +99,7 @@ spec = do
9999 testFileRange
100100 "gets a file size from file system"
101101 status200 [] "attic/hex" Nothing Nothing
102− $ Right (status200,[("Content-Length","16")],0,16)
102+ $ Right (status200,[("Content-Length","16"), ("Accept-Ranges","bytes")],0,16)
103103 testFileRange
104104 "gets an error if a file does not exist"
105105 status200 [] "attic/nonexist" Nothing Nothing
@@ -107,24 +107,24 @@ spec = do
107107 testFileRange
108108 "changes status if FileParts is specified"
109109 status200 [] "attic/hex" (Just (FilePart 2 10 16)) Nothing
110− $ Right (status206,[("Content-Range", "bytes 2-11/16"),("Content-Length","10")],2,10)
110+ $ Right (status206,[("Content-Range", "bytes 2-11/16"),("Content-Length","10"),("Accept-Ranges","bytes")],2,10)
111111 testFileRange
112112 "does not change status and does not add Content-Range if FileParts means the entire"
113113 status200 [] "attic/hex" (Just (FilePart 0 16 16)) Nothing
114− $ Right (status200,[("Content-Length","16")],0,16)
114+ $ Right (status200,[("Content-Length","16"), ("Accept-Ranges","bytes")],0,16)
115115 testFileRange
116116 "gets a file size from file system and handles Range and returns Partical Content"
117117 status200 [] "attic/hex" Nothing (Just "bytes=2-14")
118− $ Right (status206,[("Content-Range","bytes 2-14/16"),("Content-Length","13")],2,13)
118+ $ Right (status206,[("Content-Range","bytes 2-14/16"),("Content-Length","13"),("Accept-Ranges","bytes")],2,13)
119119 testFileRange
120120 "truncates end point of range to file size"
121121 status200 [] "attic/hex" Nothing (Just "bytes=10-20")
122− $ Right (status206,[("Content-Range","bytes 10-15/16"),("Content-Length","6")],10,6)
122+ $ Right (status206,[("Content-Range","bytes 10-15/16"),("Content-Length","6"),("Accept-Ranges","bytes")],10,6)
123123 testFileRange
124124 "gets a file size from file system and handles Range and returns OK if Range means the entire"
125125 status200 [] "attic/hex" Nothing (Just "bytes=0-15")
126− $ Right (status200,[("Content-Length","16")],0,16)
126+ $ Right (status200,[("Content-Length","16"),("Accept-Ranges","bytes")],0,16)
127127 testFileRange
128128 "igores Range if FilePart is specified"
129129 status200 [] "attic/hex" (Just (FilePart 2 10 16)) (Just "bytes=8-9")
130− $ Right (status206,[("Content-Range", "bytes 2-11/16"),("Content-Length","10")],2,10)
130+ $ Right (status206,[("Content-Range", "bytes 2-11/16"),("Content-Length","10"),("Accept-Ranges","bytes")],2,10)