settings.py文件中

MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
]
# from django.contrib.sessions.middleware import SessionMiddleware
# ctrl+鼠标左击进入SessionMiddleware
class SessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
xxx
def process_request(self, request):
xxx
def process_response(self, request, response):
xxx

注释:xxx代表省略。这里实现的两个方法:process_requestprocess_response两个方法是Django中间件里面可以实现的5个方法中的两个。

# 初始化方法
def __init__(self, get_response=None):
self.get_response = get_response
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore

这里的初始化方法,首先将None赋值给了一个变量,engine其实是赋值了一个db对象。跟踪进settings发现了一个LazySettings对象,它没有SESSION_ENGINE变量,在它的的父类LazyObject也没有此变量,再来详细看一看LazySettings:

    def _setup(self, name=None):
"""
Load the settings module pointed to by the environment variable. This
is used the first time we need any settings at all, if the user has not
previously configured the settings manually.
"""
settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
if not settings_module:
desc = ("setting %s" % name) if name else "settings"
raise ImproperlyConfigured(
"Requested %s, but settings are not configured. "
"You must either define the environment variable %s "
"or call settings.configure() before accessing settings."
% (desc, ENVIRONMENT_VARIABLE)) self._wrapped = Settings(settings_module)

进入Settings对象:

class Settings(object):
def __init__(self, settings_module):
# update this dict from global settings (but only for ALL_CAPS settings)
for setting in dir(global_settings):
if setting.isupper():
setattr(self, setting, getattr(global_settings, setting)) # store the settings module in case someone later cares
self.SETTINGS_MODULE = settings_module mod = importlib.import_module(self.SETTINGS_MODULE) tuple_settings = (
"INSTALLED_APPS",
"TEMPLATE_DIRS",
"LOCALE_PATHS",
)
self._explicit_settings = set()
for setting in dir(mod):
if setting.isupper():
setting_value = getattr(mod, setting) if (setting in tuple_settings and
not isinstance(setting_value, (list, tuple))):
raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
setattr(self, setting, setting_value)
self._explicit_settings.add(setting)
# xxxxxxxxxx省略

注意到有一个global_settings变量,跟踪发现其实是一个py文件,它里面定义了一种初始化变量,类似于settings一样。在这里终于发现SESSION_ENGINESESSION_ENGINE = 'django.contrib.sessions.backends.db'。这就是一个操纵数据库的文件。

这里的代码主要是进行各种设置,以后为了进行settings这种操作。这里可以看到,大量使用了getattrsetattr这种自省操作。

回到SessionMiddleware,看看import_module

def import_module(name, package=None):
level = 0
if name.startswith('.'):
if not package:
msg = ("the 'package' argument is required to perform a relative "
"import for {!r}")
raise TypeError(msg.format(name))
for character in name:
if character != '.':
break
level += 1
return _bootstrap._gcd_import(name[level:], package, level)

这里主要是通过字符串标识的路径来导入该模块或其下的方法(属性)。

还有一个SessionStore,它是一个类,主要是为了进行session的操作。

看一看process_request

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

process_request在收到request之后,未解析url之前进行。

这里session_key主要就是在cookis中取出sessionid。其实SESSION_COOKIE_NAME是在上面提到的global_settings.py中定义的:SESSION_COOKIE_NAME = 'sessionid'。我们以后任何设置都可以在这里找到原型。

session是一个SessionStore对象。进入(from django.contrib.sessions.backends import db 进入):

class SessionStore(SessionBase):
def __init__(self, session_key=None):
super(SessionStore, self).__init__(session_key)

紧接着进入:SessionBase

class SessionBase(object):
TEST_COOKIE_NAME = 'testcookie'
TEST_COOKIE_VALUE = 'worked' __not_given = object() def __init__(self, session_key=None):
self._session_key = session_key
self.accessed = False
self.modified = False
self.serializer = import_string(settings.SESSION_SERIALIZER)

