Skip to content

Commit 6f1dc5d

Browse files
committed
cache etag and mtime, recalc etag only on mtime change
1 parent ab8af04 commit 6f1dc5d

1 file changed

Lines changed: 79 additions & 2 deletions

File tree

livereload/handlers.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
:copyright: (c) 2013 by Hsiaoming Yang
99
:license: BSD, see LICENSE for more details.
1010
"""
11-
11+
import datetime
12+
import hashlib
1213
import os
14+
import stat
1315
import time
1416
import logging
1517
from tornado import web
1618
from tornado import ioloop
1719
from tornado import escape
20+
from tornado.log import gen_log
1821
from tornado.websocket import WebSocketHandler
1922
from tornado.util import ObjectDict
2023

@@ -133,6 +136,80 @@ def on_message(self, message):
133136
LiveReloadHandler.waiters.add(self)
134137

135138

139+
class MtimeStaticFileHandler(web.StaticFileHandler):
140+
_static_mtimes = {} # type: typing.Dict
141+
142+
@classmethod
143+
def get_content_modified_time(cls, abspath):
144+
"""Returns the time that ``abspath`` was last modified.
145+
146+
May be overridden in subclasses. Should return a `~datetime.datetime`
147+
object or None.
148+
"""
149+
stat_result = os.stat(abspath)
150+
modified = datetime.datetime.utcfromtimestamp(
151+
stat_result[stat.ST_MTIME])
152+
return modified
153+
154+
@classmethod
155+
def get_content_version(cls, abspath):
156+
"""Returns a version string for the resource at the given path.
157+
158+
This class method may be overridden by subclasses. The
159+
default implementation is a hash of the file's contents.
160+
161+
.. versionadded:: 3.1
162+
"""
163+
data = cls.get_content(abspath)
164+
hasher = hashlib.md5()
165+
166+
mtime_data = format(cls.get_content_modified_time(abspath), "%Y-%m-%d %H:%M:%S")
167+
168+
hasher.update(mtime_data.encode())
169+
170+
if isinstance(data, bytes):
171+
hasher.update(data)
172+
else:
173+
for chunk in data:
174+
hasher.update(chunk)
175+
return hasher.hexdigest()
176+
177+
@classmethod
178+
def _get_cached_version(cls, abs_path):
179+
def _load_version(abs_path):
180+
try:
181+
hsh = cls.get_content_version(abs_path)
182+
mtm = cls.get_content_modified_time(abs_path)
183+
184+
return mtm, hsh
185+
except Exception:
186+
gen_log.error("Could not open static file %r", abs_path)
187+
return None, None
188+
189+
with cls._lock:
190+
hashes = cls._static_hashes
191+
mtimes = cls._static_mtimes
192+
193+
if abs_path not in hashes:
194+
mtm, hsh = _load_version(abs_path)
195+
196+
hashes[abs_path] = mtm
197+
mtimes[abs_path] = hsh
198+
else:
199+
hsh = hashes.get(abs_path)
200+
mtm = mtimes.get(abs_path)
201+
202+
if mtm != cls.get_content_modified_time(abs_path):
203+
mtm, hsh = _load_version(abs_path)
204+
205+
hashes[abs_path] = mtm
206+
mtimes[abs_path] = hsh
207+
208+
if hsh:
209+
return hsh
210+
return None
211+
212+
136213
class LiveReloadJSHandler(web.RequestHandler):
137214

138215
def get(self):
@@ -150,6 +227,6 @@ def get(self):
150227
self.write('ok')
151228

152229

153-
class StaticFileHandler(web.StaticFileHandler):
230+
class StaticFileHandler(MtimeStaticFileHandler):
154231
def should_return_304(self):
155232
return False

0 commit comments

Comments
 (0)