django中Cookie和Session

django中session源码分析

在学习django时,先通过设置request.session[“user_id”] = user.pk ,然后发送请求后 'request.session.get(‘user_id’)获取到用户id时产生了下面一个问题

在request请求模块里,默认是不带session功能的,也就是说request没有session属性的,为何我们还能使用呢?因为session功能是以中间件形式提供,由于在settings里配置了MIDDLEWARE_CLASSES这个变量,同时将模块’django.contrib.sessions.middleware.SessionMiddleware’添加到项目里,Django1.8以后的版本改名为MIDDLEWARE,所以我们才能通过request.session方式设置session。那么这个中间件对我们的request做了些什么呢?

一 、 request.session[“user_id”] = user.pk是如何执行的

在我们进入session中间件中,可以看到如下代码:

class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
    #所有 engine = import_module(settings.SESSION_ENGINE)获取到的engine 是一个db模块
	#接下来的代码self.SessionStore = engine.SessionStore是拿到了一个db模块下的SessionStore对象
	#该模块是对数据库的一些操作,后续我们在看这块的源码
        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)

#中间件首先从我们的请求request.COOKIES获取对应的cookie信息,这个信息最开始是从浏览器的cookie获取的,如果浏览器没有相应的cookie信息,则服务器会为这个浏览器创建一个cookie实例,本质上cookie对象就是一个继承了dict字典的对象,
接着从cookie对象取出session_key,也就是cookie为 sessionid的值,,然后拿着这个值到self.SessionStore进行实例化,

这个初始化方法我们在SessionBase中找到了,就是 self._session_key = session_key ,也就是self._session_key = ‘sessionid’

..\django\contrib\sessions\backends\base.py

class SessionBase:
    """
    Base class for all Session classes.
    """
    TEST_COOKIE_NAME = 'testcookie'
    TEST_COOKIE_VALUE = 'worked'

    __not_given = object()
#这里通过代码意思也能猜到是赋值session_key以及accessed是标识是否获取过session,
#以及modified 是否进行对session进行修改操作,serializer就是对session进行序列化,然后存入数据库级别
    def __init__(self, session_key=None):
        self._session_key = session_key
        self.accessed = False
        self.modified = False
        self.serializer = import_string(settings.SESSION_SERIALIZER)

图一:
图一
在python标准库中,对象利用字典式的添加属性,会调用__setitem__方法,获取操作调用__getitem__方法
当我们执行 request.session[“user_id”] 就会触发__getitem__

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

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

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

当第一访问服务器时,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  #表示修改了session

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

..\django\contrib\sessions\backends\db.py

class SessionStore(SessionBase):

       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 {}

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

处理完视图逻辑后进入下面:

二 、request.session.save()是如何保存到数据库的

接下来是process_response中的执行流程:

进入后首先定义了三个变量来接收request.session中的三个变量:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()

其中 accessed是标识是否获取过session,以及modified 是否进行对session进行修改操作。
接下来我们看is_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_key且session 是空时候为True,我们之前的代码可以分析出self._session_cache有值,所以最后返回False。
try里面如果没有异常,会执行else部分代码,由于empty为False,会执行如下代码
            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:
                #大概意思就是django中间件对response进行了修改,然后将Cookie添加进去,构建新的headers
                    patch_vary_headers(response, ('Cookie',))
                    ‘’‘
                    1、SESSION_SAVE_EVERY_REQUEST 
如果设置为True,django为每次request请求都保存session的内容,默认为False。
					2、如果修改了session
					3、有值(必须)
					‘’‘
                if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
                	#如果session失效,或者设置时间为0,也就是关闭浏览器就失效。
                    if request.session.get_expire_at_browser_close():
                        max_age = None
                        expires = None
                    else:
                    #否则,获取设置的session时效
                        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."
                            )
                           #设置cookie
                        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


我们可以看懂在process_response中有这么一句request.session.save(),这里才是将session保存到数据库中,

    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
注意:但是当我们第一次访问的时候是没有session_key的,但是会执行两边save()

也就是执行了这段代码,返回了一个session_key

if self.session_key is None:
            return self.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
我们在create中可以看到这段代码:self.save(must_create=True),它就会再次执行一遍上面的save()方法,但是这一次我们就已经产生了session_key了。
接下来:会到save()方法中,这次在判断self.session_key就不为None了,进而执行余下的代码1、data = self._get_session(no_load=must_create)。
    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

因为no_load=True,我们分析过了,在process_request中是进行了session缓存操作,如果有缓存的化,就直接返回缓存数据,在某种意外的前提下,或者缓存数据丢失,就执行self._session_cache = {}构建一个字典数据类型。因为我们进行request.session[“user_id”] = user.pk 操作后self._session_cache已经有了值,就会直接返回这个字典。

接下来:2、obj = self.create_model_instance(data)
  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(),
        )
self.model()
    def model(self):
        return self.get_model_class()
 @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

返回了一个Session模型类,这个Session就是django给我们创建的Session模型。

class Session(AbstractBaseSession):

    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'

我们进入AbstractBaseSession初始化方法中可以看到下面三个字段
 session_key=self._get_or_create_session_key(),
 session_data=self.encode(data),
 expire_date=self.get_expiry_date(),
class AbstractBaseSession(models.Model):
    session_key = models.CharField(_('session key'), max_length=40, primary_key=True)
    session_data = models.TextField(_('session data'))
    expire_date = models.DateTimeField(_('expire date'), db_index=True)
3、using = router.db_for_write(self.model, instance=obj)
db_for_write = _router_func('db_for_write')
 def _router_func(action):
        def _route_db(self, model, **hints):
            chosen_db = None
            for router in self.routers:
                try:
                    method = getattr(router, action)
                except AttributeError:
                    # If the router doesn't have a method, skip to the next one.
                    pass
                else:
                    chosen_db = method(model, **hints)
                    if chosen_db:
                        return chosen_db
            instance = hints.get('instance')
            if instance is not None and instance._state.db:
                return instance._state.db
            return DEFAULT_DB_ALIAS
        return _route_db

_router_func大概意思就是找到可以使用的db数据库,比如代码里面的settings.DATABASE_ROUTERS去项目下的settings.py配置文件去找。

try:
            with transaction.atomic(using=using):
                obj.save(force_insert=must_create, force_update=not must_create, using=using)

写入数据库。

三、 当写入浏览器cookie返回,下次浏览器就会带着sessionid键值对进行服务器访问,我们来看下在访问时候,是如何加载load数据的。
    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 {}
@cached_property
    def model(self):
        return self.get_model_class()  #在前面讲过

可以看到就是通过模型类从数据库中获取到值。

四、切换用户的问题

如果当我们在当前浏览器切换用户时会发生什么呢?
使用同一个浏览器,带来还是第一个用户的sessionid中的键值session_key
session_key依然从缓存拿(可以看前面第一步)requset.session[‘new_user_id’]= user.pk,我们着重看下
当我们请求时,session_key已经存在了.(具体登录的源码以后分析)

  def save(self, must_create=False):
        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

我们从缓存中获取的data = self._get_session(no_load=must_create)获取到data是新的数据{‘new_user_id’:user.pk}
因此data是新的用户,是对数据库进行的更新操作,session_key是不变的,session_data进行了更新。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值