路由和视图
这一波主要是通过看源码加深对 Flask 中路由和视图的了解,可以先回顾一下装饰器的知识:【装饰器函数与进阶】
路由设置的两种方式
# 示例代码
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
查看 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
flask.app.Flask.route
在上述示例中, rule 就是我们自定义的路由参数 '/index' ; endpoint 就是终结点参数(用来反向生成 url),这里我们没传;而 f 实际上就是该装饰器所装饰的函数,在这里也就是 index 函数。其实到这里就可以断定,该装饰器实际上就是通过第 4 行的 add_url_rule。我们可以测试:
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
endpoint
接着看 app.add_url_rule
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
flask.app.Flask.add_url_rule
看 3、4、5 行,在示例中我们并没有传入 endpoint 参数,所以 endpoint 在第 3 行肯定是 None。接着执行第 4 行,查看 _endpoint_from_view_func
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__
flask.helpers._endpoint_from_view_func
看第 4 行的返回值是视图函数的函数名称,所以当不传 endpoint 参数时, endpoint 的值就是视图函数的函数名称。
继续看 flask.app.Flask.add_url_rule 函数的 34-39 行, 39 行做的就是每次装饰器执行时,就会把装饰器当前装饰的函数当做值, endpoint 当做 key ,放入 view_functions 这个字典中。而从 35-38 行可以看到,如果一个新的视图函数的 endpoint 已经存在 view_functions 中,但这个函数又与 endpoint 之前对应的视图函数不是同一个函数,就会产生 37 行错误。所以我们要保证每个视图函数对应的 endpoint。
app.route的参数
除了我们已经熟悉的 rule 和 view_func
defaults
默认值,当URL中无参数,但函数需要参数时,可以使用 defaults={'k':'v'} 为函数提供参数。
endpoint
名称,用于反向生成 URL,即: url_for('endpoint')
methods
允许的请求方式,如: methods=["GET","POST"]
strict_slashes
对 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
redirect
重定向到指定地址。
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
def index():
return 'index'
subdomain
子域名访问。
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()
CBV
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 实现很相似,源码就不细说了。
正则匹配URL
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()
Session
源码
首先我们要知道 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)
flask.app.Flask.wsgi_app
environ 是请求相关信息,第 7 行将 environ 传入 request_context
1 def request_context(self, environ):
2 return RequestContext(self, environ)
flask.app.Flask.request_context
可以看到它的返回值就是以 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()
flask.ctx.RequestContext.__init__
看 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)
flask.ctx.RequestContext.push
直接看 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()
flask.sessions.SecureCookieSessionInterface.open_session
看第 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)
flask.app.Flask.full_dispatch_request
看第 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
flask.app.Flask.finalize_request
再看到第 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
flask.app.process_response
直接看 11、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 )
flask.sessions.SecureCookieSessionInterface.save_session
看 25-35 行,会发现最后又将 session
得出结论:当请求刚到来时,flask 读取 cookie 中 session 对应的值,并将该值解密并反序列化成字典,放入内存以便视图函数使用;当请求结束时,flask 会读取内存中字典的值,进行序列化和加密,再写入到 cookie 中。
第三方session
使用
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
- flask_session.Session
执行到第 8 行,可以看到它就是给 app.session_interface 赋值为 self._get_interface(app) ,而这个方法的返回值是根据在上面使用中第 12 行配置的 'SESSION_TYPE' 字段决定的,这里设置的是 'redis' ,所以 self._get_interface(app) 的返回值就为第 82 行的 RedisSessionInterface
Java博客目录 | Python博客目录 | C#博客目录