关于我
编程界的一名小小程序猿,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。 联系:[email protected]
0x00 什么是WSGI
Web Server Gateway Interface
它由Python
标准定义的一套Web Server
与Web Application
的接口交互规范。
WSGI
不是一个应用、框架、模块或者库,而是规范。
那什么是Web Server
(Web
服务器)和什么是Web Application
(Web
应用)呢?
举例子来说明容易理解,例如常见的Web
应用框架有Django
、Flask
等,而Web
服务器有uWSGI
、Gunicorn
等。WSGI
就是定义了这两端接口交互的规范。
0x01 什么是Werkzeug
Werkzeug is a comprehensive WSGI web application library.
Werkzeug
是一套实现WSGI
规范的函数库。我们可以使用它来创建一个Web Application
(Web
应用)。例如本文介绍的Flask
应用框架就是基于Werkzeug
来开发的。
这里我们使用Werkzeug
启动一个简单的服务器应用
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello, World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
复制代码
运行之后可以在控制台上将看到如下信息
* Running on http://localhost:4000/ (Press CTRL+C to quit)
复制代码
使用浏览器打开 http://localhost:4000/ 看到以下信息,说明
Hello, World!
复制代码
0x02 什么是Flask
Flask is a lightweight WSGI web application framework.
Flask
是一个轻量级的web
应用框架,它是跑在web
服务器中的一个应用。Flask
底层就是封装的Werkzeug
。
使用Flask
开发一个web
应用非常简单
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return f'Hello, World!'
if __name__ == '__main__':
app.run()
复制代码
很简单吧。
接下来我们看看Flask
应用的启动流程。
0x03 启动流程
从项目地址 github.com/pallets/fla… 中把源码clone
下来,然后切换到0.1版本的tag
。为何要使用0.1版本呢?因为这个是作者最开始写的版本,代码量应该是最少的,而且可以很容易看到作者整体编码思路。
下面就从最简单的Demo
开始看看Flask
是如何启动的。我们知道程序启动是执行了以下方法
if __name__ == '__main__':
app.run()
复制代码
而
app = Flask(__name__)
复制代码
打开Flask
源码中的__init__
方法
Flask.init()
def __init__(self, package_name):
#: 是否打开debug模式
self.debug = False
#: 包名或模块名
self.package_name = package_name
#: 获取app所在目录
self.root_path = _get_package_path(self.package_name)
#: 存储视图函数的字典,键为函数名称,值为函数对象,使用@route装饰器进行注册
self.view_functions = {}
#: 存储错误处理的字典. 键为error code, 值为处理错误的函数,使用errorhandler装饰器进行注册
self.error_handlers = {}
#: 处理请求前执行的函数列表,使用before_request装饰器进行注册
self.before_request_funcs = []
#: 处理请求前执行的函数列表,使用after_request装饰器进行注册
self.after_request_funcs = []
#: 模版上下文
self.template_context_processors = [_default_template_ctx_processor]
#: url 映射
self.url_map = Map()
#: 静态文件
if self.static_path is not None:
self.url_map.add(Rule(self.static_path + '/' ,
build_only=True, endpoint='static'))
if pkg_resources is not None:
target = (self.package_name, 'static')
else:
target = os.path.join(self.root_path, 'static')
self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
self.static_path: target
})
#: 初始化 Jinja2 模版环境.
self.jinja_env = Environment(loader=self.create_jinja_loader(),
**self.jinja_options)
self.jinja_env.globals.update(
url_for=url_for,
get_flashed_messages=get_flashed_messages
)
复制代码
在Flask
的构造函数中进行了各种初始化操作。
然后就是执行app.run()
方法
app.run()
def run(self, host='localhost', port=5000, **options):
"""启动本地开发服务器. 如果debug设置为True,那么会自动检查代码是否改动,有改动则会自动执行部署
:param host: 监听的IP地址. 如果设置为 ``'0.0.0.0'``就可以进行外部访问
:param port: 端口,默认5000
:param options: 这个参数主要是对应run_simple中需要的参数
"""
from werkzeug.serving import run_simple
if 'debug' in options:
self.debug = options.pop('debug')
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
return run_simple(host, port, self, **options)
复制代码
run
很简洁,主要是调用了werkzeug.serving
中的run_simple
方法。
再打开run_simple
的源码
rum_simple()
def run_simple(hostname, port, application, use_reloader=False,
use_debugger=False, use_evalex=True,
extra_files=None, reloader_interval=1, threaded=False,
processes=1, request_handler=None, static_files=None,
passthrough_errors=False, ssl_context=None):
# 这方法还是比较短的,但是注释写得很详细,由于篇幅问题,就把源码中的注释省略了
if use_debugger:
from werkzeug.debug import DebuggedApplication
application = DebuggedApplication(application, use_evalex)
if static_files:
from werkzeug.wsgi import SharedDataMiddleware
application = SharedDataMiddleware(application, static_files)
def inner():
make_server(hostname, port, application, threaded,
processes, request_handler,
passthrough_errors, ssl_context).serve_forever()
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
display_hostname = hostname != '*' and hostname or 'localhost'
if ':' in display_hostname:
display_hostname = '[%s]' % display_hostname
_log('info', ' * Running on %s://%s:%d/', ssl_context is None
and 'http' or 'https', display_hostname, port)
if use_reloader:
# Create and destroy a socket so that any exceptions are raised before
# we spawn a separate Python interpreter and lose this ability.
test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
test_socket.bind((hostname, port))
test_socket.close()
run_with_reloader(inner, extra_files, reloader_interval)
else:
inner()
复制代码
在rum_simple
方法中还定义一个嵌套方法inner()
,这个是方法的核心部分。
inner()
def inner():
make_server(hostname, port, application, threaded, processes, request_handler, passthrough_errors, ssl_context).serve_forever()
复制代码
在inner()
方法里面,调用make_server(...).serve_forever()
启动了服务。
make_server()
def make_server(host, port, app=None, threaded=False, processes=1,
request_handler=None, passthrough_errors=False,
ssl_context=None):
"""Create a new server instance that is either threaded, or forks
or just processes one request after another.
"""
if threaded and processes > 1:
raise ValueError("cannot have a multithreaded and "
"multi process server.")
elif threaded:
return ThreadedWSGIServer(host, port, app, request_handler,
passthrough_errors, ssl_context)
elif processes > 1:
return ForkingWSGIServer(host, port, app, processes, request_handler,
passthrough_errors, ssl_context)
else:
return BaseWSGIServer(host, port, app, request_handler,
passthrough_errors, ssl_context)
复制代码
在make_server()
中会根据线程或者进程的数量创建对应的WSGI
服务器。Flask
在默认情况下是创建BaseWSGIServer
服务器。
BaseWSGIServer、ThreadedWSGIServer、ForkingWSGIServer
class BaseWSGIServer(HTTPServer, object):
...
class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
"""A WSGI server that does threading."""
...
class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
"""A WSGI server that does forking."""
...
复制代码
可以看出他们之前的继承关系如下
打开BaseWSGIServer
的start_server()
方法
start_server()
def serve_forever(self):
try:
HTTPServer.serve_forever(self)
except KeyboardInterrupt:
pass
复制代码
可以看到最终是使用HTTPServer
中的启动服务的方法。而HTTPServer
是Python
标准类库中的接口。
HTTPServer
是socketserver.TCPServer
的子类
socketserver.TCPServer
如果要使用Python
中类库启动一个http server
,则类似代码应该是这样的
import http.server
import socketserver
PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("serving at port", PORT)
httpd.serve_forever()
复制代码
至此,整个服务的启动就到这里就启动起来了。
这个过程的调用流程为
graph TD
A[Flask]-->B[app.run]
B[app.run]-->C[werkzeug.run_simple]
C[werkzeug.run_simple]-->D[BaseWSGIServer]
D[BaseWSGIServer]-->E[HTTPServer.serve_forever]
E[HTTPServer.serve_forever]-->F[TCPServer.serve_forever]
复制代码
0x04 总结一下
WSGI
是WEB
服务器与WEB
应用之间交互的接口规范。werkzeug
是实现了这一个规范的函数库,而Flask
框架是基于werkzeug
来实现的。 我们从Flask.run()
方法启动服务开始,追踪了整个服务启动的流程。
0x05 学习资料
- werkzeug.palletsprojects.com/en/0.15.x/
- palletsprojects.com/p/flask/
- docs.python.org/3/library/h…