@@ -7,7 +7,6 @@
77module Network.Wai.Handler.Warp.Response (
88 sendResponse
99 , sanitizeHeaderValue -- for testing
10− , fileRange -- for testing
1110 , warpVersion
1211 , addDate
1312 , addServer
@@ -26,7 +25,7 @@ import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTe
2625#if __GLASGOW_HASKELL__ < 709
2726import Control.Applicative
2827#endif
29−import Control.Exception
28+import qualified Control.Exception as E
3029import Control.Monad (unless, when)
3130import Data.Array ((!))
3231import Data.ByteString (ByteString)
@@ -57,7 +56,7 @@ import Network.Wai
5756import Network.Wai.Handler.Warp.Buffer (toBuilderBuffer)
5857import qualified Network.Wai.Handler.Warp.Date as D
5958import qualified Network.Wai.Handler.Warp.FdCache as F
60−import qualified Network.Wai.Handler.Warp.FileInfoCache as I
59+import Network.Wai.Handler.Warp.File
6160import Network.Wai.Handler.Warp.Header
6261import Network.Wai.Handler.Warp.IO (toBufIOWith)
6362import Network.Wai.Handler.Warp.ResponseHeader
@@ -77,42 +76,14 @@ import qualified Paths_warp
7776
7877----------------------------------------------------------------
7978
80−fileRange :: H.Status -> H.ResponseHeaders -> FilePath
81− -> Maybe FilePart -> Maybe HeaderValue
82− -> (FilePath -> IO I.FileInfo)
83− -> IO (Either IOException
84− (H.Status, H.ResponseHeaders, Integer, Integer))
85−fileRange s0 hs0 path Nothing mRange get = do
86− efinfo <- try (get path)
87− case efinfo of
88− Left e -> return $ Left e
89− Right finfo -> do
90− let ret = fileRangeSized s0 hs0 Nothing mRange $ I.fileInfoSize finfo
91− return $ Right ret
92−fileRange s0 hs0 _ mPart@(Just part) mRange _ =
93− return . Right $ fileRangeSized s0 hs0 mPart mRange size
94− where
95− size = filePartFileSize part
96−
97−fileRangeSized :: H.Status -> H.ResponseHeaders
98− -> Maybe FilePart -> Maybe HeaderValue -> Integer
99− -> (H.Status, H.ResponseHeaders, Integer, Integer)
100−fileRangeSized s0 hs0 mPart mRange fileSize = (s, hs, beg, len)
101− where
102− part = fromMaybe (chooseFilePart fileSize mRange) mPart
103− beg = filePartOffset part
104− len = filePartByteCount part
105− (s, hs) = adjustForFilePart s0 hs0 $ FilePart beg len fileSize
106−
107−----------------------------------------------------------------
108−
10979-- | Sending a HTTP response to 'Connection' according to 'Response'.
11080--
111−-- Applications/middlewares MUST specify a proper 'H.ResponseHeaders'.
81+-- Applications/middlewares MUST provide a proper 'H.ResponseHeaders'.
11282-- so that inconsistency does not happen.
11383-- No header is deleted by this function.
11484--
115−-- Especially, Applications/middlewares MUST take care of
85+-- Especially, Applications/middlewares MUST provide a proper
86+-- Content-Type. They MUST NOT provide
11687-- Content-Length, Content-Range, and Transfer-Encoding
11788-- because they are inserted, when necessary,
11889-- regardless they already exist.
@@ -124,22 +95,6 @@ fileRangeSized s0 hs0 mPart mRange fileSize = (s, hs, beg, len)
12495--
12596-- There are three basic APIs to create 'Response':
12697--
127−-- ['responseFile' :: 'H.Status' -> 'H.ResponseHeaders' -> 'FilePath' -> 'Maybe' 'FilePart' -> 'Response']
128−-- HTTP response body is sent by sendfile() for GET method.
129−-- HTTP response body is not sent by HEAD method.
130−-- Applications are categorized into simple and sophisticated.
131−-- Simple applications should specify 'Nothing' to
132−-- 'Maybe' 'FilePart'. The size of the specified file is obtained
133−-- by disk access. Then Range is handled.
134−-- Sophisticated applications should specify 'Just' to
135−-- 'Maybe' 'FilePart'. They should treat Range (and If-Range) by
136−-- themselves. In both cases,
137−-- Content-Length and Content-Range (if necessary) are automatically
138−-- added into the HTTP response header.
139−-- If Content-Length and Content-Range exist in the HTTP response header,
140−-- they would cause inconsistency.
141−-- Status is also changed to 206 (Partial Content) if necessary.
142−--
14398-- ['responseBuilder' :: 'H.Status' -> 'H.ResponseHeaders' -> 'Builder' -> 'Response']
14499-- HTTP response body is created from 'Builder'.
145100-- Transfer-Encoding: chunked is used in HTTP/1.1.
@@ -150,6 +105,27 @@ fileRangeSized s0 hs0 mPart mRange fileSize = (s, hs, beg, len)
150105--
151106-- ['responseRaw' :: ('IO' 'ByteString' -> ('ByteString' -> 'IO' ()) -> 'IO' ()) -> 'Response' -> 'Response']
152107-- No header is added and no Transfer-Encoding: is applied.
108+--
109+-- ['responseFile' :: 'H.Status' -> 'H.ResponseHeaders' -> 'FilePath' -> 'Maybe' 'FilePart' -> 'Response']
110+-- HTTP response body is sent (by sendfile(), if possible) for GET method.
111+-- HTTP response body is not sent by HEAD method.
112+-- Content-Length and Content-Range are automatically
113+-- added into the HTTP response header if necessary.
114+-- If Content-Length and Content-Range exist in the HTTP response header,
115+-- they would cause inconsistency.
116+-- \"Accept-Ranges: bytes\" is also inserted.
117+--
118+-- Applications are categorized into simple and sophisticated.
119+-- Sophisticated applications should specify 'Just' to
120+-- 'Maybe' 'FilePart'. They should treat the conditional request
121+-- by themselves. A proper 'Status' (200 or 206) must be provided.
122+--
123+-- Simple applications should specify 'Nothing' to
124+-- 'Maybe' 'FilePart'. The size of the specified file is obtained
125+-- by disk access or from the file infor cache.
126+-- If-Modified-Since, If-Unmodified-Since, If-Range and Range
127+-- are processed. Since a proper status is chosen, 'Status' is
128+-- ignored. Last-Modified is inserted.
153129
154130sendResponse :: Settings
155131 -> Connection
@@ -189,13 +165,12 @@ sendResponse settings conn ii req reqidxhdr src response = do
189165 th = threadHandle ii
190166 dc = dateCacher ii
191167 addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr
192− mRange = reqidxhdr ! idxRange
193168 (isPersist,isChunked0) = infoFromRequest req reqidxhdr
194169 isChunked = not isHead && isChunked0
195170 (isKeepAlive, needsChunked) = infoFromResponse rspidxhdr (isPersist,isChunked)
196171 isHead = requestMethod req == H.methodHead
197172 rsp = case response of
198− ResponseFile _ _ path mPart -> RspFile path mPart mRange isHead (T.tickle th)
173+ ResponseFile _ _ path mPart -> RspFile path mPart reqidxhdr isHead (T.tickle th)
199174 ResponseBuilder _ _ b
200175 | isHead -> RspNoBody
201176 | otherwise -> RspBuilder b needsChunked
@@ -237,7 +212,7 @@ sanitizeHeaderValue v = case S8.lines $ S.filter (/= _cr) v of
237212----------------------------------------------------------------
238213
239214data Rsp = RspNoBody
240− | RspFile FilePath (Maybe FilePart) (Maybe HeaderValue) Bool (IO ())
215+ | RspFile FilePath (Maybe FilePart) IndexedHeader Bool (IO ())
241216 | RspBuilder Builder Bool
242217 | RspStream StreamingBody Bool T.Handle
243218 | RspRaw (IO ByteString -> (ByteString -> IO ()) -> IO ()) (IO ByteString) (IO ())
@@ -262,47 +237,6 @@ sendRsp conn _ ver s hs RspNoBody = do
262237
263238----------------------------------------------------------------
264239
265−sendRsp conn ii ver s0 hs0 (RspFile path mPart mRange isHead hook) = do
266− ex <- fileRange s0 hs0 path mPart mRange get
267− case ex of
268− Left _ex ->
269−#ifdef WARP_DEBUG
270− print _ex >>
271−#endif
272− sendRsp conn ii ver s2 hs2 (RspBuilder body True)
273− Right (s, hs, beg, len)
274− | len >= 0 ->
275− if isHead then
276− sendRsp conn ii ver s hs RspNoBody
277− else do
278− lheader <- composeHeader ver s hs
279−#ifdef WINDOWS
280− let fid = FileId path Nothing
281− hook' = hook
282−#else
283− (mfd, hook') <- case mfdc of
284− -- settingsFdCacheDuration is 0
285− Nothing -> return (Nothing, hook)
286− Just fdc -> do
287− (fd, fresher) <- F.getFd fdc path
288− return (Just fd, hook >> fresher)
289− let fid = FileId path mfd
290−#endif
291− connSendFile conn fid beg len hook' [lheader]
292− return (Just s, Just len)
293− | otherwise -> do
294− sendRsp conn ii ver H.requestedRangeNotSatisfiable416
295− (filter (\(k, _) -> k /= "content-length") hs)
296− (RspBuilder mempty True)
297− where
298− s2 = H.notFound404
299− hs2 = replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
300− body = byteString "File not found"
301− mfdc = fdCacher ii
302− get = fileInfo ii
303−
304−----------------------------------------------------------------
305−
306240sendRsp conn _ ver s hs (RspBuilder body needsChunked) = do
307241 header <- composeHeaderBuilder ver s hs needsChunked
308242 let hdrBdy
@@ -350,6 +284,76 @@ sendRsp conn _ _ _ _ (RspRaw withApp src tickle) = do
350284 return bs
351285 send bs = connSendAll conn bs >> tickle
352286
287+----------------------------------------------------------------
288+
289+-- Sophisticated WAI applications.
290+-- We respect s0. s0 MUST be a proper value.
291+sendRsp conn ii ver s0 hs0 (RspFile path (Just part) _ isHead hook) =
292+ sendRspFile2XX conn ii ver s0 hs path beg len isHead hook
293+ where
294+ beg = filePartOffset part
295+ len = filePartByteCount part
296+ hs = addContentHeadersForFilePart hs0 part
297+
298+----------------------------------------------------------------
299+
300+-- Simple WAI applications.
301+-- Status is ignored
302+sendRsp conn ii ver _ hs0 (RspFile path Nothing idxhdr isHead hook) = do
303+ efinfo <- E.try $ fileInfo ii path
304+ case efinfo of
305+ Left (_ex :: E.IOException) ->
306+#ifdef WARP_DEBUG
307+ print _ex >>
308+#endif
309+ sendRspFile404 conn ii ver hs0
310+ Right finfo -> case conditionalRequest finfo hs0 idxhdr of
311+ WithoutBody s -> sendRsp conn ii ver s hs0 RspNoBody
312+ WithBody s hs beg len -> sendRspFile2XX conn ii ver s hs path beg len isHead hook
313+
314+----------------------------------------------------------------
315+
316+sendRspFile2XX :: Connection
317+ -> InternalInfo
318+ -> H.HttpVersion
319+ -> H.Status
320+ -> H.ResponseHeaders
321+ -> FilePath
322+ -> Integer
323+ -> Integer
324+ -> Bool
325+ -> IO ()
326+ -> IO (Maybe H.Status, Maybe Integer)
327+sendRspFile2XX conn ii ver s hs path beg len isHead hook
328+ | isHead = sendRsp conn ii ver s hs RspNoBody
329+ | otherwise = do
330+ lheader <- composeHeader ver s hs
331+#ifdef WINDOWS
332+ let fid = FileId path Nothing
333+ hook' = hook
334+#else
335+ (mfd, hook') <- case fdCacher ii of
336+ -- settingsFdCacheDuration is 0
337+ Nothing -> return (Nothing, hook)
338+ Just fdc -> do
339+ (fd, fresher) <- F.getFd fdc path
340+ return (Just fd, hook >> fresher)
341+ let fid = FileId path mfd
342+#endif
343+ connSendFile conn fid beg len hook' [lheader]
344+ return (Just s, Just len)
345+
346+sendRspFile404 :: Connection
347+ -> InternalInfo
348+ -> H.HttpVersion
349+ -> H.ResponseHeaders
350+ -> IO (Maybe H.Status, Maybe Integer)
351+sendRspFile404 conn ii ver hs0 = sendRsp conn ii ver s hs (RspBuilder body True)
352+ where
353+ s = H.notFound404
354+ hs = replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
355+ body = byteString "File not found"
356+
353357----------------------------------------------------------------
354358----------------------------------------------------------------
355359
@@ -376,7 +380,7 @@ checkPersist req reqidxhdr
376380 | otherwise = checkPersist10 conn
377381 where
378382 ver = httpVersion req
379− conn = reqidxhdr ! idxConnection
383+ conn = reqidxhdr ! fromEnum ReqConnection
380384 checkPersist11 (Just x)
381385 | CI.foldCase x == "close" = False
382386 checkPersist11 _ = True
@@ -401,7 +405,7 @@ infoFromResponse rspidxhdr (isPersist,isChunked) = (isKeepAlive, needsChunked)
401405 where
402406 needsChunked = isChunked && not hasLength
403407 isKeepAlive = isPersist && (isChunked || hasLength)
404− hasLength = isJust $ rspidxhdr ! idxContentLength
408+ hasLength = isJust $ rspidxhdr ! fromEnum ResContentLength
405409
406410----------------------------------------------------------------
407411
@@ -422,7 +426,7 @@ addTransferEncoding hdrs = ("transfer-encoding", "chunked") : hdrs
422426#endif
423427
424428addDate :: D.DateCache -> IndexedHeader -> H.ResponseHeaders -> IO H.ResponseHeaders
425−addDate dc rspidxhdr hdrs = case rspidxhdr ! idxDate of
429+addDate dc rspidxhdr hdrs = case rspidxhdr ! fromEnum ResDate of
426430 Nothing -> do
427431 gmtdate <- D.getDate dc
428432 return $ (H.hDate, gmtdate) : hdrs
@@ -435,7 +439,7 @@ warpVersion :: String
435439warpVersion = showVersion Paths_warp.version
436440
437441addServer :: HeaderValue -> IndexedHeader -> H.ResponseHeaders -> H.ResponseHeaders
438−addServer serverName rspidxhdr hdrs = case rspidxhdr ! idxServer of
442+addServer serverName rspidxhdr hdrs = case rspidxhdr ! fromEnum ResServer of
439443 Nothing -> (H.hServer, serverName) : hdrs
440444 _ -> hdrs
441445