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

root/branches/cp3-wsgi-remix/lib/caching.py

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

Lots of mixedCase to lower_with_underscores.

  • 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         return cherrypy.request.config.get("tools.caching.key", cherrypy.request.browser_url)
31     key = property(_key)
32    
33     def expire_cache(self):
34         # expire_cache runs in a separate thread which the servers are
35         # not aware of. It's possible that "time" will be set to None
36         # arbitrarily, so we check "while time" to avoid exceptions.
37         # See tickets #99 and #180 for more information.
38         while time:
39             now = time.time()
40             for expiration_time, objects in self.expirations.items():
41                 if expiration_time <= now:
42                     for obj_size, obj_key in objects:
43                         try:
44                             del self.cache[obj_key]
45                             self.tot_expires += 1
46                             self.cursize -= obj_size
47                         except KeyError:
48                             # the key may have been deleted elsewhere
49                             pass
50                     del self.expirations[expiration_time]
51             time.sleep(0.1)
52    
53     def get(self):
54         """Return the object if in the cache, else None."""
55         self.tot_gets += 1
56         cache_item = self.cache.get(self.key, None)
57         if cache_item:
58             self.tot_hist += 1
59             return cache_item
60         else:
61             return None
62    
63     def put(self, obj):
64         conf = cherrypy.request.config.get
65        
66         if len(self.cache) < conf("tools.caching.maxobjects", 1000):
67             # Size check no longer includes header length
68             obj_size = len(obj[2])
69             maxobj_size = conf("tools.caching.maxobj_size", 100000)
70            
71             total_size = self.cursize + obj_size
72             maxsize = conf("tools.caching.maxsize", 10000000)
73            
74             # checks if there's space for the object
75             if (obj_size < maxobj_size and total_size < maxsize):
76                 # add to the expirations list and cache
77                 expiration_time = time.time() + conf("tools.caching.delay", 600)
78                 obj_key = self.key
79                 bucket = self.expirations.setdefault(expiration_time, [])
80                 bucket.append((obj_size, obj_key))
81                 self.cache[obj_key] = obj
82                 self.tot_puts += 1
83                 self.cursize = total_size
84
85
86 def init(cache_class=None):
87     if cache_class is None:
88         cache_class = MemoryCache
89     cherrypy._cache = cache_class()
90
91 def get():
92     # Ignore POST, PUT, DELETE.
93     # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10.
94     invalid = cherrypy.config.get("tools.caching.invalid_methods",
95                                   ("POST", "PUT", "DELETE"))
96     if cherrypy.request.method in invalid:
97         cherrypy.request.cached = c = False
98     else:
99         cache_data = cherrypy._cache.get()
100         cherrypy.request.cached = c = bool(cache_data)
101    
102     if c:
103         response = cherrypy.response
104         s, response.headers, b, create_time = cache_data
105        
106         # Add the required Age header
107         response.headers["Age"] = str(int(time.time() - create_time))
108        
109         try:
110             cptools.validate_since()
111         except cherrypy.HTTPError, x:
112             if x.status == 304:
113                 cherrypy._cache.tot_non_modified += 1
114             raise
115        
116         # serve it & get out from the request
117         response.status = s
118         response.body = b
119     return c
120
121 def tee_output():
122     if cherrypy.request.cached:
123         return
124    
125     response = cherrypy.response
126     output = []
127     def tee(body):
128         """Tee response.body into a list."""
129         for chunk in body:
130             output.append(chunk)
131             yield chunk
132         # Might as well do this here; why cache if the body isn't consumed?
133         if response.headers.get('Pragma', None) != 'no-cache':
134             # save the cache data
135             body = ''.join([chunk for chunk in output])
136             create_time = time.time()
137             cherrypy._cache.put((response.status, response.headers or {},
138                                  body, create_time))
139     response.body = tee(response.body)
140
141
142 # CherryPy interfaces. Pick one.
143
144 def enable(**kwargs):
145     """Compile-time decorator (turn on the tool in config)."""
146     def wrapper(f):
147         if not hasattr(f, "_cp_config"):
148             f._cp_config = {}
149         f._cp_config["tools.caching.on"] = True
150         for k, v in kwargs.iteritems():
151             f._cp_config["tools.caching." + k] = v
152         return f
153     return wrapper
154
155 def _wrapper():
156     if get():
157         cherrypy.request.handler = None
158     else:
159         # Note the devious technique here of adding hooks on the fly
160         cherrypy.request.hooks.attach('before_finalize', tee_output)
161
162 def _setup():
163     """Hook caching into cherrypy.request using the given conf."""
164     conf = cherrypy.request.toolmap.get("caching", {})
165     if not getattr(cherrypy, "_cache", None):
166         init(conf.get("class", None))
167     cherrypy.request.hooks.attach('before_main', _wrapper)
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     cacheable = False
186     if not force:
187         # some header names that indicate that the response can be cached
188         for indicator in ('Etag', 'Last-Modified', 'Age', 'Expires'):
189             if indicator in cherrypy.response.headers:
190                 cacheable = True
191                 break
192    
193     if not cacheable:
194         if isinstance(secs, datetime.timedelta):
195             secs = (86400 * secs.days) + secs.seconds
196        
197         if secs == 0:
198             if force or "Pragma" not in cherrypy.response.headers:
199                 cherrypy.response.headers["Pragma"] = "no-cache"
200             if cherrypy.request.protocol >= (1, 1):
201                 if force or "Cache-Control" not in cherrypy.response.headers:
202                     cherrypy.response.headers["Cache-Control"] = "no-cache"
203        
204         expiry = http.HTTPDate(cherrypy.response.time + secs)
205         if force or "Expires" not in cherrypy.response.headers:
206             cherrypy.response.headers["Expires"] = expiry
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets