Changeset 1511

Show
Ignore:
Timestamp:
03/28/08 08:51:04 (2 years ago)
Author:
JensDiemer
Message:

Updating the cache middleware:

  • Splitting pagestats from cache is a good idea, after all ;)
  • add a debug information in the response
  • verify the shortcut from the url
  • update unittests
Location:
trunk/pylucid
Files:
1 added
5 modified

Legend:

Unmodified
Added
Removed
  • trunk/pylucid/PyLucid/middlewares/cache.py

    r1510 r1511  
    1212        - Build the cache key based on the page shortcuts 
    1313 
    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. 
    1618 
    1719    The page statistics part bases on: 
     
    3537from django.core.cache import cache 
    3638 
     39from PyLucid.models import Page 
     40from PyLucid.tools.shortcuts import verify_shortcut 
     41from PyLucid.middlewares.pagestats import TAG 
    3742from PyLucid.template_addons.filters import human_duration 
    38 from PyLucid.models import Page 
    3943 
    4044CACHE_TIMEOUT = settings.CACHE_MIDDLEWARE_SECONDS 
    4145 
    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 ) 
     46CACHED_INFO_YES = u"from cache" 
     47CACHED_INFO_NO = u"not cached" 
    5248 
    5349# RFC1123 date format as specified by HTTP RFC2616 section 3.3.1: 
     
    7066        shortcut = url.rsplit("/", 1)[-1] 
    7167 
     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 
    7274    cache_key = settings.PAGE_CACHE_PREFIX + shortcut 
    7375    return shortcut, cache_key 
     
    7577 
    7678class CacheMiddleware(object): 
     79    def __init__(self): 
     80        self.cache_key = None 
     81 
    7782    def process_request(self, request): 
    7883        """ 
     
    8186        user makes a GET or HEAD requests. 
    8287        """ 
    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 
    8689 
    8790        # cache only GET or HEAD requests 
     
    99102        # Build the cache key based on the page shortcuts 
    100103        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 
    102110 
    103111        # Get the page data from the cache. If not exist response is None. 
    104112        response = cache.get(self.cache_key) 
    105113 
    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) 
    115125 
    116126        return response 
     
    121131        Cache the response and insert the page statistics. 
    122132        """ 
     133        if request._from_cache == True: 
     134            # The content comes from the cache 
     135            return response 
     136 
    123137        if getattr(request, "_use_cache", False) != True: 
    124138            # Don't cache 
    125             self.insert_page_stats(request, response) 
     139            response = self._insert_cache_info(request, response, False) 
    126140            return response 
    127141 
    128142        # 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) 
    132146 
    133147        return response 
    134148 
    135149 
    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.") 
    188166 
    189167        content = response.content 
     
    197175                return response 
    198176 
    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) 
    201182        response.content = new_content 
    202183 
    203184        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  
    8484# 
    8585MIDDLEWARE_CLASSES = ( 
     86    # Insert a statistic line into the generated page: 
     87    'PyLucid.middlewares.pagestats.PageStatsMiddleware', 
     88 
    8689    # PyLucidCommonMiddleware loads the django middlewares: 
    8790    #    - 'django.contrib.sessions.middleware.SessionMiddleware' 
     
    9093    'PyLucid.middlewares.common.PyLucidCommonMiddleware', 
    9194 
     95    # Cache all anonymous cms page request, if CACHE_BACKEND worked. 
    9296    'PyLucid.middlewares.cache.CacheMiddleware', 
    9397 
  • trunk/pylucid/PyLucid/tools/shortcuts.py

    r1503 r1511  
    1919ALLOW_CHARS = string.ascii_letters + string.digits + "_" 
    2020 
     21def 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            ) 
    2134 
    2235def makeUnique(item_name, name_list): 
  • trunk/pylucid/tests/cache.py

    r1510 r1511  
    135135            must_not_contain=("Traceback",) 
    136136        ) 
    137  
    138         from_cache = response.get("from_cache", None) 
    139         self.failUnlessEqual(from_cache, None, msg) 
     137        self.failUnlessEqual(response["from_cache"], "no", msg) 
    140138 
    141139    def failIfNotFromCache(self, response, msg=None): 
     
    157155            raise AssertionError("Wrong url?") 
    158156 
    159         from_cache = response["from_cache"] 
    160         self.failUnlessEqual(from_cache, "yes", msg) 
     157        self.failUnlessEqual(response["from_cache"], "yes", msg) 
    161158 
    162159    #-------------------------------------------------------------------------- 
     
    314311        self._check_cache_after(test_func) 
    315312 
     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 
    316339 
    317340if __name__ == "__main__": 
  • trunk/pylucid/tests/security.py

    r1509 r1511  
    3737 
    3838CHARACTER_VARIANTS = ( 
    39     "<", "\x3c", "\x3C", u"\u003c", u"\u003C", 
     39    "<", r"\x3c", r"\x3C", r"\u003c", u"\u003c", u"\u003C", 
    4040#    "&#60", "&#060", "&#0060", "&#00060", "&#000060", "&#0000060", "&#60;", 
    4141#    "&#060;", "&#0060;", "&#00060;", "&#000060;", "&#0000060;", 
     
    143143        for char_variant in CHARACTER_VARIANTS: 
    144144            test_string = char_variant + VARIANTS_STRING 
    145             response = self.client.get("/" + test_string) 
     145            url = "/" + test_string 
     146            response = self.client.get(url) 
    146147            self.assertResponse( 
    147148                response, must_not_contain=(test_string, "<" + VARIANTS_STRING,) 
     
    167168                    self.assertResponse( 
    168169                        response, 
    169                         must_not_contain=(test_string, "<" + VARIANTS_STRING,) 
     170                        must_not_contain=("<" + VARIANTS_STRING,) 
    170171                    ) 
    171172 
     
    184185                self.assertResponse( 
    185186                    response, 
    186                     must_not_contain=(test_string, "<" + VARIANTS_STRING,) 
     187                    must_not_contain=("<" + VARIANTS_STRING,) 
    187188                ) 
    188189