comparison piecrust/serving.py @ 258:7ec06ec14247

serve: Use Etags and 304 responses for assets.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 22 Feb 2015 22:02:59 -0800
parents da5e6e00fb41
children 980bbbd0705e
comparison
equal deleted inserted replaced
257:e6ae65212c32 258:7ec06ec14247
6 import time 6 import time
7 import queue 7 import queue
8 import os.path 8 import os.path
9 import hashlib 9 import hashlib
10 import logging 10 import logging
11 import datetime
11 import threading 12 import threading
12 from werkzeug.exceptions import ( 13 from werkzeug.exceptions import (
13 NotFound, MethodNotAllowed, InternalServerError, HTTPException) 14 NotFound, MethodNotAllowed, InternalServerError, HTTPException)
14 from werkzeug.wrappers import Request, Response 15 from werkzeug.wrappers import Request, Response
15 from werkzeug.wsgi import ClosingIterator, wrap_file 16 from werkzeug.wsgi import ClosingIterator, wrap_file
167 os.path.dirname(__file__), 168 os.path.dirname(__file__),
168 'resources', 'server') 169 'resources', 'server')
169 full_path = os.path.join(mount, rel_req_path) 170 full_path = os.path.join(mount, rel_req_path)
170 try: 171 try:
171 response = self._make_wrapped_file_response( 172 response = self._make_wrapped_file_response(
172 environ, full_path) 173 environ, request, full_path)
173 return response 174 return response
174 except OSError: 175 except OSError:
175 pass 176 pass
176 177
177 debug_mount = '/__piecrust_debug/' 178 debug_mount = '/__piecrust_debug/'
200 else: 201 else:
201 full_path = os.path.join(self._out_dir, rel_req_path) 202 full_path = os.path.join(self._out_dir, rel_req_path)
202 203
203 try: 204 try:
204 response = self._make_wrapped_file_response( 205 response = self._make_wrapped_file_response(
205 environ, full_path) 206 environ, request, full_path)
206 return response 207 return response
207 except OSError: 208 except OSError:
208 pass 209 pass
209 return None 210 return None
210 211
214 215
215 full_path = os.path.join(app.root_dir, request.path[len('/_asset/'):]) 216 full_path = os.path.join(app.root_dir, request.path[len('/_asset/'):])
216 if not os.path.isfile(full_path): 217 if not os.path.isfile(full_path):
217 return None 218 return None
218 219
219 return self._make_wrapped_file_response(environ, full_path) 220 return self._make_wrapped_file_response(environ, request, full_path)
220 221
221 def _try_serve_page(self, app, environ, request): 222 def _try_serve_page(self, app, environ, request):
222 # Try to find what matches the requested URL. 223 # Try to find what matches the requested URL.
223 req_path = request.path 224 req_path = request.path
224 page_num = 1 225 page_num = 1
350 "falling back to uncompressed.") 351 "falling back to uncompressed.")
351 response.set_data(rp_content) 352 response.set_data(rp_content)
352 353
353 return response 354 return response
354 355
355 def _make_wrapped_file_response(self, environ, path): 356 def _make_wrapped_file_response(self, environ, request, path):
356 logger.debug("Serving %s" % path) 357 logger.debug("Serving %s" % path)
358
359 # Check if we can return a 304 status code.
360 mtime = os.path.getmtime(path)
361 etag_str = '%s$$%s' % (path, mtime)
362 etag = hashlib.md5(etag_str.encode('utf8')).hexdigest()
363 if etag in request.if_none_match:
364 response = Response()
365 response.status_code = 304
366 return response
367
357 wrapper = wrap_file(environ, open(path, 'rb')) 368 wrapper = wrap_file(environ, open(path, 'rb'))
358 response = Response(wrapper) 369 response = Response(wrapper)
359 _, ext = os.path.splitext(path) 370 _, ext = os.path.splitext(path)
371 response.set_etag(etag)
372 response.last_modified = datetime.datetime.fromtimestamp(mtime)
360 response.mimetype = self._mimetype_map.get( 373 response.mimetype = self._mimetype_map.get(
361 ext.lstrip('.'), 'text/plain') 374 ext.lstrip('.'), 'text/plain')
362 return response 375 return response
363 376
364 def _handle_error(self, exception, environ, start_response): 377 def _handle_error(self, exception, environ, start_response):