从最简单的flask程序来了解学习Werkzeug与Jinjia2[使用方法+源码剖析]
在学习flask的过程中会遇到Werkzeug与Jinjia2两个模块,这两个模块在底层为flask提供了丰富的支持。
Werkzeug
官网上是这么介绍Werkzeug的:
Werkzeug在德语中是工具的意思,是一个综合的WSGI web应用库。它起初只是WSGI应用程序的各种实用程序的简单集合,现在已经成为最先进的WSGI实用程序库之一。
WSGI
所谓的WSGI为web服务器网关接口,在处理一个WSGI请求时,服务器会为应用程序提供环境信息及一个回调函数(Callback Function)。当应用程序完成处理请求后,透过前述的回调函数,将结果回传给服务器。WSGI定义了服务器和应用程序通信的标准。
一个简单的符合WSGI的应用程序:
1 | def application_callable(environ, start_response): |
Web程序必须有一个 可调用对象
当收到一个请求后,服务器会通过 application_callable(environ, start_response) 调用应用。
- 其中environ是一个字典,包含请求的相关信息,如请求方式、请求路径等等。
- 而start_response为上述 可调用对象 中调用的函数,接受的参数为状态(status)和响应头(response_headers)。
从flask到Werkzeug
flask hellworld
1 | from flask import Flask |
以下分析步骤仅考虑在一般情况下,只列出关键代码:
- 跟进app.run方法,在run方法中调用了werkzeug.serving的run_simple函数
- 跟进run_simple函数调用了inner函数
- inner函数中又调用了make_server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def inner():
try:
fd = int(os.environ["WERKZEUG_SERVER_FD"])
except (LookupError, ValueError):
fd = None
srv = make_server(
hostname,
port,
application,
threaded,
processes,
request_handler,
passthrough_errors,
ssl_context,
fd=fd,
)
if fd is None:
log_startup(srv.socket)
srv.serve_forever() - make_server中调用了BaseWSGIServer
1
2
3return BaseWSGIServer(
host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
) - BaseWSGIServer又继承了HTTPServer,HTTPServer继承了TCPServer,在TCPServer中我们看到了非常熟悉的东西:在我们初学python socket的时候,编写服务端的时候便是这样一个步骤,即创建套接字,绑定端口,持续监听;通过分析源码,我们浅显的搞清楚了从python应用到web服务后边函数调用链,也就是wsgi的原理,在应用与服务器之间架设桥梁。
1
2
3self.socket = socket.socket(self.address_family, self.socket_type)
self.socket.bind(self.server_address)
self.socket.listen(self.request_queue_size)
试想一下如果没有Werkzeug,flask不可能用短短几行代码跑起一个WEB应用,而正是有了Werkzeug在底层的支持,我们才能如此快捷的使用flask框架。
Jinjia2
Jinjia2是一种Python模板语言,具有很多友好的特性。Jinjia是日文中神庙的意思,对应英文为temple,和模板template非常相似。
与Werkzeug一样,从一个最简单的用到jinjia2模板渲染的程序来探究下jinjia2.
1 | from flask import Flask, render_template |
index.html
1 | <!DOCTYPE html> |
- 跟进render_template,调用了_render
1
2
3
4
5return _render(
ctx.app.jinja_env.get_or_select_template(template_name_or_list),
context,
ctx.app,
) - 跟进_render函数。过程如下:发送信号,渲染模板,发送信号,返回渲染后字符。
1
2
3
4
5
6
7def _render(template, context, app):
"""Renders the template and fires the signal"""
before_render_template.send(app, template=template, context=context) # signal.py before_render_template = _signals.signal("before-render-template")
rv = template.render(context) # rv is the rendered results
template_rendered.send(app, template=template, context=context) # signal.py before_render_template = _signals.signal("template_rendered")
return rv - 关于flask中的信号量与模板源码的细节以后再讨论,这里只是简单过下Jinjia2的模板渲染流程。
- 再看下Jinjia2的一些基本语法:
1
2
3
4
5
6
7控制结构 *{% %}
变量取值 {{ }}
注释 {# #}
例:
{% for name in names %}
<h2>hello,{{name}}</h2>
{% endfor %}