此篇博客是分析django SessionMiddleware源码执行流程 
SessionMiddlewaredjango框架的一个中间件,关于中间件的自定义创建、执行流程,这篇博客不去说明讲解,我们只是来了解下SessionMiddleware的源码

当我们创建一个django项目,会默认在项目目录下settings.py中会设置中间件,这篇笔记就来简单了解下SessionMiddleware中间件的执行流程

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

那么在上面代码中,我们该如何去浏览SessionMiddleware的源码呢?我们看python安装包的目录: 
这里写图片描述 
找到我们pip安装的django库,在django中间件的配置中有如下一行代码

'django.contrib.sessions.middleware.SessionMiddleware',

我们通过这个字符串可以定位到该中间件的代码目录,从而进行查看其源码 
我们来看下SessionMiddleware在django库的什么位置? 
这里写图片描述

django在加载中间件配置时,会在conf下去加载配置,这个加载过程,我们接下来的代码会简单分析 
所以我们可以通过,进行点击源码查看

from django.contrib.sessions.middleware import SessionMiddleware

ok我们现在定位到middleware模块,就能查看SessionMiddleware类及其类方法了 
我们看到该中间件代码如下,这也是其他中间件的基本代码组成

class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
    ..省去一些代码..
    def process_request(self, request):
    ..省去一些代码..
    def process_response(self, request, response):
    ..省去一些代码..

 

毋庸置疑,我们先看__init__方法,在类初始化时,进行调用的,我们看下做了什么操作? 
贴出__init__代码如下:

    def __init__(self, get_response=None):
        self.get_response = get_response
        engine = import_module(settings.SESSION_ENGINE)
        self.SessionStore = engine.SessionStore

 

第一行不解释,直接赋值了一个None值 
第二行代码engine = import_module(settings.SESSION_ENGINE)看看做了什么? 
该方法 import_module是利用了importlib模块下的方法,意思就是通过字符串标识的路径,去导入该模块或者该模块下的方法或属性,我们来简单看下即可,最后定位到如下代码:

def _find_and_load(name, import_):
    """Find and load the module."""
    with _ModuleLockManager(name):
        module = sys.modules.get(name, _NEEDS_LOADING)
        if module is _NEEDS_LOADING:
            return _find_and_load_unlocked(name, import_)

    if module is None:
        message = ('import of {} halted; '
                   'None in sys.modules'.format(name))
        raise ModuleNotFoundError(message, name=name)

    _lock_unlock_module(name)
    return module

那么settings.SESSION_ENGINE来看下到底是什么字符串,来代表哪个模块或者属性方法 
当我们看settings时候,去发现SESSION_ENGINE定位到了conf\_init_模块,进而定位到了如下属性:

settings = LazySettings()

但是该对象下没有SESSION_ENGINE此属性,我们可能想到该类是继承了LazyObject,难道在LazyObject下面,我们跟随代码查看,结果依然没有,那我们只能在LazySettings类中进行查找了,看看能不能发现线索,我们看LazySettings类下的_setup方法代码:

  def _setup(self, name=None):

        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(settings_module)中跟随代码进行追踪查看,会看到如下代码

 def __init__(self, settings_module):
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))
        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)

我们能通过代码大致了解到,django会先去循环遍历global_settings中的配置,然后在去循环settings_module中的配置,也就是我们项目下的setting配置,里面有诸多我们自己的配置 
比如如下的

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rbac.apps.RbacConfig',
    'app.apps.AppConfig',
]

好把,所以settings.SESSION_ENGINE我们从global_settings找到如下配置

SESSION_ENGINE = 'django.contrib.sessions.backends.db'

所有 engine = import_module(settings.SESSION_ENGINE)获取到的engine 是一个db模块 
接下来的代码self.SessionStore = engine.SessionStore是拿到了一个db模块下的SessionStore对象 
该模块是对数据库的一些操作,后续我们在看这块的源码

以上就把SessionMiddleware中间件中的__init__方法分析完毕,接下来我们看下process_request方法

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

 

我们在global_settings中拿到SESSION_COOKIE_NAME = 'sessionid'request.COOKIES.get(settings.SESSION_COOKIE_NAME)意思就是获取我们设置的sessionid值, 
接下来request.session = self.SessionStore(session_key)我们就看db模块做了什么操作? 
首先看对象初始化方法

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

 

