caching.py (9335B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\lib\caching.py 2 import datetime 3 import sys 4 import threading 5 import time 6 import cherrypy 7 from cherrypy.lib import cptools, httputil 8 from cherrypy._cpcompat import copyitems, ntob, set_daemon, sorted 9 10 class Cache(object): 11 12 def get(self): 13 raise NotImplemented 14 15 def put(self, obj, size): 16 raise NotImplemented 17 18 def delete(self): 19 raise NotImplemented 20 21 def clear(self): 22 raise NotImplemented 23 24 25 class AntiStampedeCache(dict): 26 27 def wait(self, key, timeout = 5, debug = False): 28 value = self.get(key) 29 if isinstance(value, threading._Event): 30 if timeout is None: 31 if debug: 32 cherrypy.log('No timeout', 'TOOLS.CACHING') 33 return 34 if debug: 35 cherrypy.log('Waiting up to %s seconds' % timeout, 'TOOLS.CACHING') 36 value.wait(timeout) 37 if value.result is not None: 38 if debug: 39 cherrypy.log('Result!', 'TOOLS.CACHING') 40 return value.result 41 if debug: 42 cherrypy.log('Timed out', 'TOOLS.CACHING') 43 e = threading.Event() 44 e.result = None 45 dict.__setitem__(self, key, e) 46 return 47 if value is None: 48 if debug: 49 cherrypy.log('Timed out', 'TOOLS.CACHING') 50 e = threading.Event() 51 e.result = None 52 dict.__setitem__(self, key, e) 53 return value 54 55 def __setitem__(self, key, value): 56 existing = self.get(key) 57 dict.__setitem__(self, key, value) 58 if isinstance(existing, threading._Event): 59 existing.result = value 60 existing.set() 61 62 63 class MemoryCache(Cache): 64 maxobjects = 1000 65 maxobj_size = 100000 66 maxsize = 10000000 67 delay = 600 68 antistampede_timeout = 5 69 expire_freq = 0.1 70 debug = False 71 72 def __init__(self): 73 self.clear() 74 t = threading.Thread(target=self.expire_cache, name='expire_cache') 75 self.expiration_thread = t 76 set_daemon(t, True) 77 t.start() 78 79 def clear(self): 80 self.store = {} 81 self.expirations = {} 82 self.tot_puts = 0 83 self.tot_gets = 0 84 self.tot_hist = 0 85 self.tot_expires = 0 86 self.tot_non_modified = 0 87 self.cursize = 0 88 89 def expire_cache(self): 90 while time: 91 now = time.time() 92 for expiration_time, objects in copyitems(self.expirations): 93 if expiration_time <= now: 94 for obj_size, uri, sel_header_values in objects: 95 try: 96 del self.store[uri][tuple(sel_header_values)] 97 self.tot_expires += 1 98 self.cursize -= obj_size 99 except KeyError: 100 pass 101 102 del self.expirations[expiration_time] 103 104 time.sleep(self.expire_freq) 105 106 def get(self): 107 request = cherrypy.serving.request 108 self.tot_gets += 1 109 uri = cherrypy.url(qs=request.query_string) 110 uricache = self.store.get(uri) 111 if uricache is None: 112 return 113 header_values = [ request.headers.get(h, '') for h in uricache.selecting_headers ] 114 variant = uricache.wait(key=tuple(sorted(header_values)), timeout=self.antistampede_timeout, debug=self.debug) 115 if variant is not None: 116 self.tot_hist += 1 117 return variant 118 119 def put(self, variant, size): 120 request = cherrypy.serving.request 121 response = cherrypy.serving.response 122 uri = cherrypy.url(qs=request.query_string) 123 uricache = self.store.get(uri) 124 if uricache is None: 125 uricache = AntiStampedeCache() 126 uricache.selecting_headers = [ e.value for e in response.headers.elements('Vary') ] 127 self.store[uri] = uricache 128 if len(self.store) < self.maxobjects: 129 total_size = self.cursize + size 130 if size < self.maxobj_size and total_size < self.maxsize: 131 expiration_time = response.time + self.delay 132 bucket = self.expirations.setdefault(expiration_time, []) 133 bucket.append((size, uri, uricache.selecting_headers)) 134 header_values = [ request.headers.get(h, '') for h in uricache.selecting_headers ] 135 uricache[tuple(sorted(header_values))] = variant 136 self.tot_puts += 1 137 self.cursize = total_size 138 139 def delete(self): 140 uri = cherrypy.url(qs=cherrypy.serving.request.query_string) 141 self.store.pop(uri, None) 142 143 144 def get(invalid_methods = ('POST', 'PUT', 'DELETE'), debug = False, **kwargs): 145 request = cherrypy.serving.request 146 response = cherrypy.serving.response 147 if not hasattr(cherrypy, '_cache'): 148 cherrypy._cache = kwargs.pop('cache_class', MemoryCache)() 149 for k, v in kwargs.items(): 150 setattr(cherrypy._cache, k, v) 151 152 cherrypy._cache.debug = debug 153 if request.method in invalid_methods: 154 if debug: 155 cherrypy.log('request.method %r in invalid_methods %r' % (request.method, invalid_methods), 'TOOLS.CACHING') 156 cherrypy._cache.delete() 157 request.cached = False 158 request.cacheable = False 159 return False 160 if 'no-cache' in [ e.value for e in request.headers.elements('Pragma') ]: 161 request.cached = False 162 request.cacheable = True 163 return False 164 cache_data = cherrypy._cache.get() 165 request.cached = bool(cache_data) 166 request.cacheable = not request.cached 167 if request.cached: 168 max_age = cherrypy._cache.delay 169 for v in [ e.value for e in request.headers.elements('Cache-Control') ]: 170 atoms = v.split('=', 1) 171 directive = atoms.pop(0) 172 if directive == 'max-age': 173 if len(atoms) != 1 or not atoms[0].isdigit(): 174 raise cherrypy.HTTPError(400, 'Invalid Cache-Control header') 175 max_age = int(atoms[0]) 176 break 177 elif directive == 'no-cache': 178 if debug: 179 cherrypy.log('Ignoring cache due to Cache-Control: no-cache', 'TOOLS.CACHING') 180 request.cached = False 181 request.cacheable = True 182 return False 183 184 if debug: 185 cherrypy.log('Reading response from cache', 'TOOLS.CACHING') 186 s, h, b, create_time = cache_data 187 age = int(response.time - create_time) 188 if age > max_age: 189 if debug: 190 cherrypy.log('Ignoring cache due to age > %d' % max_age, 'TOOLS.CACHING') 191 request.cached = False 192 request.cacheable = True 193 return False 194 response.headers = rh = httputil.HeaderMap() 195 for k in h: 196 dict.__setitem__(rh, k, dict.__getitem__(h, k)) 197 198 response.headers['Age'] = str(age) 199 try: 200 cptools.validate_since() 201 except cherrypy.HTTPRedirect: 202 x = sys.exc_info()[1] 203 if x.status == 304: 204 cherrypy._cache.tot_non_modified += 1 205 raise 206 207 response.status = s 208 response.body = b 209 elif debug: 210 cherrypy.log('request is not cached', 'TOOLS.CACHING') 211 return request.cached 212 213 214 def tee_output(): 215 request = cherrypy.serving.request 216 if 'no-store' in request.headers.values('Cache-Control'): 217 return 218 219 def tee(body): 220 if 'no-cache' in response.headers.values('Pragma') or 'no-store' in response.headers.values('Cache-Control'): 221 for chunk in body: 222 yield chunk 223 224 return 225 output = [] 226 for chunk in body: 227 output.append(chunk) 228 yield chunk 229 230 body = ntob('').join(output) 231 cherrypy._cache.put((response.status, 232 response.headers or {}, 233 body, 234 response.time), len(body)) 235 236 response = cherrypy.serving.response 237 response.body = tee(response.body) 238 239 240 def expires(secs = 0, force = False, debug = False): 241 response = cherrypy.serving.response 242 headers = response.headers 243 cacheable = False 244 if not force: 245 for indicator in ('Etag', 'Last-Modified', 'Age', 'Expires'): 246 if indicator in headers: 247 cacheable = True 248 break 249 250 if not cacheable and not force: 251 if debug: 252 cherrypy.log('request is not cacheable', 'TOOLS.EXPIRES') 253 else: 254 if debug: 255 cherrypy.log('request is cacheable', 'TOOLS.EXPIRES') 256 if isinstance(secs, datetime.timedelta): 257 secs = 86400 * secs.days + secs.seconds 258 if secs == 0: 259 if force or 'Pragma' not in headers: 260 headers['Pragma'] = 'no-cache' 261 if cherrypy.serving.request.protocol >= (1, 1): 262 if force or 'Cache-Control' not in headers: 263 headers['Cache-Control'] = 'no-cache, must-revalidate' 264 expiry = httputil.HTTPDate(1169942400.0) 265 else: 266 expiry = httputil.HTTPDate(response.time + secs) 267 if force or 'Expires' not in headers: 268 headers['Expires'] = expiry