diff -r 166e6d5d8e17 -r d5b1b75805dd web/request.py --- a/web/request.py Thu Mar 15 17:57:40 2012 +0100 +++ b/web/request.py Thu Mar 15 17:59:27 2012 +0100 @@ -27,6 +27,7 @@ from calendar import timegm from datetime import date, datetime from urlparse import urlsplit +import httplib from itertools import count from warnings import warn @@ -43,8 +44,8 @@ from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, RequestError, StatusResponse) -from cubicweb.web.httpcache import GMTOFFSET -from cubicweb.web.http_headers import Headers, Cookie +from cubicweb.web.httpcache import GMTOFFSET, get_validators +from cubicweb.web.http_headers import Headers, Cookie, parseDateTime _MARKER = object() @@ -750,14 +751,33 @@ return 'view' def validate_cache(self): - """raise a `DirectResponse` exception if a cached page along the way + """raise a `StatusResponse` exception if a cached page along the way exists and is still usable. calls the client-dependant implementation of `_validate_cache` """ - self._validate_cache() - if self.http_method() == 'HEAD': - raise StatusResponse(200, '') + modified = True + if self.get_header('Cache-Control') not in ('max-age=0', 'no-cache'): + # Here, we search for any invalid 'not modified' condition + # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3 + validators = get_validators(self._headers_in) + if validators: # if we have no + modified = any(func(val, self.headers_out) for func, val in validators) + # Forge expected response + if modified: + if 'Expires' not in self.headers_out: + # Expires header seems to be required by IE7 -- Are you sure ? + self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT') + if self.http_method() == 'HEAD': + raise StatusResponse(200, '') + # /!\ no raise, the function returns and we keep processing the request) + else: + # overwrite headers_out to forge a brand new not-modified response + self.headers_out = self._forge_cached_headers() + if self.http_method() in ('HEAD', 'GET'): + raise StatusResponse(httplib.NOT_MODIFIED) + else: + raise StatusResponse(httplib.PRECONDITION_FAILED) # abstract methods to override according to the web front-end ############# @@ -765,11 +785,19 @@ """returns 'POST', 'GET', 'HEAD', etc.""" raise NotImplementedError() - def _validate_cache(self): - """raise a `DirectResponse` exception if a cached page along the way - exists and is still usable - """ - raise NotImplementedError() + def _forge_cached_headers(self): + # overwrite headers_out to forge a brand new not-modified response + headers = Headers() + for header in ( + # Required from sec 10.3.5: + 'date', 'etag', 'content-location', 'expires', + 'cache-control', 'vary', + # Others: + 'server', 'proxy-authenticate', 'www-authenticate', 'warning'): + value = self._headers_in.getRawHeaders(header) + if value is not None: + headers.setRawHeaders(header, value) + return headers def relative_path(self, includeparams=True): """return the normalized path of the request (ie at least relative