Created
February 10, 2011 15:39
-
-
Save dnouri/820730 to your computer and use it in GitHub Desktop.
A 136 line alternative to plone.{app.,}caching that includes policy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datetime | |
import Acquisition | |
from zope import interface | |
from zope import component | |
import plone.postpublicationhook.interfaces | |
#from collective.skinny.interfaces import IPublicLayer | |
CACHE_POLICY_HEADER = 'x-caching-policy' | |
def _set_max_age(response, delta, cache_ctrl=None): | |
"""Sets max-age and expires headers based on the timedelta `delta`. | |
If `cache_ctrl` is not None, I'll add items found therein to the | |
Cache-Control header. | |
Will overwrite existing values and preserve non overwritten ones. | |
""" | |
if cache_ctrl is None: | |
cache_ctrl = {} | |
seconds = delta.seconds + delta.days * 24 * 60 * 60 | |
if seconds < 0: | |
seconds = 0 | |
now = datetime.datetime.utcnow() | |
cache_ctrl.setdefault('max-age', seconds) | |
# Preserve an existing cache-control header: | |
existing = response.headers.get('cache-control') | |
if existing: | |
for e in [e.strip() for e in existing.split(',')]: | |
kv = e.split('=') | |
if len(kv) == 2: | |
cache_ctrl.setdefault(kv[0], kv[1]) | |
else: | |
cache_ctrl.setdefault(kv[0], None) | |
# Render the cache-control header: | |
cache_control_header = [] | |
for key, value in sorted(cache_ctrl.items()): | |
if value is None: | |
cache_control_header.append(key) | |
else: | |
cache_control_header.append('%s=%s' % (key, value)) | |
cache_control_header = ','.join(cache_control_header) | |
response.setHeader('cache-control', cache_control_header) | |
response.setHeader( | |
'expires', (now + delta).strftime("%a, %d %b %Y %H:%M:%S GMT")) | |
# This is our mapping of caching policies (X-Caching-Policy) to | |
# functions that set the response headers accordingly: | |
caching_policies = { | |
'Cache HTML': | |
lambda response: _set_max_age(response, datetime.timedelta(days=-1), | |
cache_ctrl={'s-maxage': '3600'}), | |
'Cache Media Content': | |
lambda response: _set_max_age(response, datetime.timedelta(hours=4)), | |
'Cache Resource': | |
lambda response: _set_max_age(response, datetime.timedelta(days=32), | |
cache_ctrl={'public': None}), | |
'No Cache': | |
lambda response: _set_max_age(response, datetime.timedelta(days=-1)), | |
} | |
def _is_logged_in(request): | |
return request.cookies.get('__ac') or request.cookies.get('user') | |
def _choose_caching_policy(object, request): | |
content_type = request.response.headers.get('content-type', '') | |
# Don't cache requests other than GET or HEAD | |
if request.get('REQUEST_METHOD') not in ('GET', 'HEAD'): | |
return 'No Cache' | |
# Don't cache responses that set a cookie | |
if request.response.getHeader('Set-Cookie'): | |
return 'No Cache' | |
# Try to find the portal_type | |
chain = Acquisition.aq_chain(object) | |
if len(chain) > 1: | |
# Object is probably a view, so let's look under the hood: | |
context = chain[1] | |
portal_type = getattr(Acquisition.aq_base(context), 'portal_type', None) | |
elif getattr(Acquisition.aq_base(object), 'im_self'): | |
# Object is an instance method: | |
portal_type = getattr(object.im_self, 'portal_type', None) | |
else: | |
portal_type = None | |
# -- Don't cache portal root redirect | |
if portal_type == 'Plone Site' and request.response.status == 302: | |
return 'No Cache' | |
# -- HTML pages | |
elif content_type.startswith('text/html'): | |
return 'Cache HTML' | |
# -- Media content | |
elif portal_type is not None: | |
return 'Cache Media Content' | |
# -- Resources | |
elif '/++resource++' in request.URL: | |
return 'Cache Resource' | |
else: | |
pass # Not being clever | |
@component.adapter(interface.Interface, | |
plone.postpublicationhook.interfaces.IAfterPublicationEvent) | |
def set_cache_headers(object, event): | |
request = event.request | |
response = request.response | |
# Only handle cache headers of public skin | |
# if not IPublicLayer.providedBy(request): | |
# return | |
# If no caching policy was previously set, we'll choose one at this point: | |
caching_policy = response.headers.get(CACHE_POLICY_HEADER) | |
if caching_policy is None: | |
caching_policy = _choose_caching_policy(object, request) | |
if caching_policy is not None: | |
# Set a header on the response with the policy chosen | |
response.setHeader(CACHE_POLICY_HEADER, caching_policy) | |
# Here's where we actually set the cache headers: | |
if caching_policy: | |
caching_policies[caching_policy](response) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment