用Django REST Framework做的REST API,其中有个API有这样的需求:

APP端请求这个API,服务器端从数据库读数据,返回json。返回的数据量稍微有些大,但是可能一年才修改一次,所以希望能够仅在数据修改的时候才传输数据,让APP端更新。

1. Last-Modified和ETag

HTTP响应头Last-Modified和ETag都能实现这个需求,关于二者的详细解释,这篇文章说的简单明了:http://www.iwms.net/n2029c12.aspx

原文摘抄如下:

1) 什么是”Last-Modified”?
  在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:

  Last-Modified: Fri, 12 May 2006 18:53:33 GMT
  客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:

  If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
  如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
2) 什么是”Etag”?
  HTTP 协议规格说明定义ETag为“被请求变量的实体值” (参见 —— 章节 14.19)。 另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
  ETag: "50b1c1d4f775c61:df3"
  客户端的查询更新格式是这样的:
  If-None-Match: W/"50b1c1d4f775c61:df3"
  如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。本人测试Etag主要在断点下载时比较有用。
3) Last-Modified和Etags如何帮助提高性能?
  聪明的开发者会把Last-Modified 和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生 Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
  过程如下:

  1. 客户端请求一个页面(A)。
  2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。
  3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
  4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
  5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。

2. Django中添加Last-Modified和ETag

Django中有三个decorator,可以为响应添加Last-Modified和ETag,参看:https://docs.djangoproject.com/en/1.8/topics/conditional-view-processing/

  • condition(etag_func=None, last_modified_func=None)
  • etag(etag_func)
  • last_modified(last_modified_func)

要注意的是,官网给的使用方法都是针对function-based view,如果你使用的是class-based view,需要在url配置中调用decorator,例如:

url(r'^rest/colours/$',
etag(get_colour_checksum)(views.ColourList.as_view()),
name='colour-list'),

要实现前面的需求的话,思路很简单,获得数据表上次修改的时间或者给数据表算个hash值就行了。

一开始搜到了一个Mysql获取table最近更新时间的方法:http://stackoverflow.com/questions/307438/how-can-i-tell-when-a-mysql-table-was-last-updated

SELECT UPDATE_TIME
FROM information_schema.tables
WHERE TABLE_SCHEMA = 'dbname'
AND TABLE_NAME = 'tabname'

在自己的数据库上试了一下,返回却是NULL。仔细一看评论,这个方法在MyISAM上有用,但是在InnoDB上没有用。只好再找其他办法。

后来又找到一个能查看Table Checksum的语句:http://dev.mysql.com/doc/refman/5.5/en/checksum-table.html

CHECKSUM TABLE tbl_name [, tbl_name] ... [ QUICK | EXTENDED ]

测试有用!于是就用这个语句作为了生成ETag的方法。

3. 通过cache framework添加Last-Modified和ETag

