減少 server loading 的方式

1. 將頁面產生的靜態檔案儲存在 db 裡面, 用Blob 型態儲存

2. 將 response header 裡面設定 304(Not Modify)

3. 將頁面的expire time 設定長一點
expires = datetime.now() + self.expire_interval


參考程式碼

# HINT: based on
# https://github.com/Arachnid/bloggart/blob/part1/static.py
# by Nick

from datetime import datetime
import webapp2
import hashlib
from google.appengine.ext import ndb
import os


class StaticContent(ndb.Model):
    # serve static & cache content
    path = ndb.TextProperty(required=True)
    body = ndb.BlobProperty(required=True, indexed=False)
    content_type = ndb.StringProperty(required=True, indexed=False)

    last_modified = ndb.DateTimeProperty(required=True, auto_now=True, indexed=False)

    etag = ndb.ComputedProperty(lambda self: hashlib.sha1(self.body).hexdigest(), indexed=False)
    branch = ndb.ComputedProperty(lambda self: self.version_id.split('.')[0], indexed=False)
    version_id = ndb.StringProperty(required=True, indexed=False)

    timeout = ndb.IntegerProperty(default=0)

    @classmethod
    def _get_key_name(cls, path):
        branch_name = os.environ.get("CURRENT_VERSION_ID").split('.')[0]
        return abs(hash(path + "|" + branch_name))

    @classmethod
    def get(cls, path):
        # HINT: in order to support different branch, use brench name + path as id
        obj = cls.get_by_id(cls._get_key_name(path))
        if not obj:
            return None
        elif (obj.timeout and (datetime.now() - obj.last_modified).total_seconds() > obj.timeout):
            return None
        else:
            return obj

    @classmethod
    def set(cls, path, body, content_type, timeout=0):
        # HINT: in order to support different branch, use brench name + path as id
        version_id = os.environ.get("CURRENT_VERSION_ID")
        branch_name = version_id.split('.')[0]

        content = StaticContent(
                                id=abs(hash(path + "|" + branch_name)),
                                path=path,
                                body=body,
                                content_type=content_type,
                                timeout = timeout,
                                version_id=version_id,
                                )
       
        content.put()
        return content

# HINT:
# http://stackoverflow.com/questions/12423614/local-variables-in-python-nested-functions
# the python nested function has some limit

HTTP_DATE_FMT_GMT = "%a, %d %b %Y %H:%M:%S GMT"
HTTP_DATE_FMT_UTC = "%a, %d %b %Y %H:%M:%S UTC"

class expires(object):
    """class based decorator is easier (because closure)
    """
    def __init__(self, expire_interval= None, force_expires=None):
        self.expire_interval = expire_interval
        self.force_expires = force_expires

    def __call__(self, handler_method):
        if not self.expire_interval and not self.force_expires:
            return handler_method

        def wrapper(h, *args, **kwds):
            result = handler_method(h, *args, **kwds)

            if self.force_expires:
                expires = force_expires
                self.expire_interval = expires - datetime.now()
            else:
                expires = datetime.now() + self.expire_interval

            if self.expire_interval.days > 364:
                max_age = 364 * 24 * 60 * 60
            else:
                max_age = self.expire_interval.total_seconds()

            h.response.headers['Expires'] = expires.strftime(HTTP_DATE_FMT_GMT)

            # enable edge cache (proxy)
            # ref: https://github.com/lucemia/Tagtoo/issues/528
            h.response.headers['Cache-Control'] = 'public, max-age=%d' % max_age
            h.response.headers['Pragma'] = 'Public'

            return result

        return wrapper

class cache_content(object):
    def __init__(self, cache_key_func = None, timeout = None):
        self.cache_key_func = cache_key_func
        self.timeout = timeout

    def add_response_header(self, h, content):              
        last_modified = content.last_modified.strftime(HTTP_DATE_FMT_GMT)
        h.response.headers.add('Last-Modified', last_modified)
        h.response.headers.add('ETag', content.etag)

    def output_content(self, h, content, status = None):
        self.add_response_header(h, content)
       
        if status != 304:
            h.response.headers.add('Content-Type', str(content.content_type))
            h.response.out.write(content.body)

        if status:
            h.response.set_status(status)

    def __call__(self, handler_method):
        def wrapper(h, *args, **kwds):
            path = (self.cache_key_func(h) if self.cache_key_func else h.request.url)
            content = StaticContent.get(path)

            if content and content.version_id == os.environ.get('CURRENT_VERSION_ID'):
                if content.timeout != self.timeout:
                    content.timeout = self.timeout
                    content.put()

                if 'If-Modified-Since' in h.request.headers:                  
                    if_modified_since = h.request.headers['If-Modified-Since']
                    if ';' in if_modified_since:
                        if_modified_since = if_modified_since.split(';')[0]
                    if "GMT" in if_modified_since:
                        try:
                            last_seen = datetime.strptime(if_modified_since, HTTP_DATE_FMT_GMT)
                        except:
                            last_seen = None
                    elif "UTC" in if_modified_since:
                        try:
                            last_seen = datetime.strptime(if_modified_since, HTTP_DATE_FMT_UTC)
                        except:                          
                            last_seen = None
                    else:
                        last_seen = None

                    if last_seen and last_seen >= content.last_modified.replace(microsecond=0):
                        return self.output_content(h, content, status=304)

                if 'If-None-Match' in h.request.headers:                  
                    etags = [x.strip() for x in h.request.headers['If-None-Match'].split(',')]

                    if content.etag in etags:
                        return self.output_content(h, content, status=304)

                return self.output_content(h, content, status=200)
           
            result = handler_method(h, *args, **kwds)

            content = StaticContent.set(path, h.response.body, h.response.headers.get('Content-Type'), self.timeout)
            self.add_response_header(h, content)

            return result          

        return wrapper


前端方法: http://www.instantshift.com/2009/03/10/11-tips-to-reduce-server-load-and-save-bandwidth/



留言

熱門文章