这里主要进行了赋值,注意到SESSION_SERIALIZER,在global_settings.py中找到:SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'。如果你用过drf的话,那么对这个serializers一定熟悉,就是做序列化的。

在设置session的时候通常认为它就是一个字典,可以进行字典赋值。字典的赋值调用__setitem__方法,获取__getitem__方法。

    def __getitem__(self, key):
return self._session[key]
def _get_session(self, no_load=False):
self.accessed = True
try:
return self._session_cache
except AttributeError:
if self.session_key is None or no_load:
self._session_cache = {}
else:
self._session_cache = self.load()
return self._session_cache _session = property(_get_session)

可以看到这里运用了property,就是将一个方法变为属性来操作的装饰器。第一次访问服务器时_session_cache没有这个变量,然后session_key也是空的,no_loadFalse,所以初始化一个self._session_cache = {}。这里总体来说,如果你第一次访问服务器,那么创建一个session的字典。

再来看一看__setitem__:

    def __setitem__(self, key, value):
self._session[key] = value
self.modified = True
def _get_session(self, no_load=False):
self.accessed = True
try:
return self._session_cache
except AttributeError:
if self.session_key is None or no_load:
self._session_cache = {}
else:
self._session_cache = self.load()
return self._session_cache _session = property(_get_session)

这里与上面变化不大,如果是赋值的时候,那么_session_cache已经存在,直接赋值即可。

注意这里还没有保存到数据库中。

再来看一看process_response

    def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
pass
else:
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
)
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
)
return response

主要是在执行视图函数之后返回Response后调用的,进行一些session的返回等。

首先进行赋值:accessedmodified这两个值在之前设置为True。这里empty:

    def is_empty(self):
"Returns True when there is no session_key and the session is empty"
try:
return not bool(self._session_key) and not self._session_cache
except AttributeError:
return True

意思就是当没有session_keysession是空的时候为True,之后进行了一些cookie的设置以及向response写入了一些东西。接着在process_response里面我们发现了一个重要的操作:

request.session.save()

我们知道session是一个SessionStore对象,所以我们就到那里去寻找save操作:

    def save(self, must_create=False):
"""
Saves the current session data to the database. If 'must_create' is
True, a database error will be raised if the saving operation doesn't
create a *new* entry (as opposed to possibly updating an existing
entry).
"""
if self.session_key is None:
return self.create()
data = self._get_session(no_load=must_create)
obj = self.create_model_instance(data)
using = router.db_for_write(self.model, instance=obj)
try:
with transaction.atomic(using=using):
obj.save(force_insert=must_create, force_update=not must_create, using=using)
except IntegrityError:
if must_create:
raise CreateError
raise
except DatabaseError:
if not must_create:
raise UpdateError
raise

这里主要进行session的持久化保存,就是保存到数据库。

浏览器第一次访问不会携带session。这里第一次会执行return self.create(),进入create()

    def create(self):
while True:
self._session_key = self._get_new_session_key()
try:
# Save immediately to ensure we have a unique entry in the
# database.
self.save(must_create=True)
except CreateError:
# Key wasn't unique. Try again.
continue
self.modified = True
return

这里进行了唯一性判断,_session_key保证不重复。然后进入_get_new_session_key()

    def _get_new_session_key(self):
"Returns session key that isn't being used."
while True:
session_key = get_random_string(32, VALID_KEY_CHARS)
if not self.exists(session_key):
break
return session_key

create()一致,进入get_random_string():

