首先请求进来先执行

  app.run(debug=True)

进入run方法中

from werkzeug.serving import run_simple
        if host is None:
            host = '127.0.0.1'
        if port is None:
            server_name = self.config['SERVER_NAME']
            if server_name and ':' in server_name:
                port = int(server_name.rsplit(':', 1)[1])
            else:
                port = 5000
        if debug is not None:
            self.debug = bool(debug)
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        try:
            run_simple(host, port, self, **options)
        finally:
            # reset the first request information if the development server
            # reset normally.  This makes it possible to restart the server
            # without reloader and that stuff from an interactive shell.
            self._got_first_request = False
View Code

它会执行run_simple() 方法,我们知道run_simple函数会对self加括号执行,那么self是谁呢,就是Flask的实例化对象app,给对象加(),相当于执行对象的__call__方法,我们只能从Flask的类中找__call__

    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

我们可以看到他把environ和start_response传到wsgi_app()中,我们进入到wsgi_app()方法中

def wsgi_app(self, environ, start_response):
        
        ctx = self.request_context(environ) #封装了request和session,并把environ处理成了request,session=None,其实这里还有路由匹配
        ctx.push()#1.把ctx放到local对象中保存起来,给self.session赋值 2.app_ctx封装了app和g,并把app_ctx放到另个一个local对象中去
        error = None
        try:
            try:
                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
            ctx.auto_pop(error)

wsgi_app

 

我们慢慢的分析该函数的代码,先看第一句

ctx = self.request_context(environ)

进入到request_context中

 def request_context(self, environ):
   
        return RequestContext(self, environ) #这里的self是app

我们可以看到该函数返回一个实例化对象并把request,和session传进去了,这里我们要分清self就是app,

也就是说ctx就是RequestContex实例化的对象,注意ctc中还有我们的app对象,也就是说ctc可以调出app下的方法

我们进去看一下,我们只看该类的init方法

  def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ) #把environ处理成request
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

我们可以看到,该类实例化方法把environ加工处理封装给了request对象了.其实request是Request的一个对象,这个类下含有好多的方法.

注意ctx中还有我们的app对象,也就是说ctc可以调出app下的方法,还有一点我们要注意到,

还有:self.session=None

我们接着来看wsgi_app中的第二句代码

 ctx.push()

我们进去push方法

def push(self):
        
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

    
        app_ctx = _app_ctx_stack.top     #2.3去localStack中的local对象中拿app_ctx,主意这里的LocalStack和_request_ctx_stack中的LocalStack不是一个对象
        if app_ctx is None or app_ctx.app != self.app:#2.4如果localStack中没有当前的app_ctx
            app_ctx = self.app.app_context()#2.5 就自己创建一个app_ctx,其中封装了app和g
            app_ctx.push()#2.6 #把新创建的app_ctx放到local对象中
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
    
        _request_ctx_stack.push(self)#这里就是把ctx对象放入到local中   #2.1

        self.session = self.app.open_session(self.request) #给self.session赋值 #2.2 
        if self.session is None:
            self.session = self.app.make_null_session()

push方法

我们来看2.1关键代码

   _request_ctx_stack.push(self)

先进入_request_ctx_stack中我们看到,但是注意这里的self是ctx

_request_ctx_stack = LocalStack() #

原来是一个实例化对象,我们继续进入Localstack中()

我们要注意这句,类里面又实例化了一个对象

    def __init__(self):
        self._local = Local()

进入Local类中我们看到

class Local(object):
    __slots__ = ('__storage__', '__ident_func__') #限制类的属性只能有这俩

    def __init__(self):
        object.__setattr__(self, '__storage__', {}) #生成一个字典
        object.__setattr__(self, '__ident_func__', get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
local类

可以看到这和我们写的local对象中第三种代码一样,这里就是用来存放每个线程中的ctx的

下面我们来看Localstack对象中的push方法

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None) #self._local就是local对象,查看对象中有没有stack属性
        if rv is None:#如果没有就会调用__setattr__方法#2.1.1
            self._local.stack = rv = []
        rv.append(obj)#把ctx放入到列表中
        return rv 

这里我们来看这句代码

        if rv is None:
            self._local.stack = rv = []

其实就是执行__setattr__方法

    def __setattr__(self, name, value):
        ident = self.__ident_func__() #唯一标识
        storage = self.__storage__ #storage={}
        try:
            storage[ident][name] = value #不会走这里,因为第一次进来没有ident更没有name,会出现keyValue
        except KeyError:
            storage[ident] = {name: value} # 会走这里

上把您的结果:

storage={ident:{“stack”:[ctx ]}}
_request_ctx_stack.push的主要作用就是把ctc实例对象放到local对象的变量字典中

我们看2.2关键代码 给session赋值

        self.session = self.app.open_session(self.request) #Flask类中的open_session

我们先进入到open_session中

    def open_session(self, request):
 
        return self.session_interface.open_session(self, request)

继续进入

    session_interface = SecureCookieSessionInterface()

继续进入

 def open_session(self, app, request):
        s = self.get_signing_serializer(app)#这里直接判断有没有设置screct_key
        if s is None:
            return None
        val = request.cookies.get(app.session_cookie_name)
        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()

我们直接看这句代码,主要是先看有没有设置secret_key: 如果没有直接返回None,所以必须设置secret_key的值

 s = self.get_signing_serializer(app)

 我们接着往下看

 val = request.cookies.get(app.session_cookie_name)
        if not val:
            return self.session_class()

我们知道第一次进来cookie中肯定没值 ,所以val=None,我们就直接进入到session_class中

session_class = SecureCookieSession

竟然是实例化了一个对象,

class SecureCookieSession(CallbackDict, SessionMixin):

class CallbackDict(UpdateDictMixin, dict):

我们继续进入就可以了解到这个对象是一个类似字典的一个类型

也就是如果是请求第一次进入的话self.session=实例化了一个类似字典的一个对象,

但是当你第二次进来的时候这个字典里就有我们设置的值了.

如果你没有设置secret_key,nameself.session=None就会执行push方法中的这个代码

 if self.session is None:
            self.session = self.app.make_null_session() #如果没有设置sesret_kek就会抛出错误

我们接着来看wsgi_app中的第三句代码

   response = self.full_dispatch_request() 

该函数主要是用来执行视图函数的.

进入该函数

    def full_dispatch_request(self):

        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            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)  #

我们来看最重要的self.disptch_request

 def dispatch_request(self):
      
        req = _request_ctx_stack.top.request# 从local对象中取出ctx中的request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule #
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if getattr(rule, 'provide_automatic_options', False) \
           and req.method == 'OPTIONS':#
            return self.make_default_options_response()#
        # otherwise dispatch to the handler for that endpoint
        return self.view_functions[rule.endpoint](**req.view_args)#
req = _request_ctx_stack.top.request# 从local对象中取出ctx中的request,
我们进入到 req.url_rule 中,

看不懂了

 

 

---------------------------------------------------------------------------------------在视图函数中导入reques和导入session-----------------------------------------------------------------------

在视图函数中导入request时

from Flask import request

这句话经历了哪些源码

request = LocalProxy(partial(_lookup_req_object, 'request'))

其中partial是偏函数作用于__lookup_req_object函数上减少其参数的输入,这里就代表着__lookup_req_object,函数不用传参数,request参数已经传好了,上边这句话的代码等价于

request = LocalProxy(_lookup_req_object)#把该函数的函数名当参数传递给LocalProxy类

进入LocalProxy类中,

    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)#这里相当于self.__local=local   #local就是__lookup_req_obj函数名,至于为什么不是self.__LocalProxy__local ,因为
self.__local是私有属性,我们在外部不能调,但是如果真的想调用的话,这样调self.__类名__local就可以,因为这里要用到object中的私有属性
object.__setattr__(self, '__name__', name)#self.__name__=name

我们先只看这些代码,讲的是LocalProxy的初始化

当我们在视图函数中写入

m=request.method

经历了哪些源码,首先触发了LocalProxy中的__getattr__

 def __getattr__(self, name):#name就是method
        if name == '__members__': #判断是不是request.__members
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

我们进入到self.__get_current_object中

    def _get_current_object(self):
        
        if not hasattr(self.__local, '__release_local__'):
            return self.__local() #这里返回___lookup_req_object函数的执行结果,其中request的参数已经省略
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError('no object bound to %s' % self.__name__)

我们进入到_lookup_req_object函数中,

def _lookup_req_object(name):
    top = _request_ctx_stack.top #name=request 返回ctxraise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name) #等价于:getattr(ctx,'request')

其中

top = _request_ctx_stack.top #其中_request_ctx_stack = LocalStack()

会调用

Localstack类中的top方法

 def top(self):
        try:
            return self._local.stack[-1] #其中__local=local
        except (AttributeError, IndexError):
            return None

其中

 return self._local.stack[-1]

回调用local中的__getattr__方法

 def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name] #name=stark
        except KeyError:
            raise AttributeError(name)

该函数返回存储在local中该线程的ctx对象

也就是

top = _request_ctx_stack.top  #top等于ctx

那么:

getattr(top, name) #即 getattr(ctx, 'request') 结果就是request对象啊

即 _lookup_req_object函数 返回的就是request的对象,

则self.__get_current_object函数返回的结果就是request对象

那么刚开始的LocalProxy中的__getattr__中

getattr(self._get_current_object(), name)

等于

getattr(request, 'method')

这样request.method中的东西也就取出来.

在视图函数中导入session和导入request的原理差不多,就不在写

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------   

 Flask_session源码

from flask_session import Session
app = Flask(__name__)
Session(app)

上边是我简单写的:

进入到Session类中

 def __init__(self, app=None):
        self.app = app
        if app is not None:
            self.init_app(app) #1.1

进入 init_app中

 def init_app(self, app):
        """This is used to set up session for your app object.

        :param app: the Flask app object with proper configuration.
        """
        app.session_interface = self._get_interface(app)

继续进入 self._get_interface(app)

    def _get_interface(self, app):
        config = app.config.copy() #读取我们的配置文件
        config.setdefault('SESSION_TYPE', 'null')
        config.setdefault('SESSION_PERMANENT', True)
        config.setdefault('SESSION_USE_SIGNER', False)
        config.setdefault('SESSION_KEY_PREFIX', 'session:') #默认前缀
        config.setdefault('SESSION_REDIS', None)
        config.setdefault('SESSION_MEMCACHED', None)
        config.setdefault('SESSION_FILE_DIR',
                          os.path.join(os.getcwd(), 'flask_session'))
        config.setdefault('SESSION_FILE_THRESHOLD', 500)
        config.setdefault('SESSION_FILE_MODE', 384)
        config.setdefault('SESSION_MONGODB', None)
        config.setdefault('SESSION_MONGODB_DB', 'flask_session')
        config.setdefault('SESSION_MONGODB_COLLECT', 'sessions')
        config.setdefault('SESSION_SQLALCHEMY', None)
        config.setdefault('SESSION_SQLALCHEMY_TABLE', 'sessions')

        if config['SESSION_TYPE'] == 'redis':
            session_interface = RedisSessionInterface(
                config['SESSION_REDIS'], config['SESSION_KEY_PREFIX'],
                config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'memcached':
            session_interface = MemcachedSessionInterface(
                config['SESSION_MEMCACHED'], config['SESSION_KEY_PREFIX'],
                config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'filesystem':
            session_interface = FileSystemSessionInterface(
                config['SESSION_FILE_DIR'], config['SESSION_FILE_THRESHOLD'],
                config['SESSION_FILE_MODE'], config['SESSION_KEY_PREFIX'],
                config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'mongodb':
            session_interface = MongoDBSessionInterface(
                config['SESSION_MONGODB'], config['SESSION_MONGODB_DB'],
                config['SESSION_MONGODB_COLLECT'],
                config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'],
                config['SESSION_PERMANENT'])
        elif config['SESSION_TYPE'] == 'sqlalchemy':
            session_interface = SqlAlchemySessionInterface(
                app, config['SESSION_SQLALCHEMY'],
                config['SESSION_SQLALCHEMY_TABLE'],
                config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'],
                config['SESSION_PERMANENT'])
        else:
            session_interface = NullSessionInterface()

        return session_interface

上边就是可以配置的所有类型的数据库,

我们来看redis的

        if config['SESSION_TYPE'] == 'redis':
            session_interface = RedisSessionInterface(
                config['SESSION_REDIS'], config['SESSION_KEY_PREFIX'],
                config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])

进入到 RedisSessionInterface类中

我们来看其中的open_session

  def open_session(self, app, request):
        sid = request.cookies.get(app.session_cookie_name)#从cooke中拿sid
        if not sid:#第一次登陆
            sid = self._generate_sid() #1.1生成sid
            return self.session_class(sid=sid, permanent=self.permanent)#1.2 
        if self.use_signer:#如果有签名
            signer = self._get_signer(app)
            if signer is None:
                return None
            try:
                sid_as_bytes = signer.unsign(sid)
                sid = sid_as_bytes.decode()
            except BadSignature:
                sid = self._generate_sid()
                return self.session_class(sid=sid, permanent=self.permanent)

        if not PY2 and not isinstance(sid, text_type):#
            sid = sid.decode('utf-8', 'strict')
        val = self.redis.get(self.key_prefix + sid)#1.3从redis中根据key_prefix+sid拿数据
        if val is not None:
            try:
                data = self.serializer.loads(val)  #1.4用到了pickle的反序列haul
                return self.session_class(data, sid=sid)#1.5#把data和数据传给了RedisSession对象
            except:
                return self.session_class(sid=sid, permanent=self.permanent)
        return self.session_class(sid=sid, permanent=self.permanent)#

第一次登陆没有值:

#1.1 sid=_generate_sid()

    def _generate_sid(self):
        return str(uuid4())

返回一个uuid4的字符串

#1.2self.session_class(sid=sid, permanent=self.permanent)#

进入得到:

返回一个类

session_class = RedisSession

进入:RedisSession类中

   def __init__(self, initial=None, sid=None, permanent=None):
        def on_update(self):
            self.modified = True
        CallbackDict.__init__(self, initial, on_update)
        self.sid = sid
        if permanent:
            self.permanent = permanent
        self.modified = False

总结一点把sid 封装到了RedisSession类中

#1.3  val = self.redis.get(self.key_prefix + sid)#从redis中根据key_prefix+sid拿数据

如果redis中有值:

先把数据用pickle序列化,再把数据和sid传给RedisSession类.

----------------------------------------------------------------------------------

我们在看是如何保存把session保存到redis中和cookie中的

我们进入到Save_session中:

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app) #取cookie域名
        path = self.get_cookie_path(app) #取cookie地址
        if not session: #如果session为空就把redis中的session删除
            if session.modified:
                self.redis.delete(self.key_prefix + session.sid)
                response.delete_cookie(app.session_cookie_name,
                                       domain=domain, path=path)
            return

        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session) #过期时间
        val = self.serializer.dumps(dict(session)) #用pickle序列化session
        self.redis.setex(name=self.key_prefix + session.sid, value=val,
                         time=total_seconds(app.permanent_session_lifetime)) #2.1 把session存到redis中
        if self.use_signer:#如果有签名
            session_id = self._get_signer(app).sign(want_bytes(session.sid))
        else: #如没有签名
            session_id = session.sid
        response.set_cookie(app.session_cookie_name, session_id,
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)#把session存到cookie中

我挑主要的说啊:

  1. 该函数把session的存到redis中以key_prefix当键,以val为值.
      1. {key_prefix+sid:val}

         

  2. 该函数以session_cookie_name为键以sid为值存到了cookie中
Set-Cookie: session=0ac14b94-c60f-4096-915c-bff86503675d; HttpOnly; Path=/

 

posted on 2018-11-07 13:31  程序员一学徒  阅读(272)  评论(0编辑  收藏  举报