生活中的用户-服务器响应模型
我们先用一个画面描述一下,这样会让你很容易理解后面的原理,并加深你的印象。
人物介绍:小红(快递站工作人员),小明(要取快递的A国人),小陈(要取快递的B国人)
背景介绍:某多元化城市(存在着大量的不同种族不同国家的人)的一个普通快递站
快递站刚开门,小明和小陈就来取快递,因为语言不通,所以小红在拿到他们身份的凭证及相关信息后,做了如下的事情:
1.将他们的身份凭证等信息(参数)通过某种神奇的仪器转化成快递仪器所能识别的状态
2.将整理好的小陈和小明的个人信息分开存储,一个人对应一个小格子,并做好唯一标记(用户信息分开入栈)
3.根据小陈的需求在系统中检索小陈的快递,对小明也做同样的操作(执行视图函数)
4.找到快递,确认信息无误后,在系统中标记快递被取出的状态,将快递交给小陈和小明(返回结果)
5.小陈和小明成功取到快递,将存放小陈和小明个人信息的小格子清空,等待其他人来取快递···(删除对应的上下文信息)
稍微专业的概括性描述
app服务器启动之后,一旦客户端请求进来,就会调用app.__call__方法,此方法基于WSGI规范,负责双方消息的收发。概括来说大概是以下这几点:
1.将客户端传递过来的原生请求参数整理成flask.request可以直接调用的格式(比如request.method,request.args等等)
2.将用户信息(参数)入栈暂存(app_context上下文, request_context上下文)
检查用户cookies中是否有session,有的话取出来直接用,没有的话,生成新的session
3.去路由表中开始匹配相应的视图函数
4.依次检查是否有brefore_first_request钩子函数的存在,有就执行,检查before_request钩子函数的存在,有就执行,然后开始执行用户请求的视图函数
5.将结果传递给make_response对象,做进一步的封装(包括请求头,session等)
6.检查是否有after_request钩子函数的存在,并决定是否执行,返回结果给客户
删除相关的上下文信息
既然分享干货,怎么可能概括的描述一下,就到此为止呢,那不是"耍流氓"吗?我们开始阅读源码
源码阅读
__call__方法理解
前面我们说到,客户端请求进来,要去找app.__call__方法,但是我听说有些人还不太理解__call__方法和__init__方法的区别,我这里简单解释下,其实很简单,记住一句话就行了:__init__方法是创建对象(实例化类)所调用的方法,而__call__方法则是调用对象(实例对象+括号)所使用的方法,下面进入正题。
app.__call__源码
我们知道python的web框架都是基于WSGI协议做的,所以我们首先看到的就是这个方法的封装
# 内部调用真正的wsgi方法,为的是可以在调用前通过自定义中间件增加一些适合自己应用的功能
def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)
从上述代码可以看得出,我们实际上要看的是真正的wsgi函数干了什么事情,以下
self.wsgi_app源码
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
这个部分拆成两部分来看,第一部分为用户信息的封装和入栈;第二部分为分发请求,执行视图函数并返回结果给用户
第一部分
# 我们要看的是这部分的源码分析
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
看下request_context都干了什么
def request_context(self, environ):
# 返回请求上下文对象
return RequestContext(self, environ)
只是实例化了一个对象,看看对象初始化过程中都做了什么
RequestContext的类源码(代码中有相关的注释)
class RequestContext(object):
def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
# 整理request请求参数
request = app.request_class(environ)
self.request = request
self.url_adapter = None
try:
self.url_adapter = app.create_url_adapter(self.request)
except HTTPException as e:
self.request.routing_exception = e
self.flashes = None
# 此时session为none
self.session = session
self._implicit_app_ctx_stack = []
self.preserved = False
self._preserved_exc = None
self._after_request_functions = []
上面封装了request请求对象,并将session初始化为None,接着往下看
ctx.push()源码(代码中有相关的注释)
def push(self):
# 这里的self是指上下文对象
"""Binds the request context to the current context."""
# 第一次来为None
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
# 实例化对象AppContext
app_ctx = self.app.app_context()
# 将上述对象信息入栈(appcontext)
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
if hasattr(sys, "exc_clear"):
sys.exc_clear()
# 将上下文请求入栈(requestcontext)
_request_ctx_stack.push(self)
# 开始生成session值
if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
self.session = session_interface.make_null_session(self.app)
# 匹配路由
if self.url_adapter is not None:
self.match_request()
以上我们可以总结如下:
- ctx是一个RequestContext对象,对象中包含了request和session信息(这里先不考虑别的属性)
- 将用户请求和相关参数入栈
- 为用户生成session值
- 匹配路由
第二部分
这段代码就是我们第二部分的核心代码,主要用来分发请求,并返回结果给用户
# 我们要看的是这一句源码分析
response = self.full_dispatch_request()
先看下full_dispatch_request都干了什么
def full_dispatch_request(self):
# 检查有没有第一条请求相关的函数存在
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
# 检查before_request函数的存在与否并执行
rv = self.preprocess_request()
# 这里为什么是None,因为before_request函数一般不会设置返回值,一旦有返回值,后面的就不走了
if rv is None:
# 这才是真正的执行视图函数的地方
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
我们看到了几个钩子函数的使用,然后终于来到了核心函数dispatch_request,源码如下(包括注释)
def dispatch_request(self):
# 从请求上下文中取相应的参数
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
if (
getattr(rule, "provide_automatic_options", False)
and req.method == "OPTIONS"
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
# 根据endpoint执行相关的视图函数
return self.view_functions[rule.endpoint](**req.view_args)
上述代码还有最后一块就是self.finalize_request(rv),看下源码
def finalize_request(self, rv, from_error_handler=False):
# 构造相关的参数
response = self.make_response(rv)
try:
# 处理数据返回过程,包括after_request等
response = self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not from_error_handler:
raise
self.logger.exception(
"Request finalizing failed with an error while handling an error"
)
return response
这个主要就是负责最后的返回结果的过程了,主要在于make_response和process_response两部分
第二部分我们可以总结如下:
- 分发请求的过程中,会分别查看钩子函数的存在与否
- 用户请求是基于在服务器的路由映射关系,执行相关的视图函数
- 数据的返回需要进一步的封装并在返回前检查钩子函数的存在
当然了,最后就要从服务器的上下文栈空间删除相关的用户信息,用户就完美的拿到了所需要的数据。
我是一名奋战在编程界的pythoner,工作中既要和数据打交道,也要和erp系统,web网站保持友好的沟通……时不时的会分享一些提高效率的编程小技巧,在实际应用中遇到的问题以及解决方案,或者源码的阅读等等,欢迎大家一起来讨论!如果觉得写得还不错,欢迎关注点赞,谢谢。