這一波主要是經過看源碼加深對 Flask 中路由和視圖的瞭解,能夠先回顧一下裝飾器的知識:【裝飾器函數與進階】html
# 示例代碼 from flask import Flask app = Flask(__name__) @app.route('/index') def index(): return 'index' if __name__ == '__main__': app.run()
直接看上面代碼,在 index 方法上經過裝飾器 @app.route('/index') 就創建路由 '/index' 和方法 index 的對應關係。python
查看 app.route 的源碼:正則表達式
1 def route(self, rule, **options): 2 def decorator(f): 3 endpoint = options.pop('endpoint', None) 4 self.add_url_rule(rule, endpoint, f, **options) 5 return f 6 7 return decorator
在上述示例中, rule 就是咱們自定義的路由參數 '/index' ; endpoint 就是終結點參數(用來反向生成 url),這裏咱們沒傳;而 f 實際上就是該裝飾器所裝飾的函數,在這裏也就是 index 函數。其實到這裏就能夠判定,該裝飾器實際上就是經過第 4 行的 add_url_rule 函數來創建路由和視圖的對應關係。咱們能夠測試:redis
from flask import Flask app = Flask(__name__) def index(): return 'index' app.add_url_rule('/index', view_func=index) if __name__ == '__main__': app.run()
在這裏我去掉了 index 函數的裝飾器,而直接經過 app.add_url_rule 函數創建路由 '/index' 和 index 函數的對應關係,正常訪問。sql
接着看 app.add_url_rule 函數作了什麼:mongodb
1 @setupmethod 2 def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): 3 if endpoint is None: 4 endpoint = _endpoint_from_view_func(view_func) 5 options['endpoint'] = endpoint 6 methods = options.pop('methods', None) 7 8 if methods is None: 9 methods = getattr(view_func, 'methods', None) or ('GET',) 10 if isinstance(methods, string_types): 11 raise TypeError('Allowed methods have to be iterables of strings, ' 12 'for example: @app.route(..., methods=["POST"])') 13 methods = set(item.upper() for item in methods) 14 15 required_methods = set(getattr(view_func, 'required_methods', ())) 16 17 if provide_automatic_options is None: 18 provide_automatic_options = getattr(view_func, 19 'provide_automatic_options', None) 20 21 if provide_automatic_options is None: 22 if 'OPTIONS' not in methods: 23 provide_automatic_options = True 24 required_methods.add('OPTIONS') 25 else: 26 provide_automatic_options = False 27 28 methods |= required_methods 29 30 rule = self.url_rule_class(rule, methods=methods, **options) 31 rule.provide_automatic_options = provide_automatic_options 32 33 self.url_map.add(rule) 34 if view_func is not None: 35 old_func = self.view_functions.get(endpoint) 36 if old_func is not None and old_func != view_func: 37 raise AssertionError('View function mapping is overwriting an ' 38 'existing endpoint function: %s' % endpoint) 39 self.view_functions[endpoint] = view_func
看 三、四、5 行,在示例中咱們並無傳入 endpoint 參數,因此 endpoint 在第 3 行確定是 None。接着執行第 4 行,查看 _endpoint_from_view_func 方法:flask
1 def _endpoint_from_view_func(view_func): 2 assert view_func is not None, 'expected view func if endpoint ' \ 3 'is not provided.' 4 return view_func.__name__
看第 4 行的返回值是視圖函數的函數名稱,因此當不傳 endpoint 參數時, endpoint 的值就是視圖函數的函數名稱。cookie
繼續看 flask.app.Flask.add_url_rule 函數的 34-39 行, 39 行作的就是每次裝飾器執行時,就會把裝飾器當前裝飾的函數當作值, endpoint 當作 key ,放入 view_functions 這個字典中。而從 35-38 行能夠看到,若是一個新的視圖函數的 endpoint 已經存在 view_functions 中,但這個函數又與 endpoint 以前對應的視圖函數不是同一個函數,就會產生 37 行錯誤。因此咱們要保證每一個視圖函數對應的 endpoint 不重複。session
除了咱們已經熟悉的 rule 和 view_func ,它還可傳以下參數:app
默認值,當URL中無參數,但函數須要參數時,能夠使用 defaults={'k':'v'} 爲函數提供參數。
名稱,用於反向生成 URL,即: url_for('endpoint') 。
容許的請求方式,如: methods=["GET","POST"] 。
對 URL 最後的 '/' 符號是否嚴格要求。
@app.route('/index',strict_slashes=False) # 訪問 http://www.xx.com/index/ 或 http://www.xx.com/index都可 @app.route('/index',strict_slashes=True) # 僅訪問 http://www.xx.com/index
重定向到指定地址。
@app.route('/index/<int:nid>', redirect_to='/home/<nid>') def index(): return 'index'
子域名訪問。
from flask import Flask app = Flask(import_name=__name__) app.config['SERVER_NAME'] = 'zze.com:5000' @app.route("/", subdomain="admin") # admin.zze.com:5000 def admin_index(): return "admin" @app.route("/", subdomain="guest") # guest.zze.com:5000 def guest_index(): return "guest" @app.route("/dynamic", subdomain="<username>") # http://test.zze.com:5000/dynamic def dynamic_index(username): return username if __name__ == '__main__': app.run()
from flask import Flask, views app = Flask(__name__) class TestView(views.MethodView): methods = ['GET'] # 只支持 GET 請求 decorators = [] # 批量加上裝飾器 def get(self, *args, **kwargs): return 'GET' def post(self, *args, **kwargs): return 'POST' app.add_url_rule('/test', None, TestView.as_view('test')) # as_view 的參數就是 endpoint if __name__ == '__main__': app.run()
它的實現其實和 Django 中的 CBV 實現很類似,源碼就不細說了。
from flask import Flask, views, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) # 自定製類 class RegexConverter(BaseConverter): """ 自定義URL匹配正則表達式 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """ 路由匹配時,匹配成功後傳遞給視圖函數中參數的值 :param value: :return: """ return int(value) def to_url(self, value): """ 使用url_for反向生成URL時,傳遞的參數通過該方法處理,返回的值用於生成URL中的參數 :param value: :return: """ val = super(RegexConverter, self).to_url(value) return val # 註冊到 flask 的轉換器中 app.url_map.converters['regex'] = RegexConverter # 使用 @app.route('/index/<regex("\d+"):nid>') def index(nid): print(url_for('index', nid='888')) return 'Index' if __name__ == '__main__': app.run()
首先咱們要知道 Flask 初執行是會通過 flask.app.Flask.__call__ 方法的,能夠參考【Flask 的入口】。
def __call__(self, environ, start_response): # environ :請求相關全部數據 # start_response :用於設置響應相關數據 return self.wsgi_app(environ, start_response)
再查看 wsgi_app 方法:
1 def wsgi_app(self, environ, start_response): 2 ''' 3 獲取environ並對其進行封裝 4 從environ中獲取名爲session的cookie,解密並反序列化 5 放入請求上下文 6 ''' 7 ctx = self.request_context(environ) 8 error = None 9 try: 10 try: 11 ctx.push() 12 ''' 13 執行視圖函數 14 ''' 15 response = self.full_dispatch_request() 16 except Exception as e: 17 error = e 18 response = self.handle_exception(e) 19 except: 20 error = sys.exc_info()[1] 21 raise 22 return response(environ, start_response) 23 finally: 24 if self.should_ignore_error(error): 25 error = None 26 ''' 27 獲取session,解密並序列化,寫入cookie 28 清空請求上下文 29 ''' 30 ctx.auto_pop(error)
environ 是請求相關信息,第 7 行將 environ 傳入 request_context 方法,看一下:
1 def request_context(self, environ): 2 return RequestContext(self, environ)
能夠看到它的返回值就是以 environ 爲構造參數傳入 RequestContext 類中的一個實例,看一下它初始化時作了什麼:
1 def __init__(self, app, environ, request=None): 2 self.app = app 3 if request is None: 4 request = app.request_class(environ) 5 self.request = request 6 self.url_adapter = app.create_url_adapter(self.request) 7 self.flashes = None 8 self.session = None 9 10 self._implicit_app_ctx_stack = [] 11 12 self.preserved = False 13 14 self._preserved_exc = None 15 16 self._after_request_functions = [] 17 18 self.match_request()
看 3-8 行, environ 傳入 request_class 方法中返回一個 request 實例,賦值給 self ,並在第 8 行給 self 新增一個 session 屬性並賦值爲 None 。而最終這個 self 在 flask.app.Flask.wsgi_app 的第 7 行賦值給 ctx 。總結一下就是在執行完 flask.app.Flask.wsgi_app 的第 7 行後, ctx 被賦值爲 RequestContext 的一個實例,且這個實例中存在了將 environ 再次封裝的屬性 request 和一個爲 None 的屬性 session 。
接着看到 flask.app.Flask.wsgi_app 中的第 11 行,查看 push 方法:
1 def push(self): 2 top = _request_ctx_stack.top 3 if top is not None and top.preserved: 4 top.pop(top._preserved_exc) 5 6 app_ctx = _app_ctx_stack.top 7 if app_ctx is None or app_ctx.app != self.app: 8 app_ctx = self.app.app_context() 9 app_ctx.push() 10 self._implicit_app_ctx_stack.append(app_ctx) 11 else: 12 self._implicit_app_ctx_stack.append(None) 13 14 if hasattr(sys, 'exc_clear'): 15 sys.exc_clear() 16 17 _request_ctx_stack.push(self) 18 19 if self.session is None: 20 session_interface = self.app.session_interface 21 self.session = session_interface.open_session( 22 self.app, self.request 23 ) 24 25 if self.session is None: 26 self.session = session_interface.make_null_session(self.app)
直接看 19-26 行。若是 session 爲空,就將傳入 app 和 request 參數執行 session_interface 的 open_session 方法的返回值賦給 session ,而此時這個 session_interface 默認就是 flask.sessions.SecureCookieSessionInterface 類的實例,查看其 open_session 方法:
1 def open_session(self, app, request): 2 s = self.get_signing_serializer(app) 3 if s is None: 4 return None 5 val = request.cookies.get(app.session_cookie_name) 6 if not val: 7 return self.session_class() 8 max_age = total_seconds(app.permanent_session_lifetime) 9 try: 10 data = s.loads(val, max_age=max_age) 11 return self.session_class(data) 12 except BadSignature: 13 return self.session_class()
看第 5 行是從 cookie 中取一個鍵爲 app.session_cookie_name 的值,而這個鍵的默認值就是 'session' ,可在配置文件中配置(點擊查看配置文件默認配置參數)。緊接着就將這個值反序列化傳入 session_class 並返回 session_class 的實例,而 session_class 對應的是類 flask.sessions.SecureCookieSession 。因此在上面 flask.ctx.RequestContext.push 方法中 21 行 self.session 的值就是 flask.sessions.SecureCookieSession 的實例。也就是在上面 flask.app.Flask.wsgi_app 的 11 行執行以後, ctx 的 session 就有值了。
接着看 flask.app.Flask.wsgi_app 的第 15 行,這行就是經過 full_dispatch_request 方法來完成執行視圖函數和部分請求的收尾操做:
1 def full_dispatch_request(self): 2 self.try_trigger_before_first_request_functions() 3 try: 4 request_started.send(self) 5 rv = self.preprocess_request() 6 if rv is None: 7 rv = self.dispatch_request() 8 except Exception as e: 9 rv = self.handle_user_exception(e) 10 return self.finalize_request(rv)
看第 10 行,在完成了上面視圖函數相關操做後,經過 finalize_request 方法完成請求收尾操做:
1 def finalize_request(self, rv, from_error_handler=False): 2 response = self.make_response(rv) 3 try: 4 response = self.process_response(response) 5 request_finished.send(self, response=response) 6 except Exception: 7 if not from_error_handler: 8 raise 9 self.logger.exception('Request finalizing failed with an ' 10 'error while handling an error') 11 return response
再看到第 4 行的 process_response 方法,這個方法如其名,就是用來處理響應相關信息:
1 def process_response(self, response): 2 ctx = _request_ctx_stack.top 3 bp = ctx.request.blueprint 4 funcs = ctx._after_request_functions 5 if bp is not None and bp in self.after_request_funcs: 6 funcs = chain(funcs, reversed(self.after_request_funcs[bp])) 7 if None in self.after_request_funcs: 8 funcs = chain(funcs, reversed(self.after_request_funcs[None])) 9 for handler in funcs: 10 response = handler(response) 11 if not self.session_interface.is_null_session(ctx.session): 12 self.session_interface.save_session(self, ctx.session, response) 13 return response
直接看 十一、12 行,當 session 不爲空時,調用 session_interface.save_session 方法,而 session_interface 就是上面執行 open_session 方法的 flask.sessions.SecureCookieSessionInterface 類實例。
1 def save_session(self, app, session, response): 2 domain = self.get_cookie_domain(app) 3 path = self.get_cookie_path(app) 4 5 if not session: 6 if session.modified: 7 response.delete_cookie( 8 app.session_cookie_name, 9 domain=domain, 10 path=path 11 ) 12 13 return 14 15 if session.accessed: 16 response.vary.add('Cookie') 17 18 if not self.should_set_cookie(app, session): 19 return 20 21 httponly = self.get_cookie_httponly(app) 22 secure = self.get_cookie_secure(app) 23 samesite = self.get_cookie_samesite(app) 24 expires = self.get_expiration_time(app, session) 25 val = self.get_signing_serializer(app).dumps(dict(session)) 26 response.set_cookie( 27 app.session_cookie_name, 28 val, 29 expires=expires, 30 httponly=httponly, 31 domain=domain, 32 path=path, 33 secure=secure, 34 samesite=samesite 35 )
看 25-35 行,會發現最後又將 session 序列化,再次寫入 cookie 。
得出結論:當請求剛到來時,flask 讀取 cookie 中 session 對應的值,並將該值解密並反序列化成字典,放入內存以便視圖函數使用;當請求結束時,flask 會讀取內存中字典的值,進行序列化和加密,再寫入到 cookie 中。
1 from flask import Flask, request, session, redirect 2 from flask.sessions import SecureCookieSessionInterface 3 from flask_session import Session 4 from redis import Redis 5 6 app = Flask(__name__) 7 app.debug = True 8 9 10 app.config['SESSION_REDIS'] = Redis(host='127.0.0.1', port='6379', password='1234') 11 # 設置 session 類型 12 app.config['SESSION_TYPE'] = 'redis' 13 # 設置 根據 session 類型設置 app.session_interface 14 Session(app) 15 16 17 @app.route('/login') 18 def login(): 19 session['username'] = 'zze' 20 return 'success' 21 22 23 app.run()
經過上面的源碼部分已經知道了,flask 中的 session 存取就是經過 app.session_interface 來完成的,默認 app.session_interface = SecureCookieSessionInterface() ,而咱們只要修改這一部分,讓其存取是經過 redis 就 ok 了。查看 14 行 Session 類:
1 class Session(object): 2 def __init__(self, app=None): 3 self.app = app 4 if app is not None: 5 self.init_app(app) 6 7 def init_app(self, app): 8 app.session_interface = self._get_interface(app) 9 10 def _get_interface(self, app): 11 config = app.config.copy() 12 config.setdefault('SESSION_TYPE', 'null') 13 config.setdefault('SESSION_PERMANENT', True) 14 config.setdefault('SESSION_USE_SIGNER', False) 15 config.setdefault('SESSION_KEY_PREFIX', 'session:') 16 config.setdefault('SESSION_REDIS', None) 17 config.setdefault('SESSION_MEMCACHED', None) 18 config.setdefault('SESSION_FILE_DIR', 19 os.path.join(os.getcwd(), 'flask_session')) 20 config.setdefault('SESSION_FILE_THRESHOLD', 500) 21 config.setdefault('SESSION_FILE_MODE', 384) 22 config.setdefault('SESSION_MONGODB', None) 23 config.setdefault('SESSION_MONGODB_DB', 'flask_session') 24 config.setdefault('SESSION_MONGODB_COLLECT', 'sessions') 25 config.setdefault('SESSION_SQLALCHEMY', None) 26 config.setdefault('SESSION_SQLALCHEMY_TABLE', 'sessions') 27 28 if config['SESSION_TYPE'] == 'redis': 29 session_interface = RedisSessionInterface( 30 config['SESSION_REDIS'], config['SESSION_KEY_PREFIX'], 31 config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) 32 elif config['SESSION_TYPE'] == 'memcached': 33 session_interface = MemcachedSessionInterface( 34 config['SESSION_MEMCACHED'], config['SESSION_KEY_PREFIX'], 35 config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) 36 elif config['SESSION_TYPE'] == 'filesystem': 37 session_interface = FileSystemSessionInterface( 38 config['SESSION_FILE_DIR'], config['SESSION_FILE_THRESHOLD'], 39 config['SESSION_FILE_MODE'], config['SESSION_KEY_PREFIX'], 40 config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) 41 elif config['SESSION_TYPE'] == 'mongodb': 42 session_interface = MongoDBSessionInterface( 43 config['SESSION_MONGODB'], config['SESSION_MONGODB_DB'], 44 config['SESSION_MONGODB_COLLECT'], 45 config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], 46 config['SESSION_PERMANENT']) 47 elif config['SESSION_TYPE'] == 'sqlalchemy': 48 session_interface = SqlAlchemySessionInterface( 49 app, config['SESSION_SQLALCHEMY'], 50 config['SESSION_SQLALCHEMY_TABLE'], 51 config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], 52 config['SESSION_PERMANENT']) 53 else: 54 session_interface = NullSessionInterface() 55 56 return session_interface
執行到第 8 行,能夠看到它就是給 app.session_interface 賦值爲 self._get_interface(app) ,而這個方法的返回值是根據在上面使用中第 12 行配置的 'SESSION_TYPE' 字段決定的,這裏設置的是 'redis' ,因此 self._get_interface(app) 的返回值就爲第 82 行的 RedisSessionInterface 實例。