Python Web Flask源码解读(一)——启动流程
Python Web Flask源码解读(一)——启动流程
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
关于我
一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。
Github:https://github.com/hylinux1024
微信公众号:终身开发者(angrycode)
原文地址https://www.cnblogs.com/angrycode/p/11436727.html
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java反射使用总结
Java反射使用总结最近公司招了几名刚毕业的大学生,在给他们培训的过程中,讲到反射,他们有些人听不懂,对反射的概念云里雾里的,不知道反射有什么用。 因此就有了本文的诞生。 反射是java提供的一个重要功能,可以在运行时检查类、接口、方法和变量等信息,无需知道类的名字,方法名等。还可以在运行时实例化新对象,调用方法以及设置和获取变量值。 反射非常强大和有用,很多java框架中都有反射的影子,例如spring、mybatis等等, JDBC利用反射将数据库的表字段映射到java对象的getter/setter方法。 Jackson, GSON, Boon等类库也是利用反射将JSON文件的属性映射到java对的象getter/setter方法。 可见,只要使用java,反射就无处不在。 Class对象 检查一个类之前,必须获取到java.lang.Class对象,java中的所有类型,包括long,int,数组等基本数据类型,都和Class对象有关系。 我们很多人去医院参加体检的时候,都做过B超检查,医生只需把一个探头在我们身上滑动就可以将我们体内的肝、胆、肾等器官反射到B超设备上显示。 C...
- 下一篇
阿里云物联网平台网关子设备接入JAVA Sample
概述 子设备不直接连接物联网平台,而是通过网关接入物联网平台。首先,需在物联网平台上创建网关和子设备;然后,开发网关设备端SDK,实现网关直连物联网平台;再由网关向物联网平台上报网关与子设备的拓扑关系;通过网关上报子设备证书(一机一密方式)或者子设备动态注册的认证方式,物联网平台校验子设备的身份和该子设备与网关的拓扑关系。所有校验通过,才会建立子设备逻辑通道,并绑定至网关物理通道上,实现子设备通过网关,与物联网平台建立连接,并进行通信。本文主要如何演示使用JAVA SDK实现相关过程。 关系图 操作步骤 1、创建网关和子设备,参考链接。 2、子设备产品的物模型定义: 2、pom.xml <repositories> <repository> <id>alimaven&
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS7设置SWAP分区,小内存服务器的救世主
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,CentOS7官方镜像安装Oracle11G
- Hadoop3单机部署,实现最简伪集群
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7安装Docker,走上虚拟化容器引擎之路