我们看看父类SessionBase做了什么操作

  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_key以及accessed是标识是否获取过session,以及modified 是否进行对session进行修改操作,serializer就是对session进行序列化,然后存入数据库级别

我们在设置session时候通常进行request.session["user_id"] = user.pk类似的操作 
在python标准库中,对象利用字典式的添加属性,会调用__setitem__方法,获取操作调用__getitem__方法 
所以我们看下在SessionBase进行了如何操作? 
request.session["user_id"] 会触发如下操作

    def __getitem__(self, key):
        return self._session[key]

继而跟踪如下代码:

    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

当第一访问服务器时,self._session_cache是空的,所有会执行AttributeError异常代码,no_load默认是False,所以会执行异常代码块的如下部分,返回一个空字典

            if self.session_key is None or no_load:
                self._session_cache = {}

request.session["user_id"] = user.pk 赋值操作,会执行如下的代码:

 def __setitem__(self, key, value):
        self._session[key] = value
        self.modified = True

self._session看看做了什么操作呢?

_session = property(_get_session)

property调用会再次执行_get_session方法,这是标准库的内置函数,这里就不赘述了,看下_get_session代码

    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

这里我们看到将accessed 修改为True,标识获取了session,然后去_session_cache缓存中获取,如果缓存中不存在,那么就去self.load()加载,所以这次会获取到self._session_cache缓存值

以上就是process_request的执行流程,我们注意一点在process_request还没有保存到数据库中,只是在缓存级别进行操作

接下来会执行url路由,然后执行views视图,这里是中间件的执行流程,不在赘述,然后在执行process_response,我们来看下process_response代码进行了哪些操作?

    def process_response(self, request, response):
        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

我们看以上代码的最开始的3个属性值

accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()

accessedmodified 这2个值是由初始化的False,修改为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,我们之前的代码可以分析出self._session_cache为True,所以最后返回False

try里面如果没有异常,会执行else部分代码,由于emptyFalse,会执行如下代码


patch_vary_headers(response, ('Cookie',))

Vary 头部定义了缓存机制,在构建其缓存键值时应当将哪个请求头标考虑在内。 例如,如果网页的内容取决于用户的语言偏好,该页面被称为根据语言而不同。看如下代码

def patch_vary_headers(response, newheaders):
    if response.has_header('Vary'):
        vary_headers = cc_delim_re.split(response['Vary'])
    else:
        vary_headers = []
    # Use .lower() here so we treat headers as case-insensitive.
    existing_headers = set(header.lower() for header in vary_headers)
    additional_headers = [newheader for newheader in newheaders
                          if newheader.lower() not in existing_headers]
    response['Vary'] = ', '.join(vary_headers + additional_headers)

大概意思就是django中间件对response进行了修改,然后将Cookie添加进去,构建心的headers

接下来我们分析process_response其他的代码段

if request.session.get_expire_at_browser_close():
max_age = None
expires = None
 def get_expire_at_browser_close(self):
        """
        Returns ``True`` if the session is set to expire when the browser
        closes, and ``False`` if there's an expiry date. Use
        ``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry
        date/age, if there is one.
        """
        if self.get('_session_expiry') is None:
            return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
        return self.get('_session_expiry') == 0

在session设置中有如下几种情况

 # request.session.set_expiry(value)
        # * 如果value是个整数,session会在些秒数后失效。
        # * 如果value是个datatime或timedelta,session就会在这个时间后失效。
        # * 如果value是0,用户关闭浏览器session就会失效。
        # * 如果value是None,session会依赖全局session失效策略。

如果全局session失效,或者self.get('_session_expiry') == 0或者浏览器关闭,就将其更新为如下,让其session失效

max_age = None
expires = None

然后在接下来的代码中,获取设置的session时效

 max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = cookie_date(expires_time)

继续看如下的代码

 request.session.save()

save方法中很关键,这里才是进行数据库更新操作,在前面代码分析中,process_request中只是进行了缓存处理,在缓存级别,并没有进行数据库持久化

最后一步就是如下的代码,我们来看下

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

 

很简单就是写给浏览器set_cookie进行cookie写操作

以上就是简单流程,接下来我会继续分析session的重复更新操作、以及配合django下的auth认证模块来分析session中间件、以及数据库是如何进行持久化的

posted on 2018-04-08 21:14  Py行僧  阅读(297)  评论(0编辑  收藏  举报