Changeset 1511
- Timestamp:
- 28.03.2008 08:51:04 (2 months ago)
- Files:
-
- trunk/pylucid/PyLucid/middlewares/cache.py (modified) (8 diffs)
- trunk/pylucid/PyLucid/middlewares/pagestats.py (added)
- trunk/pylucid/PyLucid/settings_example.py (modified) (2 diffs)
- trunk/pylucid/PyLucid/tools/shortcuts.py (modified) (1 diff)
- trunk/pylucid/tests/cache.py (modified) (3 diffs)
- trunk/pylucid/tests/security.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/pylucid/PyLucid/middlewares/cache.py
r1510 r1511 12 12 - Build the cache key based on the page shortcuts 13 13 14 If request.debug == True: We added set response["from_cache"] = "yes" if 15 the response comes from the cache. 14 If request.debug == True: We added added information about the cache status 15 in _insert_cache_info(): 16 - set response["from_cache"] to "yes" / "no" (Not True/False!) 17 - Replace the pagestats TAG with cache debug information. 16 18 17 19 The page statistics part bases on: … … 35 37 from django.core.cache import cache 36 38 39 from PyLucid.models import Page 40 from PyLucid.tools.shortcuts import verify_shortcut 41 from PyLucid.middlewares.pagestats import TAG 37 42 from PyLucid.template_addons.filters import human_duration 38 from PyLucid.models import Page39 43 40 44 CACHE_TIMEOUT = settings.CACHE_MIDDLEWARE_SECONDS 41 45 42 # Save the start time of the current running pyhon instance 43 start_overall = time.time() 44 45 TAG = u"<!-- script_duration -->" 46 47 FMT = ( 48 u'render time: %(total_time)s -' 49 ' overall: %(overall_time)s -' 50 ' queries: %(queries)d' 51 ) 46 CACHED_INFO_YES = u"from cache" 47 CACHED_INFO_NO = u"not cached" 52 48 53 49 # RFC1123 date format as specified by HTTP RFC2616 section 3.3.1: … … 70 66 shortcut = url.rsplit("/", 1)[-1] 71 67 68 # Check a shortcut. A AssertionError whould be raised if something seems to 69 # be wrong. 70 # Normaly the url-re of the index view filters bad things out. But 71 # process_request in the middleware whould becalled before this done. 72 verify_shortcut(shortcut) 73 72 74 cache_key = settings.PAGE_CACHE_PREFIX + shortcut 73 75 return shortcut, cache_key … … 75 77 76 78 class CacheMiddleware(object): 79 def __init__(self): 80 self.cache_key = None 81 77 82 def process_request(self, request): 78 83 """ … … 81 86 user makes a GET or HEAD requests. 82 87 """ 83 # save start time and the number of db queries before we do anything 84 self.start_time = time.time() 85 self.old_queries = len(connection.queries) 88 request._from_cache = False 86 89 87 90 # cache only GET or HEAD requests … … 99 102 # Build the cache key based on the page shortcuts 100 103 url = request.path 101 shortcut, self.cache_key = build_cache_key(url) 104 try: 105 shortcut, self.cache_key = build_cache_key(url) 106 except AssertionError, e: 107 # Something is wrong with the given url 108 request._use_cache = False 109 return 102 110 103 111 # Get the page data from the cache. If not exist response is None. 104 112 response = cache.get(self.cache_key) 105 113 106 if response: 107 # The page data exist in the cache 108 assert isinstance(response, HttpResponse) 109 if request.debug: 110 #print "Use cached page version. (key: '%s')" % self.cache_key 111 response["from_cache"] = "yes" 112 self.insert_page_stats(request, response) 113 #elif settings.DEBUG: 114 #print "Page not in cache found. (key: '%s')" % self.cache_key 114 if response == None: 115 # The page data doesn't exist in the cache 116 return 117 118 # The page data exist in the cache 119 assert isinstance(response, HttpResponse) 120 request._from_cache = True 121 122 if request.debug: 123 # Add the cache debug information. 124 response = self._insert_cache_info(request, response, True) 115 125 116 126 return response … … 121 131 Cache the response and insert the page statistics. 122 132 """ 133 if request._from_cache == True: 134 # The content comes from the cache 135 return response 136 123 137 if getattr(request, "_use_cache", False) != True: 124 138 # Don't cache 125 self.insert_page_stats(request, response)139 response = self._insert_cache_info(request, response, False) 126 140 return response 127 141 128 142 # cache the response 129 self. cache_response(request, response)130 131 self.insert_page_stats(request, response)143 self._cache_response(request, response) 144 145 response = self._insert_cache_info(request, response, False) 132 146 133 147 return response 134 148 135 149 136 def cache_response(self, request, response): 137 """ 138 Cache the given response. 139 """ 140 # Add cache info headers to the response object 141 self.patch_response_headers(request, response) 142 143 # Save the page into the cache 144 cache.set(self.cache_key, response, CACHE_TIMEOUT) 145 146 147 def patch_response_headers(self, request, response): 148 """ 149 Adds some useful response headers for the browser cache to the given 150 HttpResponse object. 151 Based on django.utils.cache.patch_response_headers() but here we use 152 the original page last update time. 153 """ 154 # The the original page last update time 155 context = request.CONTEXT 156 current_page_obj = context["PAGE"] 157 lastupdatetime = current_page_obj.lastupdatetime 158 159 response['ETag'] = md5.new(self.cache_key).hexdigest() 160 response['Last-Modified'] = lastupdatetime.strftime(DATE_FORMAT) 161 now = datetime.datetime.utcnow() 162 expires = now + datetime.timedelta(0, CACHE_TIMEOUT) 163 response['Expires'] = expires.strftime(DATE_FORMAT) 164 165 166 def insert_page_stats(self, request, response): 167 """ 168 calculate the statistic and replace it into the html page. 169 """ 170 # Put only the statistic into HTML pages 171 if not "html" in response._headers["content-type"][1]: 172 # No HTML Page -> do nothing 173 return response 174 175 # compute the db time for the queries just run 176 # FIXME: In my shared webhosting environment the queries is always = 0 177 queries = len(connection.queries) - self.old_queries 178 179 total_time = human_duration(time.time() - self.start_time) 180 overall_time = human_duration(time.time() - start_overall) 181 182 # replace the comment if found 183 stat_info = FMT % { 184 'total_time' : total_time, 185 'overall_time' : overall_time, 186 'queries' : queries, 187 } 150 def _insert_cache_info(self, request, response, is_from_cache): 151 """ 152 Add the cache debug information. 153 154 if request.debug == True, we added the information if the response 155 was from the cache or not. We "added" the information after the 156 pagestats >TAG< ;) 157 """ 158 if request._from_cache == True: 159 response_msg = CACHED_INFO_YES 160 response["from_cache"] = "yes" 161 elif request._from_cache == False: 162 response_msg = CACHED_INFO_NO 163 response["from_cache"] = "no" 164 else: 165 raise AssertionError("wrong request._from_cache info.") 188 166 189 167 content = response.content … … 197 175 return response 198 176 199 # insert the page statistic 200 new_content = content.replace(TAG, stat_info) 177 # Replace the pagestats TAG with the cache debug information. 178 message = u"%s - %s - cache key: %s" % ( 179 TAG, response_msg, self.cache_key 180 ) 181 new_content = content.replace(TAG, message) 201 182 response.content = new_content 202 183 203 184 return response 185 186 187 def _cache_response(self, request, response): 188 """ 189 Cache the given response. 190 """ 191 # Add cache info headers to the response object 192 self._patch_response_headers(request, response) 193 194 # Save the page into the cache 195 cache.set(self.cache_key, response, CACHE_TIMEOUT) 196 197 198 def _patch_response_headers(self, request, response): 199 """ 200 Adds some useful response headers for the browser cache to the given 201 HttpResponse object. 202 Based on django.utils.cache.patch_response_headers() but here we use 203 the original page last update time. 204 """ 205 # The the original page last update time 206 context = request.CONTEXT 207 current_page_obj = context["PAGE"] 208 lastupdatetime = current_page_obj.lastupdatetime 209 210 response['ETag'] = md5.new(self.cache_key).hexdigest() 211 response['Last-Modified'] = lastupdatetime.strftime(DATE_FORMAT) 212 now = datetime.datetime.utcnow() 213 expires = now + datetime.timedelta(0, CACHE_TIMEOUT) 214 response['Expires'] = expires.strftime(DATE_FORMAT) trunk/pylucid/PyLucid/settings_example.py
r1510 r1511 84 84 # 85 85 MIDDLEWARE_CLASSES = ( 86 # Insert a statistic line into the generated page: 87 'PyLucid.middlewares.pagestats.PageStatsMiddleware', 88 86 89 # PyLucidCommonMiddleware loads the django middlewares: 87 90 # - 'django.contrib.sessions.middleware.SessionMiddleware' … … 90 93 'PyLucid.middlewares.common.PyLucidCommonMiddleware', 91 94 95 # Cache all anonymous cms page request, if CACHE_BACKEND worked. 92 96 'PyLucid.middlewares.cache.CacheMiddleware', 93 97 trunk/pylucid/PyLucid/tools/shortcuts.py
r1503 r1511 19 19 ALLOW_CHARS = string.ascii_letters + string.digits + "_" 20 20 21 def verify_shortcut(shortcut): 22 """ 23 Check a shortcut. Raise AssertionError if something seems to be wrong. 24 But normaly the urls-re should only filter the bad thing from urls ;) 25 """ 26 if shortcut=="": 27 raise AssertionError("Shortcut is empty!") 28 29 for char in shortcut: 30 if not char in ALLOW_CHARS: 31 raise AssertionError( 32 "Not allowed character in shortcut: '%r'" % char 33 ) 21 34 22 35 def makeUnique(item_name, name_list): trunk/pylucid/tests/cache.py
r1510 r1511 135 135 must_not_contain=("Traceback",) 136 136 ) 137 138 from_cache = response.get("from_cache", None) 139 self.failUnlessEqual(from_cache, None, msg) 137 self.failUnlessEqual(response["from_cache"], "no", msg) 140 138 141 139 def failIfNotFromCache(self, response, msg=None): … … 157 155 raise AssertionError("Wrong url?") 158 156 159 from_cache = response["from_cache"] 160 self.failUnlessEqual(from_cache, "yes", msg) 157 self.failUnlessEqual(response["from_cache"], "yes", msg) 161 158 162 159 #-------------------------------------------------------------------------- … … 314 311 self._check_cache_after(test_func) 315 312 313 #-------------------------------------------------------------------------- 314 315 def test_wrong_url(self): 316 """ 317 Test if the cache middleware checks bad characters in the url. 318 Normaly the urls-re doesn't match on bad urls. But the process_request 319 method of a middleware called before this. 320 """ 321 content = "Should never comes from the cache!" 322 shortcut = "bad$char&here" 323 url = "/%s/" % shortcut 324 cache_key = settings.PAGE_CACHE_PREFIX + shortcut 325 326 # Insert a cache entry, so the cache middleware can found the cached 327 # page and will be response the cached content, if the shortcut will 328 # not be veryfied. 329 response = HttpResponse(content) 330 cache.set(cache_key, response, 9999) 331 332 # Request the cached page and check the response. It should not resonse 333 # the cached content. It should be apperar a 404 page not found. 334 response = self.client.get(url) 335 #debug_response(response) 336 self.failUnlessEqual(response.status_code, 404) 337 self.assertResponse(response, must_not_contain=(content,)) 338 316 339 317 340 if __name__ == "__main__": trunk/pylucid/tests/security.py
r1509 r1511 37 37 38 38 CHARACTER_VARIANTS = ( 39 "<", "\x3c", "\x3C", u"\u003c", u"\u003C",39 "<", r"\x3c", r"\x3C", r"\u003c", u"\u003c", u"\u003C", 40 40 # "<", "<", "<", "<", "<", "<", "<", 41 41 # "<", "<", "<", "<", "<", … … 143 143 for char_variant in CHARACTER_VARIANTS: 144 144 test_string = char_variant + VARIANTS_STRING 145 response = self.client.get("/" + test_string) 145 url = "/" + test_string 146 response = self.client.get(url) 146 147 self.assertResponse( 147 148 response, must_not_contain=(test_string, "<" + VARIANTS_STRING,) … … 167 168 self.assertResponse( 168 169 response, 169 must_not_contain=( test_string,"<" + VARIANTS_STRING,)170 must_not_contain=("<" + VARIANTS_STRING,) 170 171 ) 171 172 … … 184 185 self.assertResponse( 185 186 response, 186 must_not_contain=( test_string,"<" + VARIANTS_STRING,)187 must_not_contain=("<" + VARIANTS_STRING,) 187 188 ) 188 189
