@@ -1,5 +1,6 @@
11{-# LANGUAGE OverloadedStrings #-}
22{-# LANGUAGE ScopedTypeVariables #-}
3+{-# LANGUAGE TupleSections #-}
34
45module Network.Wai.Handler.Warp.Response (
56 sendResponse
@@ -16,7 +17,7 @@ import qualified Data.CaseInsensitive as CI
1617import Data.Conduit
1718import Data.Conduit.Blaze (builderToByteString)
1819import qualified Data.Conduit.List as CL
19−import Data.Maybe (isJust)
20+import Data.Maybe (isJust, listToMaybe)
2021import Data.Monoid (mappend)
2122import qualified Network.HTTP.Types as H
2223import Network.Wai
@@ -26,6 +27,8 @@ import qualified Network.Wai.Handler.Warp.ResponseHeader as RH
2627import qualified Network.Wai.Handler.Warp.Timeout as T
2728import Network.Wai.Handler.Warp.Types
2829import qualified System.PosixCompat.Files as P
30+import Network.HTTP.Attoparsec (parseByteRanges)
31+import Numeric (showInt)
2932
3033----------------------------------------------------------------
3134----------------------------------------------------------------
@@ -36,20 +39,47 @@ sendResponse :: Settings
3639
3740----------------------------------------------------------------
3841
39−sendResponse settings cleaner req conn (ResponseFile s hs path mpart) =
42+sendResponse settings cleaner req conn (ResponseFile s0 hs0 path mpart0) =
4043 headerAndLength >>= sendResponse'
4144 where
45+ hs = addAccept hs0
4246 th = threadHandle cleaner
43− headerAndLength = case (readInt <$> checkLength hs, mpart) of
44− (Just cl, _) -> return $ Right (hs, cl)
45− (Nothing, Nothing) -> liftIO . try $ do
46− cl <- fromIntegral . P.fileSize <$> P.getFileStatus path
47− return (addLength cl hs, cl)
48− (Nothing, Just part) -> do
49− let cl = fromIntegral $ filePartByteCount part
50− return $ Right (addLength cl hs, cl)
51−
52− sendResponse' (Right (lengthyHeaders, cl))
47+ headerAndLength = liftIO . try $ do
48+ (hadLength, cl) <-
49+ case readInt <$> checkLength hs of
50+ Just cl -> return (True, cl)
51+ Nothing ->
52+ case mpart0 of
53+ Just part -> return (False, fromIntegral $ filePartByteCount part)
54+ Nothing -> (False, ) . fromIntegral . P.fileSize
55+ <$> P.getFileStatus path
56+ let (s, addRange, beg, end) =
57+ case mpart0 of
58+ Just part -> (s0, id, filePartOffset part, filePartByteCount part)
59+ Nothing ->
60+ case lookup H.hRange (requestHeaders req) >>= parseByteRanges >>= listToMaybe of
61+ Just range | s0 == H.status200 ->
62+ case range of
63+ H.ByteRangeFrom from -> rangeRes cl from (cl - 1)
64+ H.ByteRangeFromTo from to -> rangeRes cl from to
65+ H.ByteRangeSuffix count -> rangeRes cl (cl - count) (cl - 1)
66+ _ -> (s0, id, 0, cl)
67+ hs'
68+ | hadLength = hs
69+ | otherwise = addLength end hs
70+ return (s, addRange hs', beg, end)
71+
72+ rangeRes cl from to = (H.status206, (("Content-Range", rangeHeader cl from to):), from, to - from + 1)
73+
74+ rangeHeader total from to = B.pack
75+ $ 'b' : 'y': 't' : 'e' : 's' : ' '
76+ : showInt from
77+ ( '-'
78+ : showInt to
79+ ( '/'
80+ : showInt total ""))
81+
82+ sendResponse' (Right (s, lengthyHeaders, beg, end))
5383 | hasBody s req = liftIO $ do
5484 lheader <- composeHeader settings version s lengthyHeaders
5585 connSendFile conn path beg end (T.tickle th) [lheader] cleaner
@@ -60,9 +90,6 @@ sendResponse settings cleaner req conn (ResponseFile s hs path mpart) =
6090 T.tickle th
6191 return isPersist -- FIXME isKeepAlive?
6292 where
63− (beg,end) = case mpart of
64− Nothing -> (0,cl)
65− Just prt -> (filePartOffset prt, filePartByteCount prt)
6693 version = httpVersion req
6794 (isPersist,_) = infoFromRequest req
6895
@@ -192,6 +219,9 @@ hasBody s req = sc /= 204
192219addLength :: Integer -> H.ResponseHeaders -> H.ResponseHeaders
193220addLength cl hdrs = (H.hContentLength, B.pack $ show cl) : hdrs
194221
222+addAccept :: H.ResponseHeaders -> H.ResponseHeaders
223+addAccept = (("Accept-Ranges", "bytes"):)
224+
195225addEncodingHeader :: H.ResponseHeaders -> H.ResponseHeaders
196226addEncodingHeader hdrs = (hTransferEncoding, "chunked") : hdrs
197227