Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/tags/cherrypy-3.0.0/cherrypy/lib/caching.py

Revision 1495 (checked in by fumanchu, 2 years ago)

Fixed bug in caching; invalid methods did not delete cached resource. See #509.

  • Property svn:eol-style set to native
Line 
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'
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    
187     cacheable = False
188     if not force:
189         # some header names that indicate that the response can be cached
190         for indicator in ('Etag', 'Last-Modified', 'Age', 'Expires'):
191             if indicator in response.headers:
192                 cacheable = True
193                 break
194    
195     if not cacheable:
196         if isinstance(secs, datetime.timedelta):
197             secs = (86400 * secs.days) + secs.seconds
198        
199         if secs == 0:
200             if force or "Pragma" not in response.headers:
201                 response.headers["Pragma"] = "no-cache"
202             if cherrypy.request.protocol >= (1, 1):
203                 if force or "Cache-Control" not in response.headers:
204                     response.headers["Cache-Control"] = "no-cache"
205        
206         expiry = http.HTTPDate(response.time + secs)
207         if force or "Expires" not in response.headers:
208             response.headers["Expires"] = expiry
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets