source file: /Library/Python/2.3/site-packages/CherryPy-3.0.1-py2.3.egg/cherrypy/lib/caching.py
file stats: 120 lines, 17 executed: 14.2% covered
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): 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