Django丨中间件 - Session源码流程分析

Django丨中间件 - Session源码流程分析

1. 先从session中间件中进入

  • django.contrib.sessions.middleware.SessionMiddleware
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
    self.get_response = get_response
    engine = import_module(settings.SESSION_ENGINE)
    self.SessionStore = engine.SessionStore

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

    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:
    return response
    # 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,
    )
    patch_vary_headers(response, ('Cookie',))
    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 = http_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,
    samesite=settings.SESSION_COOKIE_SAMESITE,
    )
    return response

2. 首先会先进入到SessionMiddleware的__init__方法

1
2
3
4
def __init__(self, get_response=None):
self.get_response = get_response # 刚开始为None
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore

a. engine = import_module(settings.SESSION_ENGINE):获取存储session的方式,存储session一共有5种方式(缓存,缓存+数据库,数据库,文件,cookie);这里会先从setting里面取SESSION_ENGINE,如果没有设置,则到global_settings里面取,默认存储方式是储存在数据库中;
默认设置:
SESSION_ENGINE = 'django.contrib.sessions.backends.db'

import_module:就是从路径中导入一个模块

b. 执行self.SessionStore = engine.SessionStoredb模块里面的SessionStore导入并赋值给self.SessionStore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class SessionStore(SessionBase):
"""
Implement database session store.
"""
def __init__(self, session_key=None):
super().__init__(session_key)

@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

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

def _get_session_from_db(self):
try:
return self.model.objects.get(
session_key=self.session_key,
expire_date__gt=timezone.now()
)
except (self.model.DoesNotExist, SuspiciousOperation) as e:
if isinstance(e, SuspiciousOperation):
logger = logging.getLogger('django.security.%s' % e.__class__.__name__)
logger.warning(str(e))
self._session_key = None

def load(self):
s = self._get_session_from_db()
return self.decode(s.session_data) if s else {}

def exists(self, session_key):
return self.model.objects.filter(session_key=session_key).exists()

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

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(),
)

