Package cherrypy :: Package lib :: Module caching
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.lib.caching

  1  import datetime 
  2  import threading 
  3  import time 
  4   
  5  import cherrypy 
  6  from cherrypy.lib import cptools, http 
  7   
  8   
9 -class MemoryCache:
10
11 - def __init__(self):
12 self.clear() 13 t = threading.Thread(target=self.expire_cache, name='expire_cache') 14 self.expiration_thread = t 15 t.setDaemon(True) 16 t.start()
17
18 - def clear(self):
19 """Reset the cache to its initial, empty state.""" 20 self.cache = {} 21 self.expirations = {} 22 self.tot_puts = 0 23 self.tot_gets = 0 24 self.tot_hist = 0 25 self.tot_expires = 0 26 self.tot_non_modified = 0 27 self.cursize = 0
28
29 - def _key(self):
30 request = cherrypy.request 31 return request.config.get("tools.caching.key", cherrypy.url(qs=request.query_string))
32 key = property(_key) 33
34 - def expire_cache(self):
35 # expire_cache runs in a separate thread which the servers are 36 # not aware of. It's possible that "time" will be set to None 37 # arbitrarily, so we check "while time" to avoid exceptions. 38 # See tickets #99 and #180 for more information. 39 while time: 40 now = time.time() 41 for expiration_time, objects in self.expirations.items(): 42 if expiration_time <= now: 43 for obj_size, obj_key in objects: 44 try: 45 del self.cache[obj_key] 46 self.tot_expires += 1 47 self.cursize -= obj_size 48 except KeyError: 49 # the key may have been deleted elsewhere 50 pass 51 del self.expirations[expiration_time] 52 time.sleep(0.1)
53
54 - def get(self):
55 """Return the object if in the cache, else None.""" 56 self.tot_gets += 1 57 cache_item = self.cache.get(self.key, None) 58 if cache_item: 59 self.tot_hist += 1 60 return cache_item 61 else: 62 return None
63
64 - def put(self, obj):
65 conf = cherrypy.request.config.get 66 67 if len(self.cache) < conf("tools.caching.maxobjects", 1000): 68 # Size check no longer includes header length 69 obj_size = len(obj[2]) 70 maxobj_size = conf("tools.caching.maxobj_size", 100000) 71 72 total_size = self.cursize + obj_size 73 maxsize = conf("tools.caching.maxsize", 10000000) 74 75 # checks if there's space for the object 76 if (obj_size < maxobj_size and total_size < maxsize): 77 # add to the expirations list and cache 78 expiration_time = cherrypy.response.time + conf("tools.caching.delay", 600) 79 obj_key = self.key 80 bucket = self.expirations.setdefault(expiration_time, []) 81 bucket.append((obj_size, obj_key)) 82 self.cache[obj_key] = obj 83 self.tot_puts += 1 84 self.cursize = total_size
85
86 - def delete(self):
87 self.cache.pop(self.key)
88 89
90 -def get(invalid_methods=("POST", "PUT", "DELETE"), cache_class=MemoryCache, **kwargs):
91 """Try to obtain cached output. If fresh enough, raise HTTPError(304). 92 93 If POST, PUT, or DELETE: 94 * invalidates (deletes) any cached response for this resource 95 * sets request.cached = False 96 * sets request.cacheable = False 97 98 else if a cached copy exists: 99 * sets request.cached = True 100 * sets request.cacheable = False 101 * sets response.headers to the cached values 102 * checks the cached Last-Modified response header against the 103 current If-(Un)Modified-Since request headers; raises 304 104 if necessary. 105 * sets response.status and response.body to the cached values 106 * returns True 107 108 otherwise: 109 * sets request.cached = False 110 * sets request.cacheable = True 111 * returns False 112 """ 113 if not hasattr(cherrypy, "_cache"): 114 cherrypy._cache = cache_class() 115 116 request = cherrypy.request 117 118 # POST, PUT, DELETE should invalidate (delete) the cached copy. 119 # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. 120 if request.method in invalid_methods: 121 cherrypy._cache.delete() 122 request.cached = False 123 request.cacheable = False 124 return False 125 126 cache_data = cherrypy._cache.get() 127 request.cached = c = bool(cache_data) 128 request.cacheable = not c 129 if c: 130 response = cherrypy.response 131 s, response.headers, b, create_time = cache_data 132 133 # Add the required Age header 134 response.headers["Age"] = str(int(response.time - create_time)) 135 136 try: 137 # Note that validate_since depends on a Last-Modified header; 138 # this was put into the cached copy, and should have been 139 # resurrected just above (response.headers = cache_data[1]). 140 cptools.validate_since() 141 except cherrypy.HTTPError, x: 142 if x.status == 304: 143 cherrypy._cache.tot_non_modified += 1 144 raise 145 146 # serve it & get out from the request 147 response.status = s 148 response.body = b 149 return c
150 151
152 -def tee_output():
153 response = cherrypy.response 154 output = [] 155 def tee(body): 156 """Tee response.body into a list.""" 157 for chunk in body: 158 output.append(chunk) 159 yield chunk 160 # Might as well do this here; why cache if the body isn't consumed? 161 if response.headers.get('Pragma', None) != 'no-cache': 162 # save the cache data 163 body = ''.join([chunk for chunk in output]) 164 cherrypy._cache.put((response.status, response.headers or {}, 165 body, response.time))
166 response.body = tee(response.body) 167 168
169 -def expires(secs=0, force=False):
170 """Tool for influencing cache mechanisms using the 'Expires' header. 171 172 'secs' must be either an int or a datetime.timedelta, and indicates the 173 number of seconds between response.time and when the response should 174 expire. The 'Expires' header will be set to (response.time + secs). 175 176 If 'secs' is zero, the following "cache prevention" headers are also set: 177 'Pragma': 'no-cache' 178 'Cache-Control': 'no-cache, must-revalidate' 179 180 If 'force' is False (the default), the following headers are checked: 181 'Etag', 'Last-Modified', 'Age', 'Expires'. If any are already present, 182 none of the above response headers are set. 183 """ 184 185 response = cherrypy.response 186 headers = response.headers 187 188 cacheable = False 189 if not force: 190 # some header names that indicate that the response can be cached 191 for indicator in ('Etag', 'Last-Modified', 'Age', 'Expires'): 192 if indicator in headers: 193 cacheable = True 194 break 195 196 if not cacheable: 197 if isinstance(secs, datetime.timedelta): 198 secs = (86400 * secs.days) + secs.seconds 199 200 if secs == 0: 201 if force or "Pragma" not in headers: 202 headers["Pragma"] = "no-cache" 203 if cherrypy.request.protocol >= (1, 1): 204 if force or "Cache-Control" not in headers: 205 headers["Cache-Control"] = "no-cache, must-revalidate" 206 207 expiry = http.HTTPDate(response.time + secs) 208 if force or "Expires" not in headers: 209 headers["Expires"] = expiry
210