因为我使用了Django的cache framework(https://docs.djangoproject.com/en/1.8/topics/cache/),将该请求的缓存时间设置为5分钟。所以有三个响应头部按照我设置的5分钟缓存时间被自动添加:

但是5分钟之后,这个缓存会被刷新,Last-Modified的时间也会相应被刷新,这样无法满足我前面说到的需求。那么应该怎么加入ETag呢?

通过阅读UpdateCacheMiddleware源码,我发现它在处理response的时候调用了函数 patch_response_headers,该函数源码如下:

def patch_response_headers(response, cache_timeout=None):
"""
Adds some useful headers to the given HttpResponse object:
ETag, Last-Modified, Expires and Cache-Control Each header is only added if it isn't already set. cache_timeout is in seconds. The CACHE_MIDDLEWARE_SECONDS setting is used
by default.
"""
if cache_timeout is None:
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
if cache_timeout < 0:
cache_timeout = 0 # Can't have max-age negative
if settings.USE_ETAGS and not response.has_header('ETag'):
if hasattr(response, 'render') and callable(response.render):
response.add_post_render_callback(_set_response_etag)
else:
response = _set_response_etag(response)
if not response.has_header('Last-Modified'):
response['Last-Modified'] = http_date()
if not response.has_header('Expires'):
response['Expires'] = http_date(time.time() + cache_timeout)
patch_cache_control(response, max_age=cache_timeout)

从代码中可以看出:

  • 如果Last-Modified, Expires之前未被设置,则这里按照cache的设置为他们赋值。
  • 如果 settings.USE_ETAGS 是True,且ETag未被设置,则会将调用默认方法或自定义方法,给ETag赋值。

因为默认的方法 _set_response_etag 是用响应内容算出MD5值赋给ETag,只要数据未变,ETag值是不变的,可以满足需求。

所以需要做的就是在settings里面加上USE_ETAG=True,就行了。

4. nginx对ETag的支持

通过前面的设置,我在开发机器上已经实现了ETag的添加,可以当我将代码部署到生产环境再测试的时候,确发现ETag没有了。生产环境用的是gunicorn+nginx,所以很自然就想到是nginx那边出了问题。

网上大部分搜到的nginx ETag相关的文章都在说怎么为静态文件添加ETag,和我的情况不一样。

花了很久,最后找到了原因:nginx 1.7.3 之前的版本对ETag支持不好,开了gzip就会自动把ETag删掉。参看:http://stackoverflow.com/questions/31125888/nginx-missing-etag-when-gzip-is-used

解决方法很简单,升级nginx到最新版本就行,连配置都不用修改。升级方法参看:http://nginx.org/en/linux_packages.html

Django添加Last-Modified和ETag的更多相关文章

  1. django 添加comments app

    django 添加comments app 参看 django comments 文档 安装和配置comments 1.安装comments,运行:pip install django-contrib ...

  2. django 添加动态表格的方法

    传统方法(基于方法的视图):http://stellarchariot.com/blog/2011/02/dynamically-add-form-to-formset-using-javascrip ...

  3. django添加静态文件

    最近做了一个todolist webapp,需要稍微添加css时候又忘记django的添加方法了,查看了以前的项目才想起来,所以记录一下. 1.settings.py 将以下代码放到最下面 STATI ...

  4. Django添加ckeditor富文本编辑器

    源码 https://github.com/django-ckeditor/django-ckeditor 通过pip安装. pip3 install django-ckeditor pip3 ins ...

  5. django添加装饰器

    引入模块: from django.utils.decorators import method_decorator 添加:@method_decorator(func) from django.ut ...

  6. Django 添加应用

    一个项目可以添加多个应用,可以使用以下两种方法来添加应用: [root@localhost web]$ python manage.py startapp blog [root@localhost w ...

  7. Django添加tinyMCE编辑器

    tinymce的使用方法很简单,只需要在html页面中包含如下: <!-- Place inside the <head> of your HTML --> <scrip ...

  8. Python - Django - 添加首页尾页上一页下一页

    添加首页和尾页: views.py: from django.shortcuts import render from app01 import models def book_list(reques ...

  9. Python - Django - 添加作者

    在 book_list.html 的页面下方加上 “添加作者” 的链接 <!DOCTYPE html> <html lang="en"> <head& ...

随机推荐

  1. Linux线程学习(二)

    线程基础 进程 系统中程序执行和资源分配的基本单位 每个进程有自己的数据段.代码段和堆栈段 在进行切换时需要有比较复杂的上下文切换   线程 减少处理机的空转时间,支持多处理器以及减少上下文切换开销, ...

  2. Linux平台卸载MySQL总结【转】

    最近用到了mysql主从,顺手看到了这篇文章,拿出来分享一下. 转自:http://www.cnblogs.com/kerrycode/p/4364465.html 潇湘隐者 RPM包安装方式的MyS ...

  3. MS SqlServer学习笔记(索引)

    1.索引分类 MS SqlServer提供了两种索引:聚集索引和非聚集索引: 聚集索引是将数据按照索引的顺序存放 非聚集索引是将索引和数据分离存放,通过指针将二者联系到一起. 因为两种索引对比: 使用 ...

  4. IIS does not list a website that matches the launch url

    233down voteaccepted I hate answering my questions: in my question i stated that i was running VS un ...

  5. 烂泥:LVM学习之逻辑卷、卷组及物理卷删除

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 上篇文章,我们介绍了有关LVM的逻辑卷及卷组的空间缩小.这次我们来介绍下如何删除一个逻辑卷及卷组. 删除逻辑卷需要以下几个步骤: 1. 卸载已经挂载的逻 ...

  6. Hive DDL DML SQL操作

    工作中经常要用到的一些东西,一直没整理,用的多的记住了,用的不多的每次都是去查,所以记录一下. DDL(数据定义语言),那就包括建表,修改表结构等等了 建表:create hive table hiv ...

  7. 笔者的编辑语法:MarkDown

    由于博客园里的文章有很多排版不好,一大堆文字堆在一块会影响到阅读. MarkDowm:百科 Markdown 是一种轻量级标记语言,创始人为约翰·格鲁伯(John Gruber).它允许人们“使用易读 ...

  8. 图片代替radio

    <html> <head> <meta http-equiv="Content-Type" content="text/html; char ...

  9. [转]Ionic + AngularJS angular-translate 国际化本地化解决方案

    本文转自:http://www.cnblogs.com/maoyazhi/p/4332362.html 欢迎访问我们的网站,网站上有更多关于技术性的交流:http://www.ncloud.hk/技术 ...

  10. 使用jmx监控tomcat

    1.在tomcat启动过程中,开启相应的参数配置: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -D ...