Mercurial > libervia-web
diff src/server/pages.py @ 1019:34240d08f682
pages: HTTP cache headers handling:
when checkCache is used, HTTP headers handling cache are now used:
- ETag is first checked, using a hash of the rendered content
- Last-Modified is used as a fallback is client is not handling ETag
When suitable, a HTTP 304 code (Not Modified) wihtout body is returned instead of the whole page.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 21 Jan 2018 13:14:06 +0100 |
parents | 78af5457d3f8 |
children | 66a050b32df8 |
line wrap: on
line diff
--- a/src/server/pages.py Sun Jan 21 13:08:54 2018 +0100 +++ b/src/server/pages.py Sun Jan 21 13:14:06 2018 +0100 @@ -26,6 +26,7 @@ from sat.core.i18n import _ from sat.core import exceptions from sat.tools.common import uri as common_uri +from sat.tools import utils from sat.core.log import getLogger log = getLogger(__name__) from libervia.server.constants import Const as C @@ -38,6 +39,7 @@ import os.path import urllib import time +import hashlib WebsocketMeta = namedtuple("WebsocketMeta", ('url', 'token', 'debug')) @@ -69,11 +71,15 @@ self._created = time.time() self._last_access = self._created self._rendered = rendered + self._hash = hashlib.sha256(rendered).hexdigest() @property def rendered(self): return self._rendered + @property + def hash(self): + return self._hash class CacheURL(CacheBase): @@ -488,6 +494,30 @@ request.prepath.append(pathElement) return urllib.unquote(pathElement).decode('utf-8') + ## Cache handling ## + + def _setCacheHeaders(self, request, cache): + """Set ETag and Last-Modified HTTP headers, used for caching""" + request.setHeader('ETag', cache.hash) + last_modified = self.host.getHTTPDate(cache.created) + request.setHeader('Last-Modified', last_modified) + + def _checkCacheHeaders(self, request, cache): + """Check if a cache condition is set on the request + + if condition is valid, C.HTTP_NOT_MODIFIED is returned + """ + etag_match = request.getHeader('If-None-Match') + if etag_match is not None: + if cache.hash == etag_match: + self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) + else: + modified_match = request.getHeader('If-Modified-Since') + if modified_match is not None: + modified = utils.date_parse(modified_match) + if modified >= int(cache.created): + self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) + def checkCacheSubscribeCb(self, sub_id, service, node): self.cache_pubsub_sub.add((service, node, sub_id)) @@ -509,7 +539,7 @@ C.CACHE_PUBSUB: service: pubsub service node: pubsub node - short: short name of feature (needed if node is empty) + short: short name of feature (needed if node is empty to find namespace) """ if request.postpath: @@ -547,6 +577,8 @@ raise exceptions.InternalError(u'Unknown cache_type') log.debug(u'using cache for {page}'.format(page=self)) cache.last_access = time.time() + self._setCacheHeaders(request, cache) + self._checkCacheHeaders(request, cache) request.write(cache.rendered) request.finish() raise failure.Failure(exceptions.CancelError(u'cache is used')) @@ -696,23 +728,26 @@ redirect_page.renderPage(request, skip_parse_url=skip_parse_url) raise failure.Failure(exceptions.CancelError(u'page redirection is used')) - def pageError(self, request, code=C.HTTP_NOT_FOUND): + def pageError(self, request, code=C.HTTP_NOT_FOUND, no_body=False): """generate an error page and terminate the request @param request(server.Request): HTTP request @param core(int): error code to use + @param no_body: don't write body if True """ - template = u'error/' + unicode(code) + '.html' - request.setResponseCode(code) + if no_body: + request.finish() + else: + template = u'error/' + unicode(code) + '.html' - rendered = self.host.renderer.render( - template, - root_path = '/templates/', - error_code = code, - **request.template_data) + rendered = self.host.renderer.render( + template, + root_path = '/templates/', + error_code = code, + **request.template_data) - self.writeData(rendered, request) + self.writeData(rendered, request) raise failure.Failure(exceptions.CancelError(u'error page is used')) def writeData(self, data, request): @@ -720,15 +755,19 @@ if data is None: self.pageError(request) data_encoded = data.encode('utf-8') - request.write(data_encoded) - request.finish() + if self._do_cache is not None: cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache) - cache[self] = Cache(data_encoded) + page_cache = cache[self] = CachePage(data_encoded) + self._setCacheHeaders(request, page_cache) log.debug(_(u'{page} put in cache for [{profile}]').format( page=self, profile=self._do_cache[0])) self._do_cache = None + self._checkCacheHeaders(request, page_cache) + + request.write(data_encoded) + request.finish() def _subpagesHandler(self, dummy, request): """render subpage if suitable