on master help sign in
Merge pull request #473 from yesodweb/file-info-cache

File info cache

commitaad53f11f9d70ee53e9881a6eb695d1abe756316 authorMichael Snoyman <michael@snoyman.com> dateThu 3 Dec 2015, 06:30 (11 years ago) parent0569be7b61d758
11 files changed+223−48
warp/Network/Wai/Handler/Warp.hs+41−2 warp/Network/Wai/Handler/Warp/Date.hs+18−14 warp/Network/Wai/Handler/Warp/FileInfoCache.hs+106 warp/Network/Wai/Handler/Warp/Internal.hs+3 warp/Network/Wai/Handler/Warp/Request.hs+10−3 warp/Network/Wai/Handler/Warp/Response.hs+18−10 warp/Network/Wai/Handler/Warp/Run.hs+18−16 warp/Network/Wai/Handler/Warp/Settings.hs+2 warp/Network/Wai/Handler/Warp/Types.hs+2 warp/test/ResponseSpec.hs+2−1 warp/warp.cabal+3−2
warp/Network/Wai/Handler/Warp.hs+41−2
@@ -66,6 +66,7 @@ module Network.Wai.Handler.Warp (
6666 , setTimeout
6767 , setManager
6868 , setFdCacheDuration
69+ , setFileInfoCacheDuration
6970 , setBeforeMainLoop
7071 , setNoParsePath
7172 , setInstallShutdownHandler
@@ -95,6 +96,8 @@ module Network.Wai.Handler.Warp (
9596 , InvalidRequest (..)
9697 -- * Utilities
9798 , pauseTimeout
99+ , FileInfo(..)
100+ , getFileInfo
98101 -- * Internal
99102 -- | The following APIs will be removed in Warp 3.2.0. Please use ones exported from Network.Wai.Handler.Warp.Internal.
100103
@@ -136,7 +139,7 @@ module Network.Wai.Handler.Warp (
136139 , module Network.Wai.Handler.Warp.Timeout
137140 ) where
138141
139−import Control.Exception (SomeException)
142+import Control.Exception (SomeException, throwIO)
140143import Data.ByteString (ByteString)
141144import Data.Maybe (fromMaybe)
142145import Data.Streaming.Network (HostPreference)
@@ -145,6 +148,7 @@ import Network.Socket (SockAddr)
145148import Network.Wai (Request, Response, vault)
146149import Network.Wai.Handler.Warp.Buffer
147150import Network.Wai.Handler.Warp.Date
151+import Network.Wai.Handler.Warp.FileInfoCache
148152import Network.Wai.Handler.Warp.FdCache
149153import Network.Wai.Handler.Warp.Header
150154import Network.Wai.Handler.Warp.Request
@@ -219,10 +223,26 @@ setManager x y = y { settingsManager = Just x }
219223-- Enabling this cache results in drastic performance improvement for file
220224-- transfers.
221225--
222−-- Default value: since 3.0.13, default value is 0, was previously 10
226+-- Default value: 0, was previously 10
227+--
228+-- Since 3.0.13
223229setFdCacheDuration :: Int -> Settings -> Settings
224230setFdCacheDuration x y = y { settingsFdCacheDuration = x }
225231
232+-- | Cache duration time of file information in seconds. 0 means that the cache mechanism is not used.
233+--
234+-- The file information cache is an optimization that is useful for servers dealing with
235+-- static files. However, if files are being modified, it can cause incorrect
236+-- results in some cases. Therefore, we disable it by default. If you know that
237+-- your files will be static or you prefer performance to file consistency,
238+-- it's recommended to turn this on; a reasonable value for those cases is 10.
239+-- Enabling this cache results in drastic performance improvement for file
240+-- transfers.
241+--
242+-- Default value: 0
243+setFileInfoCacheDuration :: Int -> Settings -> Settings
244+setFileInfoCacheDuration x y = y { settingsFileInfoCacheDuration = x }
245+
226246-- | Code to run after the listening socket is ready but before entering
227247-- the main event loop. Useful for signaling to tests that they can start
228248-- running, or to drop permissions after binding to a restricted port.
@@ -371,3 +391,22 @@ setHTTP2Disabled y = y { settingsHTTP2Enabled = False }
371391-- Since 3.0.10
372392pauseTimeout :: Request -> IO ()
373393pauseTimeout = fromMaybe (return ()) . Vault.lookup pauseTimeoutKey . vault
394+
395+-- | Getting file information of the target file.
396+--
397+-- This function first uses a stat(2) or similar system call
398+-- to obtain information of the target file, then registers
399+-- it into the internal cache.
400+-- From the next time, the information is obtained
401+-- from the cache. This reduces the overhead to call the system call.
402+-- The internal cache is refreshed every duration specified by
403+-- 'setFileInfoCacheDuration'.
404+--
405+-- This function throws an 'IO' exception if the information is not
406+-- available. For instance, the target file does not exist.
407+-- If this function is used an a Request generated by a WAI
408+-- backend besides Warp, it also throws an 'IO' exception.
409+--
410+-- Since 3.1.10
411+getFileInfo :: Request -> FilePath -> IO FileInfo
412+getFileInfo = fromMaybe (\_ -> throwIO (userError "getFileInfo")) . Vault.lookup getFileInfoKey . vault
warp/Network/Wai/Handler/Warp/Date.hs+18−14
@@ -5,6 +5,9 @@ module Network.Wai.Handler.Warp.Date (
55 , getDate
66 , DateCache
77 , GMTDate
8+#if WINDOWS
9+ , uToH
10+#endif
811 ) where
912
1013#if __GLASGOW_HASKELL__ < 709
@@ -12,16 +15,13 @@ import Control.Applicative
1215#endif
1316import Control.AutoUpdate (defaultUpdateSettings, updateAction, mkAutoUpdate)
1417import Data.ByteString.Char8
18+import Network.HTTP.Date
1519
1620#if WINDOWS
17−import Data.Time (formatTime, getCurrentTime)
18−# if MIN_VERSION_time(1,5,0)
19−import Data.Time (defaultTimeLocale)
20−# else
21−import System.Locale (defaultTimeLocale)
22−# endif
21+import Data.Time (UTCTime, formatTime, getCurrentTime)
22+import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
23+import Foreign.C.Types (CTime(..))
2324#else
24−import Network.HTTP.Date
2525import System.Posix (epochTime)
2626#endif
2727
@@ -36,17 +36,21 @@ withDateCache :: (DateCache -> IO a) -> IO a
3636withDateCache action = initialize >>= action
3737
3838initialize :: IO DateCache
39−initialize = mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentGMTDate }
39+initialize = mkAutoUpdate defaultUpdateSettings {
40+ updateAction = formatHTTPDate <$> getCurrentHTTPDate
41+ }
4042
41−-- | Getting 'GMTDate' based on 'DateCache'.
43+-- | Getting current 'GMTDate' based on 'DateCache'.
4244getDate :: DateCache -> IO GMTDate
4345getDate = id
4446
45−getCurrentGMTDate :: IO GMTDate
4647#ifdef WINDOWS
47−getCurrentGMTDate = formatDate <$> getCurrentTime
48− where
49− formatDate = pack . formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT"
48+uToH :: UTCTime -> HTTPDate
49+uToH = epochTimeToHTTPDate . CTime . truncate . utcTimeToPOSIXSeconds
50+
51+getCurrentHTTPDate :: IO HTTPDate
52+getCurrentHTTPDate = uToH <$> getCurrentTime
5053#else
51−getCurrentGMTDate = formatHTTPDate . epochTimeToHTTPDate <$> epochTime
54+getCurrentHTTPDate :: IO HTTPDate
55+getCurrentHTTPDate = epochTimeToHTTPDate <$> epochTime
5256#endif
warp/Network/Wai/Handler/Warp/FileInfoCache.hsnew file+106
@@ -0,0 +1,106 @@
1+{-# LANGUAGE RecordWildCards, CPP #-}
2+
3+module Network.Wai.Handler.Warp.FileInfoCache (
4+ FileInfo(..)
5+ , withFileInfoCache
6+ , getInfo -- test purpose only
7+ ) where
8+
9+import Control.Exception as E
10+import Control.Monad (void)
11+import Control.Reaper
12+import Data.ByteString (ByteString)
13+import Data.HashMap.Strict (HashMap)
14+import qualified Data.HashMap.Strict as M
15+import Network.HTTP.Date
16+import System.PosixCompat.Files
17+
18+#if WINDOWS
19+import Data.Time (UTCTime)
20+import Network.Wai.Handler.Warp.Date (uToH)
21+#endif
22+
23+----------------------------------------------------------------
24+
25+-- | File information.
26+data FileInfo = FileInfo {
27+ fileInfoName :: !FilePath
28+ , fileInfoSize :: !Integer
29+ , fileInfoTime :: HTTPDate -- ^ Modification time
30+ , fileInfoDate :: ByteString -- ^ Modification time in the GMT format
31+ } deriving (Eq, Show)
32+
33+data Entry = Negative | Positive FileInfo
34+type Cache = HashMap FilePath Entry
35+type FileInfoCache = Reaper Cache (FilePath,Entry)
36+
37+----------------------------------------------------------------
38+
39+-- | Getting the file information corresponding to the file.
40+getInfo :: FilePath -> IO FileInfo
41+getInfo path = do
42+ fs <- getFileStatus path -- file access
43+ let regular = not (isDirectory fs)
44+ readable = fileMode fs `intersectFileModes` ownerReadMode /= 0
45+ if regular && readable then do
46+ let time = epochTimeToHTTPDate $ modificationTime fs
47+ date = formatHTTPDate time
48+ size = fromIntegral $ fileSize fs
49+ info = FileInfo {
50+ fileInfoName = path
51+ , fileInfoSize = size
52+ , fileInfoTime = time
53+ , fileInfoDate = date
54+ }
55+ return info
56+ else
57+ throwIO (userError "FileInfoCache:getInfo")
58+
59+----------------------------------------------------------------
60+
61+getAndRegisterInfo :: FileInfoCache -> FilePath -> IO FileInfo
62+getAndRegisterInfo reaper@Reaper{..} path = do
63+ cache <- reaperRead
64+ case M.lookup path cache of
65+ Just Negative -> throwIO (userError "FileInfoCache:getAndRegisterInfo")
66+ Just (Positive x) -> return x
67+ Nothing -> positive reaper path `E.onException` negative reaper path
68+
69+positive :: FileInfoCache -> FilePath -> IO FileInfo
70+positive Reaper{..} path = do
71+ info <- getInfo path
72+ reaperAdd (path, Positive info)
73+ return info
74+
75+negative :: FileInfoCache -> FilePath -> IO FileInfo
76+negative Reaper{..} path = do
77+ reaperAdd (path,Negative)
78+ throwIO (userError "FileInfoCache:negative")
79+
80+----------------------------------------------------------------
81+
82+-- | Creating a file information cache
83+-- and executing the action in the second argument.
84+-- The first argument is a cache duration in second.
85+withFileInfoCache :: Int -> ((FilePath -> IO FileInfo) -> IO a) -> IO a
86+withFileInfoCache 0 action = action getInfo
87+withFileInfoCache duration action = E.bracket (initialize duration)
88+ terminate
89+ (\c -> action (getAndRegisterInfo c))
90+
91+initialize :: Int -> IO FileInfoCache
92+initialize duration = mkReaper settings
93+ where
94+ settings = defaultReaperSettings {
95+ reaperAction = override
96+ , reaperDelay = duration * 1000000
97+ , reaperCons = uncurry M.insert
98+ , reaperNull = M.null
99+ , reaperEmpty = M.empty
100+ }
101+
102+override :: Cache -> IO (Cache -> Cache)
103+override _ = return $ const M.empty
104+
105+terminate :: FileInfoCache -> IO ()
106+terminate x = void $ reaperStop x
warp/Network/Wai/Handler/Warp/Internal.hs+3
@@ -67,6 +67,8 @@ module Network.Wai.Handler.Warp.Internal (
6767 , module Network.Wai.Handler.Warp.Timeout
6868 -- * File descriptor cache
6969 , module Network.Wai.Handler.Warp.FdCache
70+ -- * File information cache
71+ , module Network.Wai.Handler.Warp.FileInfoCache
7072 -- * Date
7173 , module Network.Wai.Handler.Warp.Date
7274 -- * Request and response
@@ -78,6 +80,7 @@ module Network.Wai.Handler.Warp.Internal (
7880import Network.Wai.Handler.Warp.Buffer
7981import Network.Wai.Handler.Warp.Date
8082import Network.Wai.Handler.Warp.FdCache
83+import Network.Wai.Handler.Warp.FileInfoCache
8184import Network.Wai.Handler.Warp.Header
8285import Network.Wai.Handler.Warp.Recv
8386import Network.Wai.Handler.Warp.Request
warp/Network/Wai/Handler/Warp/Request.hs+10−3
@@ -7,6 +7,7 @@ module Network.Wai.Handler.Warp.Request (
77 recvRequest
88 , headerLines
99 , pauseTimeoutKey
10+ , getFileInfoKey
1011 ) where
1112
1213import qualified Control.Concurrent as Conc (yield)
@@ -21,6 +22,7 @@ import qualified Network.HTTP.Types as H
2122import Network.Socket (SockAddr)
2223import Network.Wai
2324import Network.Wai.Handler.Warp.Conduit
25+import Network.Wai.Handler.Warp.FileInfoCache
2426import Network.Wai.Handler.Warp.Header
2527import Network.Wai.Handler.Warp.ReadInt
2628import Network.Wai.Handler.Warp.RequestHeader
@@ -81,9 +83,7 @@ recvRequest settings conn ii addr src = do
8183 , isSecure = False
8284 , remoteHost = addr
8385 , requestBody = rbody'
84− , vault = Vault.insert pauseTimeoutKey
85− (Timeout.pause th)
86− Vault.empty
86+ , vault = vaultValue
8787 , requestBodyLength = bodyLength
8888 , requestHeaderHost = idxhdr ! idxHost
8989 , requestHeaderRange = idxhdr ! idxRange
@@ -91,6 +91,9 @@ recvRequest settings conn ii addr src = do
9191 return (req, remainingRef, idxhdr, rbodyFlush)
9292 where
9393 th = threadHandle ii
94+ vaultValue = Vault.insert pauseTimeoutKey (Timeout.pause th)
95+ $ Vault.insert getFileInfoKey (fileInfo ii)
96+ Vault.empty
9497
9598----------------------------------------------------------------
9699
@@ -278,3 +281,7 @@ checkCR bs pos = if pos > 0 && 13 == S.index bs p then p else pos -- 13 is CR
278281pauseTimeoutKey :: Vault.Key (IO ())
279282pauseTimeoutKey = unsafePerformIO Vault.newKey
280283{-# NOINLINE pauseTimeoutKey #-}
284+
285+getFileInfoKey :: Vault.Key (FilePath -> IO FileInfo)
286+getFileInfoKey = unsafePerformIO Vault.newKey
287+{-# NOINLINE getFileInfoKey #-}
warp/Network/Wai/Handler/Warp/Response.hs+18−10
@@ -57,6 +57,7 @@ import Network.Wai
5757import Network.Wai.Handler.Warp.Buffer (toBuilderBuffer)
5858import qualified Network.Wai.Handler.Warp.Date as D
5959import qualified Network.Wai.Handler.Warp.FdCache as F
60+import qualified Network.Wai.Handler.Warp.FileInfoCache as I
6061import Network.Wai.Handler.Warp.Header
6162import Network.Wai.Handler.Warp.IO (toBufIOWith)
6263import Network.Wai.Handler.Warp.ResponseHeader
@@ -77,11 +78,17 @@ import qualified Paths_warp
7778
7879fileRange :: H.Status -> H.ResponseHeaders -> FilePath
7980 -> Maybe FilePart -> Maybe HeaderValue
81+ -> (FilePath -> IO I.FileInfo)
8082 -> IO (Either IOException
8183 (H.Status, H.ResponseHeaders, Integer, Integer))
82−fileRange s0 hs0 path Nothing mRange =
83− fmap (fileRangeSized s0 hs0 Nothing mRange) <$> tryGetFileSize path
84−fileRange s0 hs0 _ mPart@(Just part) mRange =
84+fileRange s0 hs0 path Nothing mRange get = do
85+ efinfo <- try (get path)
86+ case efinfo of
87+ Left e -> return $ Left e
88+ Right finfo -> do
89+ let ret = fileRangeSized s0 hs0 Nothing mRange $ I.fileInfoSize finfo
90+ return $ Right ret
91+fileRange s0 hs0 _ mPart@(Just part) mRange _ =
8592 return . Right $ fileRangeSized s0 hs0 mPart mRange size
8693 where
8794 size = filePartFileSize part
@@ -155,7 +162,7 @@ sendResponse defServer conn ii req reqidxhdr src response = do
155162 hs <- addServerAndDate hs0
156163 if hasBody req s then do
157164 -- See definition of rsp below for proper body stripping.
158− sendRsp conn mfdc ver s hs rsp
165+ sendRsp conn ii ver s hs rsp
159166 T.tickle th
160167 return ret
161168 else do
@@ -169,7 +176,6 @@ sendResponse defServer conn ii req reqidxhdr src response = do
169176 rspidxhdr = indexResponseHeader hs0
170177 th = threadHandle ii
171178 dc = dateCacher ii
172− mfdc = fdCacher ii
173179 addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr
174180 mRange = reqidxhdr ! idxRange
175181 (isPersist,isChunked) = infoFromRequest req reqidxhdr
@@ -220,20 +226,20 @@ data Rsp = RspFile FilePath (Maybe FilePart) (Maybe HeaderValue) (IO ())
220226----------------------------------------------------------------
221227
222228sendRsp :: Connection
223− -> Maybe F.MutableFdCache
229+ -> InternalInfo
224230 -> H.HttpVersion
225231 -> H.Status
226232 -> H.ResponseHeaders
227233 -> Rsp
228234 -> IO ()
229−sendRsp conn mfdc ver s0 hs0 (RspFile path mPart mRange hook) = do
230− ex <- fileRange s0 hs0 path mPart mRange
235+sendRsp conn ii ver s0 hs0 (RspFile path mPart mRange hook) = do
236+ ex <- fileRange s0 hs0 path mPart mRange get
231237 case ex of
232238 Left _ex ->
233239#ifdef WARP_DEBUG
234240 print _ex >>
235241#endif
236− sendRsp conn mfdc ver s2 hs2 (RspBuilder body True)
242+ sendRsp conn ii ver s2 hs2 (RspBuilder body True)
237243 Right (s, hs, beg, len)
238244 | len >= 0 -> do
239245 lheader <- composeHeader ver s hs
@@ -251,13 +257,15 @@ sendRsp conn mfdc ver s0 hs0 (RspFile path mPart mRange hook) = do
251257#endif
252258 connSendFile conn fid beg len hook' [lheader]
253259 | otherwise ->
254− sendRsp conn mfdc ver H.status416
260+ sendRsp conn ii ver H.status416
255261 (filter (\(k, _) -> k /= "content-length") hs)
256262 (RspBuilder mempty True)
257263 where
258264 s2 = H.status404
259265 hs2 = replaceHeader H.hContentType "text/plain; charset=utf-8" hs0
260266 body = byteString "File not found"
267+ mfdc = fdCacher ii
268+ get = fileInfo ii
261269
262270----------------------------------------------------------------
263271
warp/Network/Wai/Handler/Warp/Run.hs+18−16
@@ -29,6 +29,7 @@ import Network.Wai.Handler.Warp.Buffer
2929import Network.Wai.Handler.Warp.Counter
3030import qualified Network.Wai.Handler.Warp.Date as D
3131import qualified Network.Wai.Handler.Warp.FdCache as F
32+import qualified Network.Wai.Handler.Warp.FileInfoCache as I
3233import Network.Wai.Handler.Warp.HTTP2 (http2, isHTTP2)
3334import Network.Wai.Handler.Warp.Header
3435import Network.Wai.Handler.Warp.ReadInt
@@ -237,13 +238,18 @@ runServeSettingsConnectionMakerSecure :: Settings
237238runServeSettingsConnectionMakerSecure set getConnMaker serveConn = do
238239 settingsBeforeMainLoop set
239240 counter <- newCounter
240−
241− D.withDateCache $ \dc ->
242− F.withFdCache fdCacheDurationInSeconds $ \fc ->
243− withTimeoutManager $ \tm ->
244− acceptConnection set getConnMaker serveConn dc fc tm counter
241+ withII $ acceptConnection set getConnMaker serveConn counter
245242 where
243+ withII action =
244+ D.withDateCache $ \dc ->
245+ F.withFdCache fdCacheDurationInSeconds $ \fc ->
246+ I.withFileInfoCache fdFileInfoDurationInSeconds $ \get ->
247+ withTimeoutManager $ \tm -> do
248+ let ii0 = InternalInfo undefined tm fc get dc -- fixme: undefined
249+ action ii0
250+
246251 fdCacheDurationInSeconds = settingsFdCacheDuration set * 1000000
252+ fdFileInfoDurationInSeconds = settingsFileInfoCacheDuration set * 1000000
247253 withTimeoutManager f = case settingsManager set of
248254 Just tm -> f tm
249255 Nothing -> bracket
@@ -267,12 +273,10 @@ runServeSettingsConnectionMakerSecure set getConnMaker serveConn = do
267273acceptConnection :: Settings
268274 -> IO (IO (Connection, Transport), SockAddr)
269275 -> ServeConnection
270− -> D.DateCache
271− -> Maybe F.MutableFdCache
272− -> T.Manager
273276 -> Counter
277+ -> InternalInfo
274278 -> IO ()
275−acceptConnection set getConnMaker serveConn dc fc tm counter = do
279+acceptConnection set getConnMaker serveConn counter ii0 = do
276280 -- First mask all exceptions in acceptLoop. This is necessary to
277281 -- ensure that no async exception is throw between the call to
278282 -- acceptNewConnection and the registering of connClose.
@@ -294,7 +298,7 @@ acceptConnection set getConnMaker serveConn dc fc tm counter = do
294298 case mx of
295299 Nothing -> return ()
296300 Just (mkConn, addr) -> do
297− fork set mkConn addr serveConn dc fc tm counter
301+ fork set mkConn addr serveConn counter ii0
298302 acceptLoop
299303
300304 acceptNewConnection = do
@@ -319,12 +323,10 @@ fork :: Settings
319323 -> IO (Connection, Transport)
320324 -> SockAddr
321325 -> ServeConnection
322− -> D.DateCache
323− -> Maybe F.MutableFdCache
324− -> T.Manager
325326 -> Counter
327+ -> InternalInfo
326328 -> IO ()
327−fork set mkConn addr serveConn dc fc tm counter = settingsFork set $ \ unmask ->
329+fork set mkConn addr serveConn counter ii0 = settingsFork set $ \ unmask ->
328330 -- Run the connection maker to get a new connection, and ensure
329331 -- that the connection is closed. If the mkConn call throws an
330332 -- exception, we will leak the connection. If the mkConn call is
@@ -339,9 +341,9 @@ fork set mkConn addr serveConn dc fc tm counter = settingsFork set $ \ unmask ->
339341
340342 -- We need to register a timeout handler for this thread, and
341343 -- cancel that handler as soon as we exit.
342− bracket (T.registerKillThread tm) T.cancel $ \th ->
344+ bracket (T.registerKillThread (timeoutManager ii0)) T.cancel $ \th ->
343345
344− let ii = InternalInfo th tm fc dc
346+ let ii = ii0 { threadHandle = th }
345347 -- We now have fully registered a connection close handler
346348 -- in the case of all exceptions, so it is safe to one
347349 -- again allow async exceptions.
warp/Network/Wai/Handler/Warp/Settings.hs+2
@@ -48,6 +48,7 @@ data Settings = Settings
4848 , settingsTimeout :: Int -- ^ Timeout value in seconds. Default value: 30
4949 , settingsManager :: Maybe Manager -- ^ Use an existing timeout manager instead of spawning a new one. If used, 'settingsTimeout' is ignored. Default is 'Nothing'
5050 , settingsFdCacheDuration :: Int -- ^ Cache duration time of file descriptors in seconds. 0 means that the cache mechanism is not used. Default value: 0
51+ , settingsFileInfoCacheDuration :: Int -- ^ Cache duration time of file information in seconds. 0 means that the cache mechanism is not used. Default value: 0
5152 , settingsBeforeMainLoop :: IO ()
5253 -- ^ Code to run after the listening socket is ready but before entering
5354 -- the main event loop. Useful for signaling to tests that they can start
@@ -119,6 +120,7 @@ defaultSettings = Settings
119120 , settingsTimeout = 30
120121 , settingsManager = Nothing
121122 , settingsFdCacheDuration = 0
123+ , settingsFileInfoCacheDuration = 0
122124 , settingsBeforeMainLoop = return ()
123125 , settingsFork = void . forkIOWithUnmask
124126 , settingsNoParsePath = False
warp/Network/Wai/Handler/Warp/Types.hs+2
@@ -13,6 +13,7 @@ import Data.Word (Word16, Word8)
1313import Foreign.Ptr (Ptr)
1414import qualified Network.Wai.Handler.Warp.Date as D
1515import qualified Network.Wai.Handler.Warp.FdCache as F
16+import qualified Network.Wai.Handler.Warp.FileInfoCache as I
1617import qualified Network.Wai.Handler.Warp.Timeout as T
1718
1819#ifndef WINDOWS
@@ -118,6 +119,7 @@ data InternalInfo = InternalInfo {
118119 threadHandle :: T.Handle
119120 , timeoutManager :: T.Manager
120121 , fdCacher :: Maybe F.MutableFdCache
122+ , fileInfo :: FilePath -> IO I.FileInfo
121123 , dateCacher :: D.DateCache
122124 }
123125
warp/test/ResponseSpec.hs+2−1
@@ -9,6 +9,7 @@ import Network (connectTo, PortID (PortNumber))
99import Network.HTTP.Types
1010import Network.Wai
1111import Network.Wai.Handler.Warp
12+import Network.Wai.Handler.Warp.FileInfoCache
1213import Network.Wai.Handler.Warp.Response
1314import RunSpec (withApp)
1415import System.IO (hClose, hFlush)
@@ -76,7 +77,7 @@ testFileRange :: String
7677 -> Either String (Status, ResponseHeaders, Integer, Integer)
7778 -> Spec
7879testFileRange desc s rsphdr file mPart mRange ans = it desc $ do
79− eres <- fileRange s rsphdr file mPart mRange
80+ eres <- fileRange s rsphdr file mPart mRange getInfo
8081 let res = case eres of
8182 Left e -> Left $ show e
8283 Right r -> Right r
warp/warp.cabal+3−2
@@ -53,6 +53,7 @@ Library
5353 , stm >= 2.3
5454 , word8
5555 , hashable
56+ , unordered-containers
5657 if flag(network-bytestring)
5758 Build-Depends: network >= 2.2.1.5 && < 2.2.3
5859 , network-bytestring >= 0.1.3 && < 0.1.4
@@ -65,6 +66,7 @@ Library
6566 Network.Wai.Handler.Warp.Counter
6667 Network.Wai.Handler.Warp.Date
6768 Network.Wai.Handler.Warp.FdCache
69+ Network.Wai.Handler.Warp.FileInfoCache
6870 Network.Wai.Handler.Warp.HTTP2
6971 Network.Wai.Handler.Warp.HTTP2.EncodeFrame
7072 Network.Wai.Handler.Warp.HTTP2.HPACK
@@ -99,7 +101,6 @@ Library
99101 if os(windows)
100102 Cpp-Options: -DWINDOWS
101103 Build-Depends: time
102− , old-locale
103104 else
104105 Build-Depends: unix
105106 , http-date
@@ -152,7 +153,6 @@ Test-Suite spec
152153 , QuickCheck
153154 , hspec >= 1.3
154155 , time
155− , old-locale
156156 , text
157157 , streaming-commons >= 0.1.10
158158 , async
@@ -164,6 +164,7 @@ Test-Suite spec
164164 , http2 >= 1.3
165165 , word8
166166 , hashable
167+ , unordered-containers
167168
168169 if (os(linux) || os(freebsd) || os(darwin)) && flag(allow-sendfilefd)
169170 Cpp-Options: -DSENDFILEFD