def save(self, must_create=False):
"""
Save the current session data to the database. If 'must_create' is
True, raise a database error 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

def delete(self, session_key=None):
if session_key is None:
if self.session_key is None:
return
session_key = self.session_key
try:
self.model.objects.get(session_key=session_key).delete()
except self.model.DoesNotExist:
pass

@classmethod
def clear_expired(cls):
cls.get_model_class().objects.filter(expire_date__lt=timezone.now()).delete()

2 总结:获取session的存储方式,把SessionStore类赋值给self.SessionStore,以便后面调用.

3. 然后请求走到process_request方法

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

a. session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME):先从cookie里找sessionid这个keysessionid这个键是在globle_setting里面定义的:SESSION_COOKIE_NAME = 'sessionid'),如果有则返回,没有则返回空,一开始进入页面的时候,self.COOKIES一定为空:self.COOKIES = {}

b. request.session = self.SessionStore(session_key):实际上是在实例化一个SessionStore类,然后把session_key传进去

1
2
3
4
5
6
class SessionStore(SessionBase):
"""
Implement database session store.
"""
def __init__(self, session_key=None):
super().__init__(session_key)

这个类的__init__方法里调用的是父类的__init__方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SessionBase:
"""
Base class for all Session classes.
"""
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 # 开始等于False, 后面如果调用session时, 会变成True
self.modified = False # 同上
self.serializer = import_string(settings.SESSION_SERIALIZER)

c. 当用户通过request.session赋值时,会调用SessionStore的父类的__setitem__方法(request.session就是SessionStore的实例化), 在__setitem__里因为self._session[key] = value,又因为_session = property(_get_session), 所以找到_get_session并运行_get_session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SessionBase:
... ...
def __setitem__(self, key, value):
self._session[key] = value # 这里得到一个空字典
self.modified = True # 这里把__init__中的modified = True
... ...
def _get_session(self, no_load=False):
"""
Lazily load session from storage (unless "no_load" is True, when only
an empty dict is stored) and store it in the current instance.
"""
self.accessed = True # 这里把__init__中的acessed = True
try:
return self._session_cache # 这里尝试从类中找_session_cache
except AttributeError: # 没有则报了error
if self.session_key is None or no_load: # 第一次进来,session_key为空
self._session_cache = {} # 这里把 self._session_cache = {}
else:
self._session_cache = self.load()
return self._session_cache # 把 self._session_cache 返回出去

_session = property(_get_session)

d. self.serializer = import_string(settings.SESSION_SERIALIZER)
global_settings里定义了:
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'

process_request总结:这里主要是执行SessionBase中的__setitem__方法,把用户传入的键值对放到self._session中;

4. 然后请求走到process_response方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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 # True
modified = request.session.modified # True
empty = request.session.is_empty() # False
except AttributeError:
return response
# 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,
)
patch_vary_headers(response, ('Cookie',))
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 = http_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,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
return response

a. accessedmodifiedTrue是在process_request方法中改成True了,现在看一下empty为什么等于False

empty = request.session.is_empty():

1
2
3
4
5
6
7
8
9
10
11
12
class SessionBase:
... ...
def is_empty(self):
"Return True when there is no session_key and the session is empty."
try:
return not self._session_key and not self._session_cache
# self._session_key是没有的,所以为False,
# self._session_cache 是有值的,所以为True
# 因为是and,False and True = False
except AttributeError:
return True
... ...

b. 然后走下去,是一系列的判断,判断是否关闭浏览器等等。。。走到下面这段代码,是判断页面有无报错,无报错则调用db模块下的SessionStore类下的save()方法

1
2
3
4
5
6
def process_response(self, request, response):
... ...
if response.status_code != 500:
try:
request.session.save()
... ...

c. 接着去看save()方法:调用save()方法,session_key为空,走create()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class SessionStore(SessionBase):
... ...
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

... ...

def save(self, must_create=False):
"""
Save the current session data to the database. If 'must_create' is
True, raise a database error 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
... ...

d. 继续看create()方法:
self._session_key = self._get_new_session_key()

1
2
3
4
5
6
7
8
9
class SessionStore(SessionBase):
... ...
def _get_new_session_key(self):
"Return session key that isn't being used."
while True:
session_key = get_random_string(32, VALID_KEY_CHARS)
if not self.exists(session_key):
return session_key
... ...

_get_new_session_key()这个方法里生成一个随机字符串,判断该随机字符串是否存在,不存在则返回出去;

e. 然后继续执行create()方法里面的save()方法:self.save(must_create=True)

f. 然后save()继续走,到data = self._get_session(no_load=must_create):在create()方法调用save()方法的时候传了一个must_create=True的参数,所以no_load=True
总结:data = self._get_session(no_load=must_create):将视图中给session赋值的字典,赋值给data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SessionBase:
... ...
def _get_session(self, no_load=False):
"""
Lazily load session from storage (unless "no_load" is True, when only
an empty dict is stored) and store it in the current instance.
"""
self.accessed = True
try:
return self._session_cache # 在process_request的时候赋值了
except AttributeError:
if self.session_key is None or no_load:
self._session_cache = {}
else:
self._session_cache = self.load()
return self._session_cache
... ...

g. obj = self.create_model_instance(data):将session_keysession_dataexpire_date保存到model属性中,这个model就是Session模型,然后obj.save(force_insert=must_create, force_update=not must_create, using=using)将数据同步到数据库中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SessionStore(SessionBase):
... ...
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( # 这个model就是Session模型
session_key=self._get_or_create_session_key(),
session_data=self.encode(data),
expire_date=self.get_expiry_date(),
)
... ...

h. 回到上面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def process_response(self, request, response):
... ...
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( # 响应对象设置cookie key 为sessionid
settings.SESSION_COOKIE_NAME, # 这个是 sessionid
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,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
... ...

最后返回了response对象