def get_random_string(length=12,
allowed_chars='abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
"""
Returns a securely generated random string. The default length of 12 with the a-z, A-Z, 0-9 character set returns
a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
"""
if not using_sysrandom:
# This is ugly, and a hack, but it makes things better than
# the alternative of predictability. This re-seeds the PRNG
# using a value that is hard for an attacker to predict, every
# time a random string is required. This may change the
# properties of the chosen random sequence slightly, but this
# is better than absolute predictability.
random.seed(
hashlib.sha256(
("%s%s%s" % (
random.getstate(),
time.time(),
settings.SECRET_KEY)).encode('utf-8')
).digest())
return ''.join(random.choice(allowed_chars) for i in range(length))

这里使用了hashlib模块用sha256进行了加密,包括字符,时间以及settings.py文件中的那个SECRET_KEY。所以那个变量非常重要,千万不要外泄。

创建完成之后,在create函数中会保存,即执行save()函数,跟上面一样:

    def save(self, must_create=False):
"""
Saves the current session data to the database. If 'must_create' is
True, a database error will be raised if the saving operation doesn't
create a *new* entry (as opposed to possibly updating an existing
entry).
"""
if self.session_key is None:
return self.create()
data = self._get_session(no_load=must_create)
obj = self.create_model_instance(data)
using = router.db_for_write(self.model, instance=obj)
try:
with transaction.atomic(using=using):
obj.save(force_insert=must_create, force_update=not must_create, using=using)
except IntegrityError:
if must_create:
raise CreateError
raise
except DatabaseError:
if not must_create:
raise UpdateError
raise

这次就if不通过,然后进入_get_session:

    def _get_session(self, no_load=False):
"""
Lazily loads session from storage (unless "no_load" is True, when only
an empty dict is stored) and stores it in the current instance.
"""
self.accessed = True
try:
return self._session_cache
except AttributeError:
if self.session_key is None or no_load:
self._session_cache = {}
else:
self._session_cache = self.load()
return self._session_cache

它里面主要是判断session是否缓存了,如果缓存直接返回缓存数据,如果没有,那么构造一个_session_cache字典。返回,进入create_model_instance:

    def create_model_instance(self, data):
"""
Return a new instance of the session model object, which represents the
current session state. Intended to be used for saving the session data
to the database.
"""
return self.model(
session_key=self._get_or_create_session_key(),
session_data=self.encode(data),
expire_date=self.get_expiry_date(),
)

返回一个model,进入model()函数:

    @cached_property
def model(self):
return self.get_model_class()

进入get_model_class()函数,跟进去之后发现它导入了一个Session对象:

    @classmethod
def get_model_class(cls):
# Avoids a circular import and allows importing SessionStore when
# django.contrib.sessions is not in INSTALLED_APPS.
from django.contrib.sessions.models import Session
return Session
class Session(AbstractBaseSession):
"""
Django provides full support for anonymous sessions. The session
framework lets you store and retrieve arbitrary data on a
per-site-visitor basis. It stores data on the server side and
abstracts the sending and receiving of cookies. Cookies contain a
session ID -- not the data itself. The Django sessions framework is entirely cookie-based. It does
not fall back to putting session IDs in URLs. This is an intentional
design decision. Not only does that behavior make URLs ugly, it makes
your site vulnerable to session-ID theft via the "Referer" header. For complete documentation on using Sessions in your code, consult
the sessions documentation that is shipped with Django (also available
on the Django Web site).
"""
objects = SessionManager() @classmethod
def get_session_store_class(cls):
from django.contrib.sessions.backends.db import SessionStore
return SessionStore class Meta(AbstractBaseSession.Meta):
db_table = 'django_session'

注意底下的 db_table = 'django_session`,它就是在数据库中表的名称。它继承了父类的一些东西。

之后返回到save()方法:

    def save(self, must_create=False):
"""
Saves the current session data to the database. If 'must_create' is
True, a database error will be raised if the saving operation doesn't
create a *new* entry (as opposed to possibly updating an existing
entry).
"""
if self.session_key is None:
return self.create()
data = self._get_session(no_load=must_create)
obj = self.create_model_instance(data)
using = router.db_for_write(self.model, instance=obj)
try:
with transaction.atomic(using=using):
obj.save(force_insert=must_create, force_update=not must_create, using=using)
except IntegrityError:
if must_create:
raise CreateError
raise
except DatabaseError:
if not must_create:
raise UpdateError
raise

进入db_for_write是查找数据库的代码。这一块暂时停一下。接着就开始数据库的事务操作了,确保正确将 session 存入数据库。

SessionMiddleware源码分析的更多相关文章

  1. Django中间件之SessionMiddleware源码分析

    settings.py文件中 MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', ] # from djang ...

  2. [asp.net core 源码分析] 01 - Session

    1.Session文档介绍 毋庸置疑学习.Net core最好的方法之一就是学习微软.Net core的官方文档:https://docs.microsoft.com/zh-cn/aspnet/cor ...

  3. Django之REST framework源码分析

    前言: Django REST framework,是1个基于Django搭建 REST风格API的框架: 1.什么是API呢? API就是访问即可获取数据的url地址,下面是一个最简单的 Djang ...

  4. Django rest framework源码分析(一) 认证

    一.基础 最近正好有机会去写一些可视化的东西,就想着前后端分离,想使用django rest framework写一些,顺便复习一下django rest framework的知识,只是顺便哦,好吧. ...

  5. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  6. DRF cbv源码分析 restful规范10条 drf:APIView的源码 Request的源码 postman的安装和使用

    CBV 执行流程 路由配置:url(r'^test/',views.Test.as_view()),  --> 根据路由匹配,一旦成功,会执行后面函数(request) --> 本质就是执 ...

  7. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  8. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  9. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

随机推荐

  1. nova 通过 python curl 创建虚拟机---keystone v3

    #! /bin/python #coding=utf- import urllib2 import json import requests # token post_url = 'http://10 ...

  2. Ubuntu 磁盘满了处理方法。

    Ubuntu 磁盘满了处理方法: 1. 如果是虚拟机安装ubuntu,直接给虚拟机安装ubuntu 系统所在的盘符动态分配一点磁盘容量,就可以了. 2. 如果不是虚拟机安装ubuntu,那么有两个办法 ...

  3. Spring Boot入门系列(十九)整合mybatis,使用注解实现动态Sql、参数传递等常用操作!

    前面介绍了Spring Boot 整合mybatis 使用注解的方式实现数据库操作,介绍了如何自动生成注解版的mapper 和pojo类. 接下来介绍使用mybatis 常用注解以及如何传参数等数据库 ...

  4. 简单实现C++Stack模板

    栈的特点是先进后出以及只能在栈顶进行插入和删除操作 本次实现的栈的基本操作: 1)弹栈 2)压栈 3)求栈大小 4)求栈容量 5)判断栈空 6)获取栈顶元素 1.用数组的方式实现栈基本操作 /** * ...

  5. typepra快捷键

  6. 百度支持链接的nofollow属性吗

    http://www.wocaoseo.com/thread-269-1-1.html 简单明确的一个问题,百度目前支持链接的nofollow属性吗?rel='external nofollow' 复 ...

  7. Qt 多语言转换

    Qt QTranslator 实现多语言转换(转载)   1.在*.pro文件里面添加TRANSLATIONS += English.tsChinese.ts根据自己想要添加多少种语言和什么语言视情况 ...

  8. android,在java代码中,如何给textview设置不同的颜色,以及引用color中资源设置颜色

    textV.setTextColor(Color.parseColor("#FF0000")); <pre name="code" class=" ...

  9. 敏捷转型谁先动:老总,项目经理or团队

    摘要: 敏捷转型成功的企业究竟是从老总开始?还是从项目经理开始?还是团队本身具有这种意识?相信还有很多想要转型敏捷的公司都存在这样的疑问. 从06年首届敏捷中国开发者大会召开到现在,敏捷方法在国内的应 ...

  10. HDU多校1003-Divide the Stones(构造)

    Problem Description There are n stones numbered from 1 to n.The weight of the i-th stone is i kilogr ...