from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form action="" method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' @app.route('/logout') def logout(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
使用第三方插件python
#!/usr/bin/env python # -*- coding:utf-8 -*- """ pip3 install redis pip3 install flask-session """ from flask import Flask, session, redirect from flask.ext.session import Session app = Flask(__name__) app.debug = True app.secret_key = 'asdfasdfasd' app.config['SESSION_TYPE'] = 'redis' from redis import Redis app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379') Session(app) @app.route('/login') def login(): session['username'] = 'alex' return redirect('/index') @app.route('/index') def index(): name = session['username'] return name if __name__ == '__main__': app.run()
1 請求到來的時候redis
在這裏會獲取請求數據中的session中的隨機字符串,若是存在則去Local中獲取用戶的數據,若是不存在則會建立一個對象容器(相似字典的對象)數據庫
2 執行視圖函數的時候json
若是對session中的數據進行修改,獲取Local中獲取這個對象,進行修改flask
3 返回響應的時候 cookie
把對象容器 (隨機字符串,{放置的數據}),中的數據保存到數據庫,隨機的字符串寫入cookie中session
請求到來時候app
def wsgi_app(self, environ, start_response): """The actual WSGI application. This is not implemented in :meth:`__call__` so that middlewares can be applied without losing a reference to the app object. Instead of doing this:: app = MyMiddleware(app) It's a better idea to do this instead:: app.wsgi_app = MyMiddleware(app.wsgi_app) Then you still have the original application object around and can continue to call methods on it. .. versionchanged:: 0.7 Teardown events for the request and app contexts are called even if an unhandled error occurs. Other events may not be called depending on when an error occurs during dispatch. See :ref:`callbacks-and-errors`. :param environ: A WSGI environment. :param start_response: A callable accepting a status code, a list of headers, and an optional exception context to start the response. """ # 將請求相關的數據environ 封裝到request_context 對象中 # ctx.app = app # ctx.request = app.request_class(environ) ctx = self.request_context(environ) # 生成一個類 error = None try: try: # 把請求的對象封裝到local中,每一個線程 / 協程都是獨立的空間存儲 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)
def request_context(self, environ): """Create a :class:`~flask.ctx.RequestContext` representing a WSGI environment. Use a ``with`` block to push the context, which will make :data:`request` point at this request. See :doc:`/reqcontext`. Typically you should not call this from your own code. A request context is automatically pushed by the :meth:`wsgi_app` when handling a request. Use :meth:`test_request_context` to create an environment and context instead of this method. :param environ: a WSGI environment """ return RequestContext(self, environ)
class RequestContext(object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it. It will create the URL adapter and request object for the WSGI environment provided. Do not attempt to use this class directly, instead use :meth:`~flask.Flask.test_request_context` and :meth:`~flask.Flask.request_context` to create this object. When the request context is popped, it will evaluate all the functions registered on the application for teardown execution (:meth:`~flask.Flask.teardown_request`). The request context is automatically popped at the end of the request for you. In debug mode the request context is kept around if exceptions happen so that interactive debuggers have a chance to introspect the data. With 0.4 this can also be forced for requests that did not fail and outside of ``DEBUG`` mode. By setting ``'flask._preserve_context'`` to ``True`` on the WSGI environment the context will not pop itself at the end of the request. This is used by the :meth:`~flask.Flask.test_client` for example to implement the deferred cleanup functionality. You might find this helpful for unittests where you need the information from the context local around for a little longer. Make sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in that situation, otherwise your unittests will leak memory. """ def __init__(self, app, environ, request=None): self.app = app if request is None: request = app.request_class(environ) self.request = request self.url_adapter = app.create_url_adapter(self.request) self.flashes = None self.session = None
在上面的源碼中咱們能夠看到默認的會把self.session設置爲Nonedom
繼續追蹤wsgi_app源碼中的ctx.pushide
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 = AppContext(object) app_ctx.g app_ctx.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() # self 是request_contenx的對象,其中包含了請求相關的全部數據 # _request_ctx_stack==>LocalStack _request_ctx_stack.push(self) # Open the session at the moment that the request context is available. # This allows a custom open_session method to use the request context. # Only open a new session if this is the first time the request was # pushed, otherwise stream_with_context loses the session. # 幫助咱們獲取session的相關信息 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)
追蹤中的_request_ctx_stack.push(self)
_request_ctx_stack = LocalStack()
查看其中的push方法
def push(self, obj): """Pushes a new item to the stack""" rv = getattr(self._local, 'stack', None) if rv is None: # 執行local 對象的__setatr__方法 self._local.stack = rv = [] # 把requestContext 對象添加到列表中 self._local.stack = rv = [把requestContext] rv.append(obj) return rv
到這裏爲每一個線程開闢了一個獨立的存儲空間,存儲請求相關的數據(Request_content)
storage = { "stack" : {"線程的惟一表示":[ Request_content封裝後的請求 ]} }
繼續追蹤上面ctx.push中的 self.app.session_interface
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_interface = SecureCookieSessionInterface()
繼續追蹤ctx.push中的session_interface.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) 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()
session_class = SecureCookieSession
class SecureCookieSession(CallbackDict, SessionMixin):
class CallbackDict(UpdateDictMixin, dict):
到這裏咱們能夠看到其返回的是一個相似於字典的一個對象
當咱們在視圖中操做session的時候
咱們獲取session相關的數據能夠直接從全局變量session中獲取
session = LocalProxy(partial(_lookup_req_object, 'session'))
partial(_lookup_req_object, 'session')
def _lookup_req_object(name): top = _request_ctx_stack.top if top is None: raise RuntimeError(_request_ctx_err_msg) # 去requestContext中獲取request的值 return getattr(top, name)
最終返回的結果是會去一開始咱們當前線程開闢的存儲空間中獲取session(類字典對象的一個容器)
當咱們對session進行操做是會把最終的數據存儲到這個容器中
在返回響應的時候
追蹤wsgi_app中的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() 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)
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) 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
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: response = handler(response) if not self.session_interface.is_null_session(ctx.session): self.session_interface.save_session(self, ctx.session, response) return response
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 )
咱們能夠在最後的時候把隨機的字符串放在了cookie中 { 「session 」 :’隨機的字符串 }
response.set_cookie( app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure, samesite=samesite )
自定義session
經過上面的源碼分析咱們知道,咱們要自定義一個session須要寫一個類,咱們經過配置使他走咱們自定義的類,裏面要實現opension和savesession的方法
from flask import Flask,session app = Flask(__name__) app.secret_key = 'suijksdfsd' import json class MySessionInterFace(object): def open_session(self,app,request): return {} def save_session(self, app, session, response): response.set_cookie('session_idfsdfsdfsdf',json.dumps(session)) def is_null_session(self, obj): """Checks if a given object is a null session. Null sessions are not asked to be saved. This checks if the object is an instance of :attr:`null_session_class` by default. """ return False app.session_interface = MySessionInterFace() @app.route('/') def index(): # 特殊空字典 # 在local的ctx中找到session # 在空字典中寫值 # 在空字典中獲取值 session['xxx'] = 123 return 'Index' # # 一旦請求到來 # app.__call__ app.wsgi_app app.session_interface app.open_session if __name__ == '__main__': app.run()