Django: Cookies & Session

Cookies 和 Session 都是用于实现HTTP 持久化的工具。他们的不同处在于 Cookie 是放在客户端(浏览器)上的文本;Session 是储存在服务器上的资源。

在大部分情况下,他们的作用一样 —— Cookie(Session)是一串键值对,通过在服务器中检索键值对中的值,来获取用户登录状态、确定用户权限或返回用户请求的资源。由于HTTP 协议是无状态的,如果没有cookie(session) ,同一个用户的两次请求,服务器会认为来自不同用户,有些状态就没法保持(比如:用户在A页面将商品加入购物车后点进购物车页面)。
在大多数情况下,他们可以单独使用:

  • 仅使用cookie:在头部字段携带cookie,服务器端取cookie值,返回相应资源;
  • 仅使用session: 在头部字段携带 XMLHTTPRequest 对象索引session 位置;
    不过在Django 中,Session 完全基于 Cookie 才能使用。以下是他们的一些细节。

COOKIES

在Django 中,View 获取cookie 的方法是调用request.COOKIES['cookie_name']

COOKIES 位于HTTPRequest 类中,是字典类型变量。Request 的生命周期是这样:
从浏览器传入 -> WSGI -> 生成HTTPRequest obj -> 经过中间件 -> URLs 路由 -> View

COOKIES 是在生成HTTPRequest 类时被初始化的。

在最初对网页发起Request 请求时,COOKIES 是空的。网页回传Response 时,通过set_cookie(),在返回头中加入Set-Cookie 字段。随后再次请求同一个域才有值。

Cookie 最初应该是字符串形式,为了让它变成字典型,Django 使用 parse_cookie()将其转换,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# django/http/cookies
def parse_cookie(cookie):
"""
Return a dictionary parsed from a `Cookie:` header string.
"""
cookiedict = {}
for chunk in cookie.split(';'):
if '=' in chunk:
key, val = chunk.split('=', 1)
else:
# Assume an empty name per
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
key, val = '', chunk
key, val = key.strip(), val.strip()
if key or val:
# unquote using Python's algorithm.
cookiedict[key] = cookies._unquote(val)
return cookiedict

此时客户端已经得到了cookie ,接下来就要用它和Session 交互了。

Session

在Django 中,使用了专门的Middleware 支持Session。编辑设置中的 MIDDLEWARE,并确保他包含了 'django.contrib.sessions.middleware.SessionMiddleware'

在SessionMiddleware 中,通过process_requestprocess_response 修改 requestresponsesession 属性。

当传入Request 时,从COOKIES 取出setting.py约定的SESSION_COOKIE_NAME,通过这个值从Session引擎中取出相应的Session 对象,赋值 reqeust.session 。所以说,在Django 中,Session 和 Cookie 通过 SESSION_COOKIE_NAME 字段的值联结。代码如下:

1
2
3
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)

存储Session 的可能是数据库、文件系统或缓存,Session引擎提供了一套屏蔽不同类型操作的API。

当传出Response 时,process_response 检查 request.session 的三个属性,分别是accessed empty modify 。当empty = True ,证明在刚刚的View 中,用户登出了,此时应当删除Cookie,返回Response (但还不是删除Session 的时候,只有Session 内容全为空时才能删掉)。当accessed = True,用当前的Cookie 内容更新 Vary头;而当 modify = True 刷新的过期时间(此时还没有保存!),随后检查HTTP状态码,如果返回码不是500,保存当前session 并使用该session 内容更新Cookie ,否则不保存也不更新。最后返回Response 。

process_response 接收三个参数:self、request、response,request是上一个中间件发来的,里面包含session,response是view通过中间件一层一层传过来的,里面包含cookie。

process_response伪代码

def process_response(self, request, response):
    get request.session.access/ modify/ empty 

    if empty:
        response.delete_cookie(SESSION_COOKIE_NAME )

    else:
        if accessed:
            patch_vary_headers(response, ('Cookie',))

        if modified  and not empty:
            use session's attrs update expire & max_age

            if response.status_code != 500:
                request.session.save()
                response.set_cookie(expire and max_age)                 
    return response