Commit 7aeacb94 authored by Shane Hathaway's avatar Shane Hathaway

Added StandardCacheManagers product to trunk.

parent 294ea060
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
'''
Accelerated HTTP cache manager --
Adds caching headers to the response so that downstream caches will
cache according to a common policy.
$Id$
'''
from OFS.Cache import Cache, CacheManager
from OFS.SimpleItem import SimpleItem
import time
import Globals
from Globals import HTMLFile
import urlparse, httplib
from urllib import quote
from string import lower, join, split
def http_date(value, format='%a, %d %b %Y %H:%M:%S GMT'):
return time.strftime(format, time.gmtime(value))
class AcceleratedHTTPCache (Cache):
# Note the need to take thread safety into account.
# Also note that objects of this class are not persistent,
# nor do they use acquisition.
def __init__(self):
self.hit_counts = {}
def initSettings(self, kw):
# Note that we lazily allow AcceleratedHTTPCacheManager
# to verify the correctness of the internal settings.
self.__dict__.update(kw)
def ZCache_invalidate(self, ob):
# Note that this only works for default views of objects.
phys_path = os.getPhysicalPath()
if self.hit_counts.has_key(phys_path):
del self.hit_counts[phys_path]
ob_path = quote(join(phys_path, '/'))
results = []
for url in self.notify_urls:
if not url:
continue
# Send the PURGE request to each HTTP accelerator.
if lower(url[:7]) == 'http://':
u = url
else:
u = 'http://' + url
(scheme, host, path, params, query, fragment
) = urlparse.urlparse(u)
if path[-1:] == '/':
p = path[:-1] + ob_path
else:
p = path + ob_path
h = httplib.HTTP(host)
h.putrequest('PURGE', p)
h.endheaders()
errcode, errmsg, headers = h.getreply()
h.getfile().read() # Mandatory for httplib?
results.append('%s %s' % (errcode, errmsg))
return 'Server response(s): ' + join(results, ';')
def ZCache_get(self, ob, view_name, keywords, mtime_func, default):
return default
def ZCache_set(self, ob, data, view_name, keywords, mtime_func):
# Note the blatant ignorance of view_name, keywords, and
# mtime_func. Standard HTTP accelerators are not able to make
# use of this data.
REQUEST = ob.REQUEST
RESPONSE = REQUEST.RESPONSE
anon = 1
u = REQUEST.get('AUTHENTICATED_USER', None)
if u is not None:
if u.getUserName() != 'Anonymous User':
anon = 0
phys_path = ob.getPhysicalPath()
if self.hit_counts.has_key(phys_path):
hits = self.hit_counts[phys_path]
else:
self.hit_counts[phys_path] = hits = [0,0]
if anon:
hits[0] = hits[0] + 1
else:
hits[1] = hits[1] + 1
if not anon and self.anonymous_only:
return
# Set HTTP Expires and Cache-Control headers
seconds=self.interval
expires=http_date(time.time() + seconds)
# RESPONSE.setHeader('Last-Modified', http_date(time.time()))
RESPONSE.setHeader('Cache-Control', 'max-age=%d' % seconds)
RESPONSE.setHeader('Expires', expires)
caches = {}
PRODUCT_DIR = split(__name__, '.')[-2]
class AcceleratedHTTPCacheManager (CacheManager, SimpleItem):
' '
__ac_permissions__ = (
('View management screens', ('getSettings',
'manage_main',
'manage_stats',
'getCacheReport',
'sort_link')),
('Change cache managers', ('manage_editProps',), ('Manager',)),
)
manage_options = (
{'label':'Properties', 'action':'manage_main',
'help':(PRODUCT_DIR, 'Accel.stx'),},
{'label':'Statistics', 'action':'manage_stats',
'help':(PRODUCT_DIR, 'Accel.stx'),},
) + CacheManager.manage_options + SimpleItem.manage_options
meta_type = 'Accelerated HTTP Cache Manager'
def __init__(self, ob_id):
self.id = ob_id
self.title = ''
self._settings = {'anonymous_only':1,
'interval':3600,
'notify_urls':()}
self.__cacheid = '%s_%f' % (id(self), time.time())
def getId(self):
' '
return self.id
ZCacheManager_getCache__roles__ = ()
def ZCacheManager_getCache(self):
cacheid = self.__cacheid
try:
return caches[cacheid]
except KeyError:
cache = AcceleratedHTTPCache()
cache.initSettings(self._settings)
caches[cacheid] = cache
return cache
def getSettings(self):
' '
return self._settings.copy() # Don't let DTML modify it.
manage_main = HTMLFile('propsAccel', globals())
def manage_editProps(self, title, settings=None, REQUEST=None):
' '
if settings is None:
settings = REQUEST
self.title = str(title)
self._settings = {
'anonymous_only':settings['anonymous_only'] and 1 or 0,
'interval':int(settings['interval']),
'notify_urls':tuple(settings['notify_urls']),}
cache = self.ZCacheManager_getCache()
cache.initSettings(self._settings)
if REQUEST is not None:
return self.manage_main(
self, REQUEST, manage_tabs_message='Properties changed.')
manage_stats = HTMLFile('statsAccel', globals())
def _getSortInfo(self):
"""
Returns the value of sort_by and sort_reverse.
If not found, returns default values.
"""
req = self.REQUEST
sort_by = req.get('sort_by', 'anon')
sort_reverse = int(req.get('sort_reverse', 1))
return sort_by, sort_reverse
def getCacheReport(self):
"""
Returns the list of objects in the cache, sorted according to
the user's preferences.
"""
sort_by, sort_reverse = self._getSortInfo()
c = self.ZCacheManager_getCache()
rval = []
for path, (anon, auth) in c.hit_counts.items():
rval.append({'path': join(path, '/'),
'anon': anon,
'auth': auth})
if sort_by:
rval.sort(lambda e1, e2, sort_by=sort_by:
cmp(e1[sort_by], e2[sort_by]))
if sort_reverse:
rval.reverse()
return rval
def sort_link(self, name, id):
"""
Utility for generating a sort link.
"""
# XXX This ought to be in a library or something.
sort_by, sort_reverse = self._getSortInfo()
url = self.absolute_url() + '/manage_stats?sort_by=' + id
newsr = 0
if sort_by == id:
newsr = not sort_reverse
url = url + '&sort_reverse=' + (newsr and '1' or '0')
return '<a href="%s">%s</a>' % (url, name)
Globals.default__class_init__(AcceleratedHTTPCacheManager)
manage_addAcceleratedHTTPCacheManagerForm = HTMLFile('addAccel', globals())
def manage_addAcceleratedHTTPCacheManager(self, id, REQUEST=None):
' '
self._setObject(id, AcceleratedHTTPCacheManager(id))
if REQUEST is not None:
return self.manage_main(self, REQUEST)
# FYI good resource: http://www.web-caching.com/proxy-caches.html
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
'''
RAM cache manager --
Caches the results of method calls in RAM.
$Id$
'''
from OFS.Cache import Cache, CacheManager
from OFS.SimpleItem import SimpleItem
from thread import allocate_lock
import time
import Globals
from Globals import HTMLFile
from string import join, split
try: from cPickle import Pickler
except: from pickle import Pickler
try: from cStringIO import dumps
except: from pickle import dumps
_marker = [] # Create a new marker object.
class CacheException (Exception):
'''
A cache-related exception.
'''
class CacheEntry:
'''
Represents a cached value.
'''
def __init__(self, index, data, view_name):
try:
# This is a protective barrier that hopefully prevents
# us from caching something that might result in memory
# leaks. It's also convenient for determining the
# approximate memory usage of the cache entry.
self.size = len(dumps(index)) + len(dumps(data))
except:
raise CacheException('The data for the cache is not pickleable.')
self.data = data
self.view_name = view_name
self.access_count = 0
class ObjectCacheEntries:
'''
Represents the cache for one Zope object.
'''
hits = 0
misses = 0
def __init__(self, path):
self.physical_path = path
self.lastmod = 0
self.entries = {}
def aggregateIndex(self, view_name, req, req_names, local_keys):
'''
Returns the index to be used when looking for or inserting
a cache entry.
view_name is a string.
local_keys is a mapping or None.
'''
req_index = []
# Note: req_names is already sorted.
for key in req_names:
if req is None:
val = ''
else:
val = req.get(key, '')
req_index.append((str(key), str(val)))
if local_keys:
local_index = []
for key, val in local_keys.items():
local_index.append((str(key), str(val)))
local_index.sort()
else:
local_index = ()
return (str(view_name), tuple(req_index), tuple(local_index))
def getEntry(self, lastmod, index):
if self.lastmod < lastmod:
# Expired.
self.entries = {}
self.lastmod = lastmod
return _marker
return self.entries.get(index, _marker)
def setEntry(self, lastmod, index, data, view_name):
self.lastmod = lastmod
self.entries[index] = CacheEntry(index, data, view_name)
class RAMCache (Cache):
# Note the need to take thread safety into account.
# Also note that objects of this class are not persistent,
# nor do they make use of acquisition.
def __init__(self):
# cache maps physical paths to ObjectCacheEntries.
self.cache = {}
self.writelock = allocate_lock()
self.next_cleanup = 0
def initSettings(self, kw):
# Note that we lazily allow RAMCacheManager
# to verify the correctness of the internal settings.
self.__dict__.update(kw)
def getObjectCacheEntries(self, ob, create=0):
"""
Finds or creates the associated ObjectCacheEntries object.
Remember to lock writelock when calling with the 'create' flag.
"""
cache = self.cache
path = ob.getPhysicalPath()
oc = cache.get(path, None)
if oc is None:
if create:
cache[path] = oc = ObjectCacheEntries(path)
else:
return None
return oc
def countAllEntries(self):
'''
Returns the count of all cache entries.
'''
count = 0
for oc in self.cache.values():
count = count + len(oc.entries)
return count
def countAccesses(self):
'''
Returns a mapping of
(n) -> number of entries accessed (n) times
'''
counters = {}
for oc in self.cache.values():
for entry in oc.entries.values():
access_count = entry.access_count
counters[access_count] = counters.get(
access_count, 0) + 1
return counters
def clearAccessCounters(self):
'''
Clears access_count for each cache entry.
'''
for oc in self.cache.values():
for entry in oc.entries.values():
entry.access_count = 0
def deleteEntriesAtOrBelowThreshold(self, threshold_access_count):
"""
Deletes entries that haven't been accessed recently.
"""
self.writelock.acquire()
try:
for p, oc in self.cache.items():
for agindex, entry in oc.entries.items():
if entry.access_count <= threshold_access_count:
del oc.entries[agindex]
if len(oc.entries) < 1:
del self.cache[p]
finally:
self.writelock.release()
def cleanup(self):
'''
Removes cache entries.
'''
new_count = self.countAllEntries()
if new_count > self.threshold:
counters = self.countAccesses()
priorities = counters.items()
# Remove the least accessed entries until we've reached
# our target count.
if len(priortities) > 0:
priorities.sort()
priorities.reverse()
access_count = 0
for access_count, effect in priorities:
new_count = new_count - effect
if new_count <= self.threshold:
break
self.deleteEntriesAtOrBelowThreshold(access_count)
self.clearAccessCounters()
def getCacheReport(self):
"""
Reports on the contents of the cache.
"""
rval = []
for oc in self.cache.values():
size = 0
ac = 0
views = []
for entry in oc.entries.values():
size = size + entry.size
ac = ac + entry.access_count
view = entry.view_name or '<default>'
if view not in views:
views.append(view)
views.sort()
info = {'path': join(oc.physical_path, '/'),
'hits': oc.hits,
'misses': oc.misses,
'size': size,
'counter': ac,
'views': views,
'entries': len(oc.entries)
}
rval.append(info)
return rval
def ZCache_invalidate(self, ob):
'''
Invalidates the cache entries that apply to ob.
'''
path = ob.getPhysicalPath()
# Invalidates all subobjects as well.
self.writelock.acquire()
try:
for p, oc in self.cache.items():
pp = oc.physical_path
if pp[:len(path)] == path:
del self.cache[p]
finally:
self.writelock.release()
def ZCache_get(self, ob, view_name='', keywords=None,
mtime_func=None, default=None):
'''
Gets a cache entry or returns default.
'''
oc = self.getObjectCacheEntries(ob)
if oc is None:
return default
lastmod = ob.ZCacheable_getModTime(mtime_func)
index = oc.aggregateIndex(view_name, ob.REQUEST,
self.request_vars, keywords)
entry = oc.getEntry(lastmod, index)
if entry is _marker:
return default
oc.hits = oc.hits + 1
entry.access_count = entry.access_count + 1
return entry.data
def ZCache_set(self, ob, data, view_name='', keywords=None,
mtime_func=None):
'''
Sets a cache entry.
'''
now = time.time()
if self.next_cleanup <= now:
self.cleanup()
self.next_cleanup = now + self.cleanup_interval
lastmod = ob.ZCacheable_getModTime(mtime_func)
self.writelock.acquire()
try:
oc = self.getObjectCacheEntries(ob, create=1)
index = oc.aggregateIndex(view_name, ob.REQUEST,
self.request_vars, keywords)
oc.setEntry(lastmod, index, data, view_name)
oc.misses = oc.misses + 1
finally:
self.writelock.release()
caches = {}
PRODUCT_DIR = split(__name__, '.')[-2]
class RAMCacheManager (CacheManager, SimpleItem):
' '
__ac_permissions__ = (
('View management screens', ('getSettings',
'manage_main',
'manage_stats',
'getCacheReport',
'sort_link',)),
('Change cache managers', ('manage_editProps',), ('Manager',)),
)
manage_options = (
{'label':'Properties', 'action':'manage_main',
'help':(PRODUCT_DIR, 'RAM.stx'),},
{'label':'Statistics', 'action':'manage_stats',
'help':(PRODUCT_DIR, 'RAM.stx'),},
) + CacheManager.manage_options + SimpleItem.manage_options
meta_type = 'RAM Cache Manager'
def __init__(self, ob_id):
self.id = ob_id
self.title = ''
self._settings = {
'threshold': 1000,
'cleanup_interval': 300,
'request_vars': ('AUTHENTICATED_USER',)}
self.__cacheid = '%s_%f' % (id(self), time.time())
def getId(self):
' '
return self.id
ZCacheManager_getCache__roles__ = ()
def ZCacheManager_getCache(self):
cacheid = self.__cacheid
try:
return caches[cacheid]
except KeyError:
cache = RAMCache()
cache.initSettings(self._settings)
caches[cacheid] = cache
return cache
def getSettings(self):
'Returns the current cache settings.'
return self._settings.copy()
manage_main = HTMLFile('propsRCM', globals())
def manage_editProps(self, title, settings=None, REQUEST=None):
'Changes the cache settings.'
if settings is None:
settings = REQUEST
self.title = str(title)
request_vars = list(settings['request_vars'])
request_vars.sort()
self._settings = {
'threshold': int(settings['threshold']),
'cleanup_interval': int(settings['cleanup_interval']),
'request_vars': tuple(request_vars)}
cache = self.ZCacheManager_getCache()
cache.initSettings(self._settings)
if REQUEST is not None:
return self.manage_main(
self, REQUEST, manage_tabs_message='Properties changed.')
manage_stats = HTMLFile('statsRCM', globals())
def _getSortInfo(self):
"""
Returns the value of sort_by and sort_reverse.
If not found, returns default values.
"""
req = self.REQUEST
sort_by = req.get('sort_by', 'hits')
sort_reverse = int(req.get('sort_reverse', 1))
return sort_by, sort_reverse
def getCacheReport(self):
"""
Returns the list of objects in the cache, sorted according to
the user's preferences.
"""
sort_by, sort_reverse = self._getSortInfo()
c = self.ZCacheManager_getCache()
rval = c.getCacheReport()
if sort_by:
rval.sort(lambda e1, e2, sort_by=sort_by:
cmp(e1[sort_by], e2[sort_by]))
if sort_reverse:
rval.reverse()
return rval
def sort_link(self, name, id):
"""
Utility for generating a sort link.
"""
sort_by, sort_reverse = self._getSortInfo()
url = self.absolute_url() + '/manage_stats?sort_by=' + id
newsr = 0
if sort_by == id:
newsr = not sort_reverse
url = url + '&sort_reverse=' + (newsr and '1' or '0')
return '<a href="%s">%s</a>' % (url, name)
Globals.default__class_init__(RAMCacheManager)
manage_addRAMCacheManagerForm = HTMLFile('addRCM', globals())
def manage_addRAMCacheManager(self, id, REQUEST=None):
'Adds a RAM cache manager to the folder.'
self._setObject(id, RAMCacheManager(id))
if REQUEST is not None:
return self.manage_main(self, REQUEST)
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
'''
Some standard Zope cache managers from Digital Creations.
$Id$
'''
import RAMCacheManager
import AcceleratedHTTPCacheManager
def initialize(context):
context.registerClass(
RAMCacheManager.RAMCacheManager,
constructors = (RAMCacheManager.manage_addRAMCacheManagerForm,
RAMCacheManager.manage_addRAMCacheManager),
icon="cache.gif"
)
context.registerClass(
AcceleratedHTTPCacheManager.AcceleratedHTTPCacheManager,
constructors = (
AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManagerForm,
AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManager),
icon="cache.gif"
)
context.registerHelp()
<html><head><title>Add Accelerated HTTP Cache Manager</title></head>
<body bgcolor="#ffffff">
<h2>Add Accelerated HTTP Cache Manager</h2>
<form action="manage_addAcceleratedHTTPCacheManager" method="POST">
<table>
<tr>
<th>Id</th>
<td><input type="text" name="id"></td>
</tr>
</table>
<input type="submit" name="submit" value="Add">
</form>
</body>
</html>
<html><head><title>Add RAM Cache Manager</title></head>
<body bgcolor="#ffffff">
<h2>Add RAM Cache Manager</h2>
<form action="manage_addRAMCacheManager" method="POST">
<table>
<tr>
<th>Id</th>
<td><input type="text" name="id"></td>
</tr>
</table>
<input type="submit" name="submit" value="Add">
</form>
</body>
</html>
Accelerated HTTP Cache Managers
For background information, see the
<a href="../../../OFSP/Help/Caching.stx">description of cache management</a>.
The HTTP protocol provides for headers that can indicate to
downstream proxy caches, browser caches, and dedicated caches that
certain documents and images are cacheable. Most images, for example,
can safely be cached for a long time. Anonymous visits to most
primary pages can be cached as well.
An accelerated HTTP cache manager lets you control the headers that
get sent with the responses to requests so that downstream caches
will know what to cache and for how long. This allows you to reduce
the traffic to your site and handle larger loads than otherwise
possible. You can associate accelerated HTTP cache managers with
any kind of cacheable object that can be viewed through the web.
The main risk in using an accelerated HTTP cache manager involves
a part of a page setting headers that apply to the whole response.
If, for example, your home page contains three parts that are
cacheable and one of those parts is associated with an accelerated
HTTP cache manager, Zope will return the headers set by the part of
the page, making downstream caches think that the whole page should
be cached.
The workaround is simple: don't use an accelerated HTTP cache manager
with objects that make up parts of a page unless you really know
what you're doing.
There are some parameters available for accelerated HTTP cache managers.
The interval is the number of seconds the downstream caches should
cache the object. 3600 seconds, or one hour, is a good default.
If you find that some objects need one interval and other objects
should be set to another interval, use multiple cache managers.
If you set the *cache anonymous connections only* checkbox, you
will reduce the possibility of caching private data.
The *notify URLs* parameter allows you to specify the URLs of
specific downstream caches so they can receive invalidation messages
as 'PURGE' directives. Dedicated HTTP cache software such
as Squid will clear cached data for a given URL when receiving the
'PURGE' directive. (More details below.)
Simple statistics are provided. Remember that the only time Zope
receives a request that goes through an HTTP cache is when the
HTTP cache had a *miss*. So the hits seen by Zope correspond to
misses seen by the HTTP cache. To do traffic analysis, you should
consult the downstream HTTP caches.
When testing the accelerated HTTP cache manager, keep in mind that
the *reload* button on most browsers causes the 'Pragma: no-cache'
header to be sent, forcing HTTP caches to reload the page as well.
Try using telnet, netcat, or tcpwatch to observe the headers.
To allow Zope to execute the Squid PURGE directive, make sure the
following lines or the equivalent are in squid.conf (changing
'localhost' to the correct host name if Squid is on a different
machine)::
acl PURGE method purge
http_access allow localhost
http_access allow purge localhost
http_access deny purge
http_access deny all
RAM Cache Managers
For background information, see the
<a href="../../../OFSP/Help/Caching.stx">description of cache management</a>.
The RAM cache manager allows you to cache the result of calling DTML
methods, Python scripts, and SQL methods in memory. It allows you
to cache entire pages as well as parts of pages. It provides
access statistics and simple configuration options.
Storing the result in memory results in the fastest possible cache
retrieval, but carries some risks. Unconstrained, it can consume too
much RAM. And it doesn't reduce network traffic, it only helps
Zope return a result more quickly.
Fortunately, RAM cache managers have tunable parameters. You can
configure the threshold on the number of entries that should be in
the cache, which defaults to 1000. Reduce it if the cache is taking
up too much memory or increase it if entries are being cleared too
often.
You can also configure the cleanup interval. If the RAM cache is
fluctuating too much in memory usage, reduce the cleanup interval.
Finally, you can configure the list of REQUEST variables that will
be used in the cache key. This can be a simple and effective way
to distinguish requests from authenticated versus anonymous users
or those with session cookies.
If you find that some of your objects need certain cache parameters
while others need somewhat different parameters, create multiple
RAM cache managers.
The 'Statistics' tab allows you to view a summary of the contents
of the cache. Click the column headers to re-sort the list, twice
to sort backwards. You can use the statistics to gauge the
benefit of caching each of your objects. For a given object, if
the number of hits is less than or not much greater than the number
of misses, you probably need to re-evaluate how that object is
cached.
Although Zope does not prevent you from doing so, it generally does
not make sense to associate an image or a file object with a RAM
cache manager. It will not cache the image or file data, since the
data is already available in RAM. However, another kind of cache
manager, an *accelerated HTTP cache manager*, is available and is
suitable for images and file objects.
<html><head><title>Accelerated HTTP Cache Manager properties</title></head>
<body bgcolor="#ffffff">
<dtml-var manage_tabs>
<h2>Properties</h2>
<form action="manage_editProps" method="POST">
<dtml-with getSettings mapping>
<table>
<tr>
<th valign="top" align="left">Id</th>
<td>&dtml-id;</td>
</tr>
<tr>
<th valign="top" align="left">Title</th>
<td><input type="text" name="title" value="&dtml-title;"></td>
</tr>
<tr>
<th valign="top" align="left">Interval (seconds)</th>
<td><input type="text" name="interval"
value="&dtml-interval;"></td>
</tr>
<tr>
<th valign="top" align="left">Cache anonymous connections only</th>
<td><input type="checkbox" name="anonymous_only" value="1"
<dtml-if anonymous_only>checked="checked"</dtml-if>></td>
</tr>
<tr>
<th valign="top" align="left">Notify URLs (via PURGE)</th>
<td><textarea name="notify_urls:lines" rows="5" cols="30"
><dtml-in notify_urls>&dtml-sequence-item;
</dtml-in></textarea></td>
</tr>
</table>
</dtml-with>
<input type="submit" name="submit" value="Save">
</form>
</body>
</html>
<html><head><title>RAMCacheManager properties</title></head>
<body bgcolor="#ffffff">
<dtml-var manage_tabs>
<h2>Properties</h2>
<form action="manage_editProps" method="POST">
<dtml-with getSettings mapping>
<table>
<tr>
<th valign="top" align="left">Id</th>
<td>&dtml-id;</td>
</tr>
<tr>
<th valign="top" align="left">Title</th>
<td><input type="text" name="title" value="&dtml-title;"></td>
</tr>
<tr>
<th valign="top" align="left">REQUEST variables</th>
<td><textarea name="request_vars:lines" rows="5" cols="30"><dtml-in
request_vars>&dtml-sequence-item;
</dtml-in></textarea></td>
</tr>
<tr>
<th valign="top" align="left">Threshold entries</th>
<td><input type="text" name="threshold"
value="&dtml-threshold;"></td>
</tr>
<tr>
<th valign="top" align="left">Cleanup interval (seconds)</th>
<td><input type="text" name="cleanup_interval"
value="&dtml-cleanup_interval;"></td>
</tr>
</table>
</dtml-with>
<input type="submit" name="submit" value="Save">
</form>
</body>
</html>
<html><head><title>AcceleratedHTTPCacheManager statistics</title></head>
<body bgcolor="#ffffff">
<dtml-var manage_tabs>
<h2>Statistics</h2>
<table>
<tr>
<td><dtml-var expr="sort_link('Path', 'path')"></td>
<td><dtml-var expr="sort_link('Anonymous hits', 'anon')"></td>
<td><dtml-var expr="sort_link('Authenticated hits', 'auth')"></td>
</tr>
<dtml-in getCacheReport mapping>
<tr>
<td><a href="&dtml-path;/ZCacheable_manage">&dtml-path;</a></td>
<td>&dtml-anon;</td>
<td>&dtml-auth;</td>
</tr>
<dtml-else>
<tr><td colspan="3"><i>Nothing is in the cache.</i></td></tr>
</dtml-in>
</table>
<p><i>Notes</i></p>
<ul>
<li>Cache manager hits generally correspond to HTTP accelerator misses.</li>
<li>A hit is counted in the "authenticated hits" column even if
headers are only set for anonymous requests.</li>
</ul>
</body>
</html>
<html><head><title>RAMCacheManager statistics</title></head>
<body bgcolor="#ffffff">
<dtml-var manage_tabs>
<h2>Statistics</h2>
<table>
<tr>
<td><dtml-var expr="sort_link('Path', 'path')"></td>
<td><dtml-var expr="sort_link('Hits', 'hits')"></td>
<td><dtml-var expr="sort_link('Recent hits', 'counter')"></td>
<td><dtml-var expr="sort_link('Misses', 'misses')"></td>
<td><dtml-var expr="sort_link('Memory', 'size')"></td>
<td><dtml-var expr="sort_link('Views', 'views')"></td>
<td><dtml-var expr="sort_link('Entries', 'entries')"></td>
</tr>
<dtml-in getCacheReport mapping>
<tr>
<td><a href="&dtml-path;/ZCacheable_manage">&dtml-path;</a></td>
<td>&dtml-hits;</td>
<td>&dtml-counter;</td>
<td>&dtml-misses;</td>
<td>&dtml-size;</td>
<td><dtml-var expr="_.string.join(views, ', ')" html_quote></td>
<td>&dtml-entries;</td>
</tr>
<dtml-else>
<tr><td colspan="7"><i>Nothing is in the cache.</i></td></tr>
</dtml-in>
</table>
<ul>
<li>Memory usage is approximate. It is based on the pickled value of the
cached data.</li>
<li>The cache is cleaned up by removing the least frequently accessed
entries since the last cleanup operation. The determination is made using
the <i>Recent hits</i> counter.</li>
</ul>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment