session 的 源码
1.session 和 cookie 的 区别:
答: cookie 它是保存在浏览器的键值对。
session 它是保存在服务端的键值对。 它 依赖cookie 存在。
流程:
一个服务端, 一个客户端第一次来
服务端 去 客户端 发来的请求里的cookie 里面找一个 随机字符串。
新用户没有,服务端就会为客户端创建一个随机字符串,发到它的cookie里,并且保存到服务端。
下次这个客户端又来了,那么这次服务端就可以获取到了随机字符串。
根据这个随机字符串看看你以前有没有在session里面放数据。(对比)
如果数据更新,是会先更新到内存里面,请求返回到时候,在会把内存的数据保存的session。
session
{
pc随机字符串: {},
}
当请求第一次来,生成随机字符串,发给用户的cookie,保存到session字段中。(其实就是调用stark(中介),将随机字符串和对应的值,放到local(房主)里)
以后去取的时候,top就行了。
请求处理完毕,就将session持久化,存到数据库,存到redis,存到加密cookie中。
2.分析session源码:
1.请求刚进来先走Flask类的__call__方法。
2.用到类和方法。
3. 细分:
0.app.run() ==> run_simple(host, port, self, **options) #第一: hold 住 对象过来就执行 __call__方法.
1.__call__ 跳入:
2.self.wsgi_app 跳入:
def wsgi_app(self, environ, start_response):
2.1 如何封装? push 跳入
3. push跳入之后
_request_ctx_stack跳入: request 和 session 都会放入local 中。
3.1 刚开始处理session
4.self.wsgi_app. 处理函数
full_dispatch_request()跳入:
5.full_dispatch_request
6.full_dispatch_request。对返回值进行封装,请求完了之后 after。
finalize_request跳入;
7.finalize_request
process_response跳入:
8.process_response
9. self.wsgi_app 里。
4.超级细分:
三部曲:
1. 上下文的处理: 他做的是: 把request,session 的请求相关信息放到local里。(赋值给RequestContext对象)
首先.
if __name__ == '__main__': app.run() app.__call__() #第一
ps: __call__ 跳入
def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`.""" return self.wsgi_app(environ, start_response) #第二:请求开始的地方,也是结束的地方
ps: wsgi_app 跳入
def wsgi_app(self, environ, start_response): #第三 ''' { "唯一标识":{'stack':[ResquestContext对象(request,session)]} } ''' #第四: 开始之前做的事情。(两件事:处理了request,session),将这两个数据封装到local中。 ctx = self.request_context(environ) #将request信息封装到Request(environ)对象并赋值给了 RequestContext对象 ctx.push() error = None try: try: response = self.full_dispatch_request() #第七:执行:before装饰器函数,视图函数:after装饰器函数 except Exception as e: #如果请求出现异常 error = e response = self.handle_exception(e) # 信号6: 第2/3/4/5/got_request_exception 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) # 结束了 #ctx是request-Context对象 信号7: 里面做了 第6信号:
ps: request_context 跳入
def request_context(self, environ): return RequestContext(self, environ)
ps: RequestContext 跳入
class RequestContext(object): def __init__(self, app, environ, request=None): self.app = app if request is None: #如果request 为空 request = app.request_class(environ) # app.request_class就是request对象,app就是flask对象 self.request = request self.url_adapter = app.create_url_adapter(self.request) self.flashes = None self.session = None # Request contexts can be pushed multiple times and interleaved with # other request contexts. Now only if the last level is popped we # get rid of them. Additionally if an application context is missing # one is created implicitly so for each level we add this information self._implicit_app_ctx_stack = [] # indicator if the context was preserved. Next time another context # is pushed the preserved context is popped. self.preserved = False # remembers the exception for pop if there is one in case the context # preservation kicks in. self._preserved_exc = None # Functions that should be executed after the request on the response # object. These will be called before the regular "after_request" # functions. self._after_request_functions = [] self.match_request() def _get_g(self): return _app_ctx_stack.top.g def _set_g(self, value): _app_ctx_stack.top.g = value g = property(_get_g, _set_g) del _get_g, _set_g def copy(self): """Creates a copy of this request context with the same request object. This can be used to move a request context to a different greenlet. Because the actual request object is the same this cannot be used to move a request context to a different thread unless access to the request object is locked. .. versionadded:: 0.10 """ return self.__class__(self.app, environ=self.request.environ, request=self.request ) def match_request(self): """Can be overridden by a subclass to hook into the matching of the request. """ try: url_rule, self.request.view_args = \ self.url_adapter.match(return_rule=True) self.request.url_rule = url_rule except HTTPException as e: self.request.routing_exception = e def push(self): """Binds the request context to the current context.""" # If an exception occurs in debug mode or if context preservation is # activated under exception situations exactly one context stays # on the stack. The rationale is that you want to access that # information under debug situations. However if someone forgets to # pop that context again we want to make sure that on the next push # it's invalidated, otherwise we run at risk that something leaks # memory. This is usually only a problem in test suite since this # functionality is not active in production environments. top = _request_ctx_stack.top # 取值 if top is not None and top.preserved: top.pop(top._preserved_exc) # Before we push the request context we have to ensure that there # is an application context. app_ctx = _app_ctx_stack.top if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() # 信号1: 把app放到 local里。 app_ctx.push() # 信号2:AppContext(self)的push (里面做了:触发执行 第1信号:appcontext_pushed) self._implicit_app_ctx_stack.append(app_ctx) # 信号3: 把上面app_ctx对象加进来,以后可以取 else: self._implicit_app_ctx_stack.append(None) if hasattr(sys, 'exc_clear'): sys.exc_clear() _request_ctx_stack.push(self) #第五:将requestContext对象(request,session)的引用,只要一修改,都修改了,添加到local中。 # Open the session at the moment that the request context is # available. This allows a custom open_session method to use the # request context (e.g. code that access database information # stored on `g` instead of the appcontext). #self.session_interface.open_session(self, request) = self.app.open_session(self.request) #return self.session_interface.open_session(self, request) #return SecureCookieSessionInterface().open_session(sekf,request) 经过一段处理,session就不是None了 self.session = self.app.open_session(self.request) #第六:刚开始处理session。self.app 表示:flask 对象 if self.session is None: # self 表示:requestContext对象.请求一开始进来,为空 self.session = self.app.make_null_session() # 走这里,最后还是指null_session_class = NullSession。 def pop(self, exc=_sentinel): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. .. versionchanged:: 0.9 Added the `exc` argument. """ app_ctx = self._implicit_app_ctx_stack.pop() try: clear_request = False if not self._implicit_app_ctx_stack: self.preserved = False self._preserved_exc = None if exc is _sentinel: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) # 信号7。2: 第6信号 request_tearing_down # If this interpreter supports clearing the exception information # we do that now. This will only go into effect on Python 2.x, # on 3.x it disappears automatically at the end of the exception # stack. if hasattr(sys, 'exc_clear'): sys.exc_clear() request_close = getattr(self.request, 'close', None) if request_close is not None: request_close() clear_request = True finally: rv = _request_ctx_stack.pop() # get rid of circular dependencies at the end of the request # so that we don't require the GC to be active. if clear_request: rv.request.environ['werkzeug.request'] = None # Get rid of the app as well if necessary. if app_ctx is not None: app_ctx.pop(exc) # 信号8:pop属Appcontxt 跳入: 它做了触发了 第7信号 appcontext_tearing_down assert rv is self, 'Popped wrong request context. ' \ '(%r instead of %r)' % (rv, self) def auto_pop(self, exc): if self.request.environ.get('flask._preserve_context') or \ (exc is not None and self.app.preserve_context_on_exception): self.preserved = True self._preserved_exc = exc else: self.pop(exc) #信号7.1 跳入 def __enter__(self): self.push() return self def __exit__(self, exc_type, exc_value, tb): # do not pop the request stack if we are in debug mode and an # exception happened. This will allow the debugger to still # access the request object in the interactive shell. Furthermore # the context can be force kept alive for the test client. # See flask.testing for how this works. self.auto_pop(exc_value) if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: reraise(exc_type, exc_value, tb) def __repr__(self): return '<%s \'%s\' [%s] of %s>' % ( self.__class__.__name__, self.request.url, self.request.method, self.app.name, )
ps: 这里,我们暂时先可以看下: session 和 request 一开进来都None。 接着,我们返回,从push 方法进入:
def wsgi_app(self, environ, start_response): #第三 ''' { "唯一标识":{'stack':[ResquestContext对象(request,session)]} } ''' #第四: 开始之前做的事情。(两件事:处理了request,session),将这两个数据封装到local中。 ctx = self.request_context(environ) #将request信息封装到Request(environ)对象并赋值给了 RequestContext对象 ctx.push()
ps: push 跳入,看看里面都执行了什么?
_request_ctx_stack = LocalStack()
ps: LocalStack() 跳入
def __init__(self): self._local = Local()
ps: Local () 跳入
执行setattr 方法,
def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value}
返回:
#self.session_interface.open_session(self, request) = self.app.open_session(self.request) #return self.session_interface.open_session(self, request) #return SecureCookieSessionInterface().open_session(sekf,request) 经过一段处理,session就不是None了 self.session = self.app.open_session(self.request) #第六:刚开始处理session。self.app 表示:flask 对象 if self.session is None: # self 表示:requestContext对象.请求一开始进来,为空 self.session = self.app.make_null_session() # 走这里,最后还是指null_session_class = NullSession。
ps: open_session 跳入
def open_session(self, request): return self.session_interface.open_session(self, request)
ps: session_interface 跳入
session_interface = SecureCookieSessionInterface()
ps: SecureCookieSessionInterface() 跳入
class SecureCookieSessionInterface(SessionInterface):
#: the salt that should be applied on top of the secret key for the
#: signing of cookie based sessions.
salt = 'cookie-session'
#: the hash function to use for the signature. The default is sha1
digest_method = staticmethod(hashlib.sha1)
#: the name of the itsdangerous supported key derivation. The default
#: is hmac.
key_derivation = 'hmac'
#: A python serializer for the payload. The default is a compact
#: JSON derived serializer with support for some extra Python types
#: such as datetime objects or tuples.
serializer = session_json_serializer
session_class = SecureCookieSession #其实,最后返回的还是个字典的形式
ps: SecureCookieSession跳入
class SecureCookieSession(CallbackDict, SessionMixin): """Base class for sessions based on signed cookies.""" def __init__(self, initial=None): def on_update(self): self.modified = True CallbackDict.__init__(self, initial, on_update) self.modified = False
ps: CallbackDict 跳入
class CallbackDict(UpdateDictMixin, dict): #dict 字典 """A dict that calls a function passed every time something is changed. The function is passed the dict instance. """
ps: 从这可以看出,它最后继承是 dict 字典。
返回:
return self.session_interface.open_session(self, request)
ps: 从open_session 进入
def open_session(self, app, request): s = self.get_signing_serializer(app) # 加密 if s is None: return None val = request.cookies.get(app.session_cookie_name) #request:请求的对象,去cookie中获取session作为key对应的值。 if not val: #没有 return self.session_class() #返回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()
ps: 进行一系列处理,最后的返回的 session_class 是以字典形式存的。
小结: 以上的流程就是上下文处理的过程。
简单的来说,上下文处理做的就是把请求的数据给封装到了 local 里。
2. 视图函数处理
首先:
def wsgi_app(self, environ, start_response): #第三 ''' { "唯一标识":{'stack':[ResquestContext对象(request,session)]} } ''' #第四: 开始之前做的事情。(两件事:处理了request,session),将这两个数据封装到local中。 ctx = self.request_context(environ) #将request信息封装到Request(environ)对象并赋值给了 RequestContext对象 ctx.push() error = None try: try: response = self.full_dispatch_request() #第七:执行:before装饰器函数,视图函数:after装饰器函数 except Exception as e: #如果请求出现异常 error = e response = self.handle_exception(e) # 信号6: 第2/3/4/5/got_request_exception 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) # 结束了 #ctx是request-Context对象 信号7: 里面做了 第6信号:
ps: full_dispatch_request 跳入
def full_dispatch_request(self): """Dispatches the request and on top of that performs request pre and postprocessing as well as HTTP exception catching and error handling. .. versionadded:: 0.7 """ self.try_trigger_before_first_request_functions() #第八: 执行@before_first_request 装饰的所有函数 try: request_started.send(self) #信号4: 请求刚进来执行,触发了 第2信号:request_started. rv = self.preprocess_request() #第九: 执行 @before_request 装饰的所有函数 if rv is None: rv = self.dispatch_request() except Exception as e: rv = self.handle_user_exception(e) return self.finalize_request(rv) #第十: 执行@after_request 装饰的所有函数:session保存
ps: finalize_request 跳入
def finalize_request(self, rv, from_error_handler=False): """Given the return value from a view function this finalizes the request by converting it into a response and invoking the postprocessing functions. This is invoked for both normal request dispatching as well as error handlers. Because this means that it might be called as a result of a failure a special safe mode is available which can be enabled with the `from_error_handler` flag. If enabled, failures in response processing will be logged and otherwise ignored. :internal: """ response = self.make_response(rv) try: response = self.process_response(response) #第11: 进入process_response request_finished.send(self, response=response) #信号5:第5信号 request_finished 执行之前,视图函数如果有模板语法,会先执行:第3,4,信号 : before_render_template template-rendered except Exception: if not from_error_handler: raise self.logger.exception('Request finalizing failed with an ' 'error while handling an error') return response
ps: process_session 跳入
def process_response(self, response): """Can be overridden in order to modify the response object before it's sent to the WSGI server. By default this will call all the :meth:`after_request` decorated functions. .. versionchanged:: 0.5 As of Flask 0.5 the functions registered for after request execution are called in reverse order of registration. :param response: a :attr:`response_class` object. :return: a new response object or the same, has to be an instance of :attr:`response_class`. """ 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: # 执行 @after_request 装饰的所有函数 response = handler(response) if not self.session_interface.is_null_session(ctx.session): #最后处理session self.save_session(ctx.session, response) return response
ps: save_session 跳入
def save_session(self, session, response): """Saves the session if it needs updates. For the default implementation, check :meth:`open_session`. Instead of overriding this method we recommend replacing the :class:`session_interface`. :param session: the session to be saved (a :class:`~werkzeug.contrib.securecookie.SecureCookie` object) :param response: an instance of :attr:`response_class` """ return self.session_interface.save_session(self, session, response)
ps: save_session 跳入
3. 保存,返回
def save_session(self, app, session, response): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) # Delete case. If there is no session we bail early. # If the session was modified to be empty we remove the # whole cookie. if not session: if session.modified: response.delete_cookie(app.session_cookie_name, domain=domain, path=path) return # Modification case. There are upsides and downsides to # emitting a set-cookie header each request. The behavior # is controlled by the :meth:`should_set_cookie` method # which performs a quick check to figure out if the cookie # should be set or not. This is controlled by the # SESSION_REFRESH_EACH_REQUEST config flag as well as # the permanent flag on the session itself. if not self.should_set_cookie(app, session): return httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) val = self.get_signing_serializer(app).dumps(dict(session)) #内存中的session,字典进行加密,也有序列化 response.set_cookie(app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure) #存到加密的cookie里。
ps: 最后 加密,存到cookie里 返回: 执行 finally
ctx.auto_pop(error) # 结束了 #ctx是request-Context对象 信号7: 里面做了 第6信号:
ps: auto_pop 跳入
def auto_pop(self, exc): if self.request.environ.get('flask._preserve_context') or \ (exc is not None and self.app.preserve_context_on_exception): self.preserved = True self._preserved_exc = exc else: self.pop(exc) #信号7.1 跳入
ps: self.pop(exc) 跳入
def pop(self, exc=_sentinel): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. .. versionchanged:: 0.9 Added the `exc` argument. """ app_ctx = self._implicit_app_ctx_stack.pop() try: clear_request = False if not self._implicit_app_ctx_stack: self.preserved = False self._preserved_exc = None if exc is _sentinel: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) # 信号7。2: 第6信号 request_tearing_down # If this interpreter supports clearing the exception information # we do that now. This will only go into effect on Python 2.x, # on 3.x it disappears automatically at the end of the exception # stack. if hasattr(sys, 'exc_clear'): sys.exc_clear() request_close = getattr(self.request, 'close', None) if request_close is not None: request_close() clear_request = True finally: rv = _request_ctx_stack.pop() #把ContentText对象 POP 出来
ps: 把ContentText对象 POP 出来
结束。