Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
A 136 line alternative to plone.{app.,}caching that includes policy
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