首页 > 代码库 > 16:django 有条件的视图处理(Last-Modified和ETag)&&加密签名

16:django 有条件的视图处理(Last-Modified和ETag)&&加密签名

有条件的视图处理

上一节我们介绍了缓存来减轻服务器的负担,这里的有条件的视图处理也从一定程度上减轻了服务器的负担,在正式介绍之前,先来看两个概念:Last-Modified和ETag

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.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

ETag

请求流程

Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match.请求一个文件的流程可能如下:
====第一次请求===
1.客户端发起 HTTP GET 请求一个文件;
2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag).状态码200
====第二次请求===
1.客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d044840
2.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;
流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办?
答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.(不要陷入到底使用谁的问题怪圈)

作用

Etag 主要为了解决 Last-Modified 无法解决的一些问题。
1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
3、某些服务器不能精确的得到文件的最后修改时间;
为此,HTTP/1.1引入了 Etag(Entity Tags).Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说"2e681a-6-5d044840"这么一串看起来很神秘的编码。但是HTTP/1.1标准并没有规定Etag的内容是什么或者说要怎么实现,唯一规定的是Etag需要放在""内。

 

知道了这两个概念之后,我们应该知道这两个概念的作用了吧,也大概知道我们接下来要讲解的是什么啦。没错,就是,django中如何使用Last-Modified和ETag这两个概念呢

condition装饰器

Last-Modified和ETag在django中是两个函数,前者返回一个日期类型数据,后者返回一个值(ETag值),这两个函数可以作为参数传递给django.views.decorators.http.condition这个装饰器,装饰器原型是

condition(etag_func=None, last_modified_func=None)

这是一个使用的简单例子

def latest_entry(request, blog_id):    return Entry.objects.filter(blog=blog_id).latest("published").publishedfrom django.views.decorators.http import condition@condition(last_modified_func=latest_entry)def front_page(request, blog_id):    ...

在django.views.decorators.http.condition文件里还提供了另外两个装饰器,可以使得只需要提供一个参数即可,看源码很容易懂:

def etag(etag_func):    return condition(etag_func=etag_func)def last_modified(last_modified_func):    return condition(last_modified_func=last_modified_func)

说完了Last-Modified和ETag,我们继续说一下

django加密签名

web应用安全的黄金法则是:永远不要相信从不受信任来源的数据。但有时候我们只能从不受信任的媒介获取或者发送数据,或者不受信任的媒介更具吸引力,比如我们确保我们的数据一定是安全的,并且不受信任的媒介比受信任媒介要快捷甚至便宜的多。加密签名的值一旦被篡改就会被检测到,这是我们能确保数据安全的共识。下面是几个加密签名应用的例子:

  • 找回密码所用的url
  • 确保form表单中的隐藏域没有被修改
  • 一次性的允许访问保护数据的加密url

django提供了一个底层的用来加密数据的API和一个高层的用来设置和读取加密cookies的API

保护好你的SECRET_KEY

用django-admin.py startproject命令生成的项目自带一个自动随机生成的SECRET_KEY,注意不要泄露这个key

使用低层次的API

django的加密方法源代码位于django\core目录下的signing.py文件,可以具体去看看,下面是一些基本的用法:

>>> from django.conf import settings>>> settings.configure()>>> from django.core.signing import Signer>>> signer = Signer()>>> value = http://www.mamicode.com/signer.sign("qiweijie")>>> valueqiweijie:lVrPb11e1K9K_DcxnMGNYk8t2aQ>>> original = signer.unsign(value)>>> originaluqiweijie

使用“调味剂”参数(salt)

salt是“盐,调味剂”的意思,如果你不想每次对同一字符加密的结果都一样,那么你可以是用调味剂参数调剂一下,哈哈,看示例:

>>> signer = Signer()>>> signer.sign(My string)My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w>>> signer = Signer(salt=extra)>>> signer.sign(My string)My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw>>> signer.unsign(My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw)uMy string

验证时间戳

TimestampSigner是Signer的子类,添加了一个加密的时间戳给值。这允许你可以确保一个加密的数据是在一个给定的时间内创建的

>>> from django.core.signing import TimestampSigner>>> signer = TimestampSigner()>>> value = http://www.mamicode.com/signer.sign(hello)>>> valuehello:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c>>> signer.unsign(value)uhello>>> signer.unsign(value, max_age=10)...SignatureExpired: Signature age 15.5289158821 > 10 seconds>>> signer.unsign(value, max_age=20)uhello

保护复杂的数据结构

列表,元组和字典,如果你直接使用上述的方法,最后得到的是一个字符串而不是原来的数据类型:

>>> dic = {1:2}>>> sd=signer.sign(dic)>>> signer.unsign(sd)u{1: 2}

如果你想保护这些数据类型,请使用dumps和loads方法,它们都位于django.core.signing模块里面

dumps(objkey=Nonesalt=‘django.core.signing‘compress=False)
loads(stringkey=Nonesalt=‘django.core.signing‘max_age=None)
>>> from django.core import signing>>> value = http://www.mamicode.com/signing.dumps({"foo": "bar"})>>> valueeyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI>>> signing.loads(value){foo: bar}

 

16:django 有条件的视图处理(Last-Modified和ETag)&&加密签名