Django 中间件实现动态页面缓存

今天阅读资料时,发现一个服务器缓存相关的东西“ETAG”。

简单的讲就是浏览器请求服务器时,服务器在返回内容的同时,会把一个和内容相关的标识符“ETAG”添加到响应头中;浏览器接收到返回消息时,会把响应头里的“ETAG”值保存,在下次请求相同页面时,会把上次保存的“ETAG”值以“IF_NONE_MATCH”为key添加到请求头中;服务器在接收到请求时,会把“IF_NONE_MATCH”和返回内容生成的“ETAG”值进行比较,如果相同,则返回304;否则返回新内容并添加“ETAG”到响应头。如此循环。

正常情况下服务器反向代理软件都支持“ETAG”形式缓存,但都是只支持静态文件,无法对动态内容进行缓存。

在反向代理无法完成需求时,我们可以通过django的中间件(Middleware)实现相同功能。

一、分析

因为我们要做的是全局的缓存处理,不能是在每一个view里写相同的代码,所以最好的方式是使用中间件。

而对中间件的几个状态,我们选择process_response状态,因为此时服务器已经完成相应内容封装,我们可以轻易拿到相应内容进行处理。

最后就是特征值“ETAG”生成,(貌似)nginx的生成是MD5,我们也就采用MD5生成“ETAG”吧。

还有一个坑就是,nginx开启gzip压缩时,会过滤强“ETAG”,但不会过滤弱“ETAG”(即weak etag)。这点一定要注意。

二、实现

前面已经分析了原理及实现过程,那么下面直接上代码。

# coding: utf-8

import hashlib
from django.http import HttpResponseNotModified


class EtagMiddleware(object):
    def process_response(self, request, response):
        # 动态页面ETAG缓存策略计算
        # 在生成的ETAG前面加上"W/"来返回weak tag
        # 防止nginx开启gzip时过滤
        
        Etag = "W/" + hashlib.md5(response.content).hexdigest()
        if request.META.get("HTTP_IF_NONE_MATCH") != Etag:
            response["ETag"] = Etag
            return response
        else:
            return HttpResponseNotModified()

最后记得把中间件添加到的django配置中。如此已经实现全局etag校验功能。