关于我
一个有思想的程序猿, 终身学习实践者, 目前在一个创业团队任 team lead, 技术栈涉及 Android,Python,Java 和 Go, 这个也是我们团队的主要技术栈.
GitHub:https://github.com/hylinux1024
微信公众号: 终身开发者 (angrycode)
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 启动流程
从项目地址 https://github.com/pallets/flask 中把源码 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 + '/<filename>',
- 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 学习资料
- https://werkzeug.palletsprojects.com/en/0.15.x/
- https://palletsprojects.com/p/flask/
- https://docs.python.org/3/library/http.server.html#module-http.server
来源: https://www.cnblogs.com/angrycode/p/11436727.html