# HG changeset patch # User Pierre-Yves David # Date 1332164263 -3600 # Node ID fe60a77ae4a7c5b2af92dddbd1610d7ab07afbd5 # Parent cb838b126b0786b82da9fbeaf69683d63346c257 static-file: properly set/use cache header for static file (closes #2255013) This changesets enables the standard http cache mechanism where the static controller may reply "304 Not modified" based on `last-modified` in HTTP response and `if-modified-since` in HTTP query. The last modified time is computed using the file-system information. The pre-existing logic using an `Expires` header to prevent client from sending request stay in place. The new logic just prevents sending the file again if not necessary. diff -r cb838b126b07 -r fe60a77ae4a7 web/test/unittest_views_staticcontrollers.py --- a/web/test/unittest_views_staticcontrollers.py Tue Mar 20 18:25:06 2012 +0100 +++ b/web/test/unittest_views_staticcontrollers.py Mon Mar 19 14:37:43 2012 +0100 @@ -1,5 +1,6 @@ from __future__ import with_statement +from logilab.common.testlib import tag, Tags from cubicweb.devtools.testlib import CubicWebTC import os @@ -10,8 +11,31 @@ from cubicweb.web import StatusResponse from cubicweb.web.views.staticcontrollers import ConcatFilesHandler +class StaticControllerCacheTC(CubicWebTC): + + tags = CubicWebTC.tags | Tags('static_controller', 'cache', 'http') + + + def _publish_static_files(self, url, header={}): + req = self.request(headers=header) + req._url = url + return self.app_handle_request(req, url), req + + def test_static_file_are_cached(self): + _, req = self._publish_static_files('data/cubicweb.css') + self.assertEqual(200, req.status_out) + self.assertIn('last-modified', req.headers_out) + next_headers = { + 'if-modified-since': req.get_response_header('last-modified', raw=True), + } + _, req = self._publish_static_files('data/cubicweb.css', next_headers) + self.assertEqual(304, req.status_out) + + class ConcatFilesTC(CubicWebTC): + tags = CubicWebTC.tags | Tags('static_controller', 'concat') + def tearDown(self): super(ConcatFilesTC, self).tearDown() self._cleanup_concat_cache() diff -r cb838b126b07 -r fe60a77ae4a7 web/views/staticcontrollers.py --- a/web/views/staticcontrollers.py Tue Mar 20 18:25:06 2012 +0100 +++ b/web/views/staticcontrollers.py Mon Mar 19 14:37:43 2012 +0100 @@ -67,6 +67,16 @@ # the HTTP RFC recommands not going further than 1 year ahead expires = datetime.now() + timedelta(days=6*30) self._cw.set_header('Expires', generateDateTime(mktime(expires.timetuple()))) + + # XXX system call to os.stats could be cached once and for all in + # production mode (where static files are not expected to change) + # + # Note that: we do a osp.isdir + osp.isfile before and a potential + # os.read after. Improving this specific call will not help + # + # Real production environment should use dedicated static file serving. + self._cw.set_header('last-modified', generateDateTime(os.stat(path).st_mtime)) + self._cw.validate_cache() # XXX elif uri.startswith('/https/'): uri = uri[6:] mimetype, encoding = mimetypes.guess_type(path) self._cw.set_content_type(mimetype, osp.basename(path), encoding)