zoukankan      html  css  js  c++  java
  • Flask-Cookies和Session

    cookies

    在Flask的框架中,自己已经封装了 cookie的respons,request 有存储就有读取及删除,那么就拿购物车来举例

      在我们登陆的时候会有之前在购物车存放的物品。也就是说在一个地方为我们保存了这些数据。前提有一个是要你登陆之后才能看到自己的购物车

      cookie对应的是client session对应的是server。 也就是说,要在服务器上登录你对应的账户,才能看到你自己在购物车添加的物品。但是

      物品那么多,不能都存在服务器上吧,所以一般cookie都存在自己的计算机上,只是找不到而已,这里就不说了。一些简单的原理实现一下。

    首先来看cookie的简单存储,读数据,删除数据怎么实现

    # cookie相关的操作,依赖于make_response库,调用cookie依赖request
    from flask import Flask, make_response, request
    
    
    app = Flask(__name__)
    
    app.config.from_pyfile('config.ini')
    
    # cookie存在client里。存在自己电脑的个人当资料里
    # 存cookie的方法
    @app.route('/setcookie')
    def set_cookie():
        resp = make_response('存储cookie')
        # 使用set方法,来存储 key-values 形式的数据
        resp.set_cookie('productname','卫生纸')
        return resp
    
    
    # 获取,取到数据,调用cookie的方法
    @app.route('/getcookie')
    def get_cookie():
        # 通过request模块的cookies属性的方法指定Key 来调用value
        resp = request.cookies.get('productname')
        return resp
    
    # 删除cookie的方法
    @app.route('/delcookie')
    def del_cookie():
        # 通过make_response对象内置的delete_cookie方法来指定key来删除vaule
        resp = make_response('删除cookie')
        resp.delete_cookie('productname')
        return resp
    
    
    
    if __name__ == "__main__":
    	app.run()
    

    session

    • session:存放在客户端的键值对
    • token:存放在客户端,通过算法来校验

    在使用session之前必须现在设置一下密钥

    app.secret_key="asdas" #值随便
    

    除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。 (app.session_interface对象)

    设置:session['username'] = 'xxx'
    # 在django中发什么三件事,
    #  	 1 生成一个随机的字符串 
    #    2 往数据库存 
    #    3 写入cookie返回浏览器
    
    # 在flask中他没有数据库,但session是怎样实现的?
    # 生成一个密钥写入这个cookie,然后下次请求的时候,通过这个cookie解密,然后赋值给session
    #我们通过app.session_interface来查看
      
    删除:session.pop('username', None)
    

    save_session的参数

    save_sessionapp.session_interface中的函数,他的参数如下:

    参数 作用
    key
    value
    max_age=None 超时时间 cookie需要延续的时间(以秒为单位)如果参数是 None`` ,这个cookie会延续到浏览器关闭为止
    expires=None 超时时间(IE requires expires, so set it if hasn't been already.)
    path='/' Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。
    domain=None Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=".example.com"所构造的cookie对下面这些站点都是可读的:
    www.example.comwww2.example.com an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取
    secure=False 浏览器将通过HTTPS来回传cookie
    httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

    session源码执行流程

    请求第一次过来时

    在flask请求上下文和应用上下文中已经知道session是一个LocalProxy()对象:

    current_app = LocalProxy(_find_app)
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    session = LocalProxy(partial(_lookup_req_object, 'session'))
    g = LocalProxy(partial(_lookup_app_object, 'g'))
    

    客户端的请求进来时,会调用app.wsgi_app():

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                # 寻找视图函数,并执行
                # 获取返回值 response
                response = self.full_dispatch_request()
    

    此时,会生成一个ctx,其本质是一个RequestContext对象:

    class RequestContext(object):
        def __init__(self, app, environ, request=None, session=None):
            self.app = app
            if request is None:
                request = app.request_class(environ)
            self.request = request
            self.url_adapter = None
            try:
                self.url_adapter = app.create_url_adapter(self.request)
            except HTTPException as e:
                self.request.routing_exception = e
            self.flashes = None
            self.session = session
    

    RequestContext 对象中定义了session,且初值为None。

    接着继续看wsgi_app函数中,ctx.push()函数:

    def push(self):
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)
    
        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
    
        # 把ctx对象加入到Local()对象中
        _request_ctx_stack.push(self)
    
        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(
                self.app, self.request
            )
    
            if self.session is None:
                self.session = session_interface.make_null_session(self.app)
    

    主要看后半部分代码。判断session是否为空,我在RequestContext 中看到session初值为空.

    在 Flask 中,所有和 session 有关的调用,都是转发到 self.session_interface 的方法调用上(这样用户就能用自定义的session_interface来控制 session 的使用)。而默认的 session_inerface 有默认值:

    session_interface = SecureCookieSessionInterface()
    

    执行SecureCookieSessionInterface.open_session()来生成默认session对象:

    def open_session(self, app, request):
        # 获取session签名的算法
        s = self.get_signing_serializer(app)
        # 如果为空 直接返回None
        if s is None:
            return None
        val = request.cookies.get(app.session_cookie_name)
        # 如果val为空,即request.cookies为空
        if not val:
            return self.session_class()
        max_age = total_seconds(app.permanent_session_lifetime)
        try:
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            return self.session_class()
    

    请求第一次来时,request.cookies为空,即返回self.session_class():

    session_class = SecureCookieSession
    

    SecureCookieSession

    class SecureCookieSession(CallbackDict, SessionMixin):
        modified = False
        accessed = False
        
        def __init__(self, initial=None):
            def on_update(self):
                self.modified = True
                self.accessed = True
    
            super(SecureCookieSession, self).__init__(initial, on_update)
    
        def __getitem__(self, key):
            self.accessed = True
            return super(SecureCookieSession, self).__getitem__(key)
    
        def get(self, key, default=None):
            self.accessed = True
            return super(SecureCookieSession, self).get(key, default)
    
        def setdefault(self, key, default=None):
            self.accessed = True
            return super(SecureCookieSession, self).setdefault(key, default)
    

    看其继承关系,其实就是一个特殊的字典。到此我们知道了session就是一个特殊的字典,调用SecureCookieSessionInterface类的open_session()创建,并保存在ctx中,即RequestContext对象中。但最终由session = LocalProxy(..., 'session')对象代为管理,到此,在视图函数中就可以导入session并使用了。

    请求第二次进来

    当请求第二次到来时,与第一次的不同就在open_session()那个val判断处,此时cookies不为空, 获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回中,若cookie已经失效,则仍然返回空字典

    SecureCookieSession

    默认的 session 对象是 SecureCookieSession,这个类就是一个基本的字典,外加一些特殊的属性,比如 permanent(flask 插件会用到这个变量)、modified(表明实例是否被更新过,如果更新过就要重新计算并设置 cookie,因为计算过程比较贵,所以如果对象没有被修改,就直接跳过)。

    怎么知道实例的数据被更新过呢?SecureCookieSession是基于 werkzeug/datastructures:CallbackDict 实现的,这个类可以指定一个函数作为 on_update 参数,每次有字典操作的时候(__setitem__、__delitem__、clear、popitem、update、pop、setdefault)会调用这个函数。

    SecureCookieSession:

    class SecureCookieSession(CallbackDict, SessionMixin):
     
        modified = False
        accessed = False
     
        def __init__(self, initial=None):
            def on_update(self):  
                self.modified = True
                self.accessed = True
            #将on_update()传递给CallbackDict
            super(SecureCookieSession, self).__init__(initial, on_update)
     
        def __getitem__(self, key):
            self.accessed = True
            return super(SecureCookieSession, self).__getitem__(key)
     
        def get(self, key, default=None):
            self.accessed = True
            return super(SecureCookieSession, self).get(key, default)
     
        def setdefault(self, key, default=None):
            self.accessed = True
            return super(SecureCookieSession, self).setdefault(key, default)
    

    继承的 CallbackDict:

    class CallbackDict(UpdateDictMixin, dict):
     
        def __init__(self, initial=None, on_update=None):
            dict.__init__(self, initial or ())
            self.on_update = on_update
     
        def __repr__(self):
            return '<%s %s>' % (
                self.__class__.__name__,
                dict.__repr__(self)
            )
    

    CallbackDict又继承UpdateDictMixin:

    class UpdateDictMixin(object):
        on_update = None
     
        def calls_update(name):
            def oncall(self, *args, **kw):
                rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw)
                if self.on_update is not None:
                    self.on_update(self)
                return rv
            oncall.__name__ = name
            return oncall
     
        def setdefault(self, key, default=None):
            modified = key not in self
            rv = super(UpdateDictMixin, self).setdefault(key, default)
            if modified and self.on_update is not None:
                self.on_update(self)
            return rv
     
        def pop(self, key, default=_missing):
            modified = key in self
            if default is _missing:
                rv = super(UpdateDictMixin, self).pop(key)
            else:
                rv = super(UpdateDictMixin, self).pop(key, default)
            if modified and self.on_update is not None:
                self.on_update(self)
            return rv
     
        __setitem__ = calls_update('__setitem__')
        __delitem__ = calls_update('__delitem__')
        clear = calls_update('clear')
        popitem = calls_update('popitem')
        update = calls_update('update')
        del calls_update
    

    UpdateDictMixin()可知,对session进行改动会调用pop, __setitem__等方法,同时就会调用on_update()方法,从而修改modify,security的值

    签名算法

    都获取 cookie 数据的过程中,最核心的几句话是:

    s = self.get_signing_serializer(app)
    val = request.cookies.get(app.session_cookie_name)
    data = s.loads(val, max_age=max_age)
    
    return self.session_class(data)
    

    其中两句都和 s 有关,signing_serializer 保证了 cookie 和 session 的转换过程中的安全问题。如果 flask 发现请求的 cookie 被篡改了,它会直接放弃使用。

    我们继续看 get_signing_serializer 方法:

    def get_signing_serializer(self, app):
        if not app.secret_key:
            return None
        signer_kwargs = dict(
            key_derivation=self.key_derivation,
            digest_method=self.digest_method
        )
        return URLSafeTimedSerializer(app.secret_key,
            salt=self.salt,
            serializer=self.serializer,
            signer_kwargs=signer_kwargs)
    

    我们看到这里需要用到很多参数:

    • secret_key:密钥。这个是必须的,如果没有配置 secret_key 就直接使用 session 会报错

    • salt:为了增强安全性而设置一个 salt 字符串(可以自行搜索“安全加盐”了解对应的原理)

    • serializer:序列算法

    • signer_kwargs:其他参数,包括摘要/hash算法(默认是 sha1)和 签名算法(默认是 hmac)

    URLSafeTimedSerializeritsdangerous库的类,主要用来进行数据验证,增加网络中数据的安全性。itsdangerours提供了多种 Serializer,可以方便地进行类似 json 处理的数据序列化和反序列的操作。

    session的生命周期

    前面的几个问题实际上都发生在wsgi_app()前两句函数中,主要就是ctx.push()函数中,下面看看wsgi_app()后面干了嘛:

    def wsgi_app(self, environ, start_response):
      
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                # ctx.push函数是前半部分最重要的一个函数
                # 生成request和session并将二者保存到RequestContext()对象ctxz中
                # 最后将ctx,push到LocalStack()对象_request_ctx_stack中
                ctx.push()
                # 寻找视图函数,并执行
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            # 最后, 将自己请求在local中的数据清除
            ctx.auto_pop(error)
    

    full_dispatch_request

    def full_dispatch_request(self):
        #执行before_first_request
        self.try_trigger_before_first_request_functions()
        try:
            # 触发request_started 信号
            request_started.send(self)
            # 调用before_request
            rv = self.preprocess_request()
            if rv is None:
                #执行视图函数
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)
    

    前半部分就在执行flask钩子,before_first_request, before_request以及信号,接着执行视图函数生成rv,

    我们主要看finalize_request(rv)

    def finalize_request(self, rv, from_error_handler=False):
            response = self.make_response(rv)
            try:
                response = self.process_response(response)
                request_finished.send(self, response=response)
            except Exception:
                if not from_error_handler:
                    raise
                self.logger.exception('Request finalizing failed with an '
                                      'error while handling an error')
            return response
    

    首先根据rv生成response。再执行process_response:

    def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
            
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
            for handler in funcs:
                response = handler(response)
                
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
            
        return response
    

    前半部分主要执行flask的钩子,看后面,判断,session是否为空,如果不为空,则执行save_session():

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
     
        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    app.session_cookie_name,
                    domain=domain,
                    path=path
                )
     
            return
     
        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add('Cookie')
     
        if not self.should_set_cookie(app, session):
            return
     
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))
        response.set_cookie(
            app.session_cookie_name,
            val,
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite
        )
    

    save_session()比较简单,且有注释,便不再讲解,主要就是将session写入response.set_cookie中。这样便完成session的写入response工作,并由response返回至客户端。

    再请求结束时会执行wsgi_app()finally:ctx.auto_pop(error)函数,将与对应请求相关的request,session清除,session生命周期便结束。

    总结

    至此,flask内置session的机制便讲解完毕,session的实现是依赖与flask的上下文管理,因此先弄清楚flask上下文,再来看session就比较容易理解。其主要的就是SecureCookieSessionInterface对象的open_session()save_session() open_session在请求刚进来时执行,完成session对象的创建(就是一特殊字典),在视图函数中完成对session的赋值操作,save_session()在视图函数执行完后,生成response后执行,将session写入response的cookie中。

  • 相关阅读:
    SharePoint 2013 商务智能报表发布
    sharepoint designer web 服务器似乎没有安装microsoft sharepoint foundation
    SharePoint 2013 Designer系列之数据视图
    SharePoint 2013 Designer系列之数据视图筛选
    SharePoint 2013 Designer系列之自定义列表表单
    SharePoint 2013 入门教程之创建及修改母版页
    SharePoint 2013 入门教程之创建页面布局及页面
    SharePoint 2010 级联下拉列表 (Cascading DropDownList)
    使用SharePoint Designer定制开发专家库系统实例!
    PL/SQL Developer 建立远程连接数据库的配置 和安装包+汉化包+注册机
  • 原文地址:https://www.cnblogs.com/Hades123/p/11773417.html
Copyright © 2011-2022 走看看