@@ -7,7 +7,6 @@
7module Network.Wai.Handler.Warp.Response (
7module Network.Wai.Handler.Warp.Response (
8 sendResponse
8 sendResponse
9 , sanitizeHeaderValue -- for testing
9 , sanitizeHeaderValue -- for testing
10− , fileRange -- for testing
11 , warpVersion
10 , warpVersion
13 , addServer
12 , addServer
@@ -26,7 +25,7 @@ import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTe
26#if __GLASGOW_HASKELL__ < 709
25#if __GLASGOW_HASKELL__ < 709
27import Control.Applicative
26import Control.Applicative
29−import Control.Exception
28+import qualified Control.Exception as E
30import Control.Monad (unless, when)
29import Control.Monad (unless, when)
31import Data.Array ((!))
30import Data.Array ((!))
32import Data.ByteString (ByteString)
31import Data.ByteString (ByteString)
@@ -57,7 +56,7 @@ import Network.Wai
57import Network.Wai.Handler.Warp.Buffer (toBuilderBuffer)
56import Network.Wai.Handler.Warp.Buffer (toBuilderBuffer)
58import qualified Network.Wai.Handler.Warp.Date as D
57import qualified Network.Wai.Handler.Warp.Date as D
59import qualified Network.Wai.Handler.Warp.FdCache as F
58import 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
61import Network.Wai.Handler.Warp.Header
60import Network.Wai.Handler.Warp.Header
62import Network.Wai.Handler.Warp.IO (toBufIOWith)
61import Network.Wai.Handler.Warp.IO (toBufIOWith)
63import Network.Wai.Handler.Warp.ResponseHeader
62import Network.Wai.Handler.Warp.ResponseHeader
@@ -77,42 +76,14 @@ import qualified Paths_warp
78----------------------------------------------------------------
77----------------------------------------------------------------
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)
88− Left e -> return $ Left e
90− let ret = fileRangeSized s0 hs0 Nothing mRange $ I.fileInfoSize finfo
92−fileRange s0 hs0 _ mPart@(Just part) mRange _ =
93− return . Right $ fileRangeSized s0 hs0 mPart mRange size
95− size = filePartFileSize part
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)
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
107−----------------------------------------------------------------
109-- | Sending a HTTP response to 'Connection' according to 'Response'.
79-- | Sending a HTTP response to 'Connection' according to 'Response'.
111−-- Applications/middlewares MUST specify a proper 'H.ResponseHeaders'.
81+-- Applications/middlewares MUST provide a proper 'H.ResponseHeaders'.
112-- so that inconsistency does not happen.
82-- so that inconsistency does not happen.
113-- No header is deleted by this function.
83-- No header is deleted by this function.
115−-- Especially, Applications/middlewares MUST take care of
85+-- Especially, Applications/middlewares MUST provide a proper
86+-- Content-Type. They MUST NOT provide
116-- Content-Length, Content-Range, and Transfer-Encoding
87-- Content-Length, Content-Range, and Transfer-Encoding
117-- because they are inserted, when necessary,
88-- because they are inserted, when necessary,
118-- regardless they already exist.
89-- regardless they already exist.
@@ -124,22 +95,6 @@ fileRangeSized s0 hs0 mPart mRange fileSize = (s, hs, beg, len)
125-- There are three basic APIs to create 'Response':
96-- There are three basic APIs to create 'Response':
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.
143-- ['responseBuilder' :: 'H.Status' -> 'H.ResponseHeaders' -> 'Builder' -> 'Response']
98-- ['responseBuilder' :: 'H.Status' -> 'H.ResponseHeaders' -> 'Builder' -> 'Response']
144-- HTTP response body is created from 'Builder'.
99-- HTTP response body is created from 'Builder'.
145-- Transfer-Encoding: chunked is used in HTTP/1.1.
100-- Transfer-Encoding: chunked is used in HTTP/1.1.
@@ -150,6 +105,27 @@ fileRangeSized s0 hs0 mPart mRange fileSize = (s, hs, beg, len)
151-- ['responseRaw' :: ('IO' 'ByteString' -> ('ByteString' -> 'IO' ()) -> 'IO' ()) -> 'Response' -> 'Response']
106-- ['responseRaw' :: ('IO' 'ByteString' -> ('ByteString' -> 'IO' ()) -> 'IO' ()) -> 'Response' -> 'Response']
152-- No header is added and no Transfer-Encoding: is applied.
107-- No header is added and no Transfer-Encoding: is applied.
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.
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.
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.
154sendResponse :: Settings
130sendResponse :: Settings
155 -> Connection
131 -> Connection
@@ -189,13 +165,12 @@ sendResponse settings conn ii req reqidxhdr src response = do
189 th = threadHandle ii
165 th = threadHandle ii
190 dc = dateCacher ii
166 dc = dateCacher ii
191 addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr
167 addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr
192− mRange = reqidxhdr ! idxRange
193 (isPersist,isChunked0) = infoFromRequest req reqidxhdr
168 (isPersist,isChunked0) = infoFromRequest req reqidxhdr
194 isChunked = not isHead && isChunked0
169 isChunked = not isHead && isChunked0
195 (isKeepAlive, needsChunked) = infoFromResponse rspidxhdr (isPersist,isChunked)
170 (isKeepAlive, needsChunked) = infoFromResponse rspidxhdr (isPersist,isChunked)
196 isHead = requestMethod req == H.methodHead
171 isHead = requestMethod req == H.methodHead
197 rsp = case response of
172 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)
199 ResponseBuilder _ _ b
174 ResponseBuilder _ _ b
200 | isHead -> RspNoBody
175 | isHead -> RspNoBody
201 | otherwise -> RspBuilder b needsChunked
176 | otherwise -> RspBuilder b needsChunked
@@ -237,7 +212,7 @@ sanitizeHeaderValue v = case S8.lines $ S.filter (/= _cr) v of
237----------------------------------------------------------------
212----------------------------------------------------------------
239data Rsp = RspNoBody
214data Rsp = RspNoBody
240− | RspFile FilePath (Maybe FilePart) (Maybe HeaderValue) Bool (IO ())
215+ | RspFile FilePath (Maybe FilePart) IndexedHeader Bool (IO ())
241 | RspBuilder Builder Bool
216 | RspBuilder Builder Bool
242 | RspStream StreamingBody Bool T.Handle
217 | RspStream StreamingBody Bool T.Handle
243 | RspRaw (IO ByteString -> (ByteString -> IO ()) -> IO ()) (IO ByteString) (IO ())
218 | RspRaw (IO ByteString -> (ByteString -> IO ()) -> IO ()) (IO ByteString) (IO ())
@@ -262,47 +237,6 @@ sendRsp conn _ ver s hs RspNoBody = do
263----------------------------------------------------------------
238----------------------------------------------------------------
265−sendRsp conn ii ver s0 hs0 (RspFile path mPart mRange isHead hook) = do
266− ex <- fileRange s0 hs0 path mPart mRange get
272− sendRsp conn ii ver s2 hs2 (RspBuilder body True)
273− Right (s, hs, beg, len)
276− sendRsp conn ii ver s hs RspNoBody
278− lheader <- composeHeader ver s hs
280− let fid = FileId path Nothing
283− (mfd, hook') <- case mfdc of
284− -- settingsFdCacheDuration is 0
285− Nothing -> return (Nothing, hook)
287− (fd, fresher) <- F.getFd fdc path
288− return (Just fd, hook >> fresher)
289− let fid = FileId path mfd
291− connSendFile conn fid beg len hook' [lheader]
292− return (Just s, Just len)
294− sendRsp conn ii ver H.requestedRangeNotSatisfiable416
295− (filter (\(k, _) -> k /= "content-length") hs)
296− (RspBuilder mempty True)
299− hs2 = replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
300− body = byteString "File not found"
304−----------------------------------------------------------------
306sendRsp conn _ ver s hs (RspBuilder body needsChunked) = do
240sendRsp conn _ ver s hs (RspBuilder body needsChunked) = do
307 header <- composeHeaderBuilder ver s hs needsChunked
241 header <- composeHeaderBuilder ver s hs needsChunked
308 let hdrBdy
242 let hdrBdy
@@ -350,6 +284,76 @@ sendRsp conn _ _ _ _ (RspRaw withApp src tickle) = do
350 return bs
284 return bs
351 send bs = connSendAll conn bs >> tickle
285 send bs = connSendAll conn bs >> tickle
287+----------------------------------------------------------------
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
294+ beg = filePartOffset part
295+ len = filePartByteCount part
296+ hs = addContentHeadersForFilePart hs0 part
298+----------------------------------------------------------------
300+-- Simple WAI applications.
302+sendRsp conn ii ver _ hs0 (RspFile path Nothing idxhdr isHead hook) = do
303+ efinfo <- E.try $ fileInfo ii path
305+ Left (_ex :: E.IOException) ->
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
314+----------------------------------------------------------------
316+sendRspFile2XX :: Connection
320+ -> H.ResponseHeaders
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
330+ lheader <- composeHeader ver s hs
332+ let fid = FileId path Nothing
335+ (mfd, hook') <- case fdCacher ii of
336+ -- settingsFdCacheDuration is 0
337+ Nothing -> return (Nothing, hook)
339+ (fd, fresher) <- F.getFd fdc path
340+ return (Just fd, hook >> fresher)
341+ let fid = FileId path mfd
343+ connSendFile conn fid beg len hook' [lheader]
344+ return (Just s, Just len)
346+sendRspFile404 :: Connection
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)
354+ hs = replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
355+ body = byteString "File not found"
353----------------------------------------------------------------
357----------------------------------------------------------------
354----------------------------------------------------------------
358----------------------------------------------------------------
@@ -376,7 +380,7 @@ checkPersist req reqidxhdr
376 | otherwise = checkPersist10 conn
380 | otherwise = checkPersist10 conn
378 ver = httpVersion req
382 ver = httpVersion req
379− conn = reqidxhdr ! idxConnection
383+ conn = reqidxhdr ! fromEnum ReqConnection
380 checkPersist11 (Just x)
384 checkPersist11 (Just x)
381 | CI.foldCase x == "close" = False
385 | CI.foldCase x == "close" = False
382 checkPersist11 _ = True
386 checkPersist11 _ = True
@@ -401,7 +405,7 @@ infoFromResponse rspidxhdr (isPersist,isChunked) = (isKeepAlive, needsChunked)
402 needsChunked = isChunked && not hasLength
406 needsChunked = isChunked && not hasLength
403 isKeepAlive = isPersist && (isChunked || hasLength)
407 isKeepAlive = isPersist && (isChunked || hasLength)
404− hasLength = isJust $ rspidxhdr ! idxContentLength
408+ hasLength = isJust $ rspidxhdr ! fromEnum ResContentLength
406----------------------------------------------------------------
410----------------------------------------------------------------
@@ -422,7 +426,7 @@ addTransferEncoding hdrs = ("transfer-encoding", "chunked") : hdrs
424addDate :: D.DateCache -> IndexedHeader -> H.ResponseHeaders -> IO H.ResponseHeaders
428addDate :: 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
426 Nothing -> do
430 Nothing -> do
427 gmtdate <- D.getDate dc
431 gmtdate <- D.getDate dc
428 return $ (H.hDate, gmtdate) : hdrs
432 return $ (H.hDate, gmtdate) : hdrs
@@ -435,7 +439,7 @@ warpVersion :: String
435warpVersion = showVersion Paths_warp.version
439warpVersion = showVersion Paths_warp.version
437addServer :: HeaderValue -> IndexedHeader -> H.ResponseHeaders -> H.ResponseHeaders
441addServer :: 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
439 Nothing -> (H.hServer, serverName) : hdrs
443 Nothing -> (H.hServer, serverName) : hdrs
440 _ -> hdrs
444 _ -> hdrs