接着前篇预告,这一篇主要是真正的进行断点调试深入到一个请求过程细节去理解一个框架的设计思路,整篇内容主要围绕下面几个点展开:
在上篇中已提及到几个关键信息点:
这些其实都是一些实现ASGI协议的对象,一个请求的过程可能包含了这些对象层层嵌套递归处理。这些理解一个请求过程的核心思路。
因为框架本身启动时,是使用一个app方式来表现,所以我们需要了解一下一个app服务对象初始化的是主要需要做哪些方面的工作信息.
分析示例源码如下:
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.endpoints import HTTPEndpoint
from starlette.routing import Route
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware.cors import CORSMiddleware
class Homepage(HTTPEndpoint):
async def get(self, request):
return PlainTextResponse(f"Hello, world!")
class User(HTTPEndpoint):
async def get(self, request):
username = request.path_params['username']
return PlainTextResponse(f"Hello, {username}")
routes = [
Route("/", Homepage),
Route("/{username}", User)
]
middleware = [
Middleware(CORSMiddleware, allow_origins=['*']),
Middleware(TrustedHostMiddleware, allowed_hosts=['example.com', '*.example.com']),
Middleware(HTTPSRedirectMiddleware)
]
app = Starlette(routes=routes, middleware=middleware)
if __name__ == '__main__':
import uvicorn
uvicorn.run('main:app')
其中上述的代码中,定义有了路由和中间件,
接下来分析Starlette的类:
app = Starlette(routes=routes, middleware=middleware)
具体的源码详细信息如下:
class Starlette:
def __init__(
self,
debug: bool = False,
routes: typing.Optional[typing.Sequence[BaseRoute]] = None,
middleware: typing.Optional[typing.Sequence[Middleware]] = None,
exception_handlers: typing.Optional[
typing.Mapping[
typing.Any,
typing.Callable[
[Request, Exception],
typing.Union[Response, typing.Awaitable[Response]],
],
]
] = None,
on_startup: typing.Optional[typing.Sequence[typing.Callable]] = None,
on_shutdown: typing.Optional[typing.Sequence[typing.Callable]] = None,
lifespan: typing.Optional[
typing.Callable[["Starlette"], typing.AsyncContextManager]
] = None,
) -> None:
# The lifespan context function is a newer style that replaces
# on_startup / on_shutdown handlers. Use one or the other, not both.
assert lifespan is None or (
on_startup is None and on_shutdown is None
), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."
self._debug = debug
self.state = State()
self.router = Router(
routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan
)
self.exception_handlers = (
{} if exception_handlers is None else dict(exception_handlers)
)
self.user_middleware = [] if middleware is None else list(middleware)
self.middleware_stack = self.build_middleware_stack()
----- 省略部分代码
其中具体参数说明如下:
on_startup 表示则是app启动直接需要处理钩子函数回调处理,他要么是Callable类型的要么是None
on_shutdown 和 on_startup则是相反的是服务关闭时候的钩子
lifespan 则是on_startup和on_shutdown的合集,他们之间只能存在一直方式,且它要么是Callable类型,要么是AsyncContextManager,要么是None
在实例化的时候处理:
self.state 则是内置了一个用于保存当app上下文中一些信息,他内部其实就是一个字典,比如我们后续可以直接使用:
app.state.xxxx=xxxxxx这样后续就可以再其他地方进行使用。
self.router 则是通过当前app对象默认开始时候创建一个Router的对象,注意不是我们自定义传入的routes对象哦,他是独立创建的,并且传入想跟的on_startup和on_shutdown等引用。
self.user_middleware 是对自定传入的中间件进程处理转化为列表
self.middleware_stack = self.build_middleware_stack()
其中上面这个地方开始对应当前app中所有的中间件都做一些处理合并,具体内部源码为:
def build_middleware_stack(self) -> ASGIApp:
# 设置当前debug开关状态
debug = self.debug
# 错误处理重置
error_handler = None
# 定义一个错误处理字典存储对象,后续通过键值对的方式获取具体Callable来执行异常处理
exception_handlers: typing.Dict[
typing.Any, typing.Callable[[Request, Exception], Response]
] = {}
# 循环当前自定义的 self.exception_handlers.items节点
for key, value in self.exception_handlers.items():
# 处理500错误的异常
if key in (500, Exception):
error_handler = value
else:
# 错误非500 其他自定义响应处理
exception_handlers[key] = value
# 合并当前默认的创建的中间件:
# - ServerErrorMiddleware 服务错误中间件
# - self.user_middleware 自定义中间件列表
# - ExceptionMiddleware 异常错误中间件
middleware = (
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
+ self.user_middleware
+ [
Middleware(
ExceptionMiddleware, handlers=exception_handlers, debug=debug
)
]
)
# 把当前默认创建出来路由对方付给当前我们的临时app一个对象,注意这个router
# 就是: self.router = Router(
# routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan
# )
app = self.router
# 然后我们把中间件逆序一下,并且循环的吧当前进行当前中间件app对象嵌套更换处理
# 这里逆序之后,我们的
for cls, options in reversed(middleware):
print('逆序之后:',cls)
# 逆序之后: <class 'starlette.exceptions.ExceptionMiddleware'>
# 逆序之后: <class 'starlette.middleware.cors.CORSMiddleware'>
# 逆序之后: <class 'starlette.middleware.errors.ServerErrorMiddleware'>
app = cls(app=app, **options)
# 最终我们当前app的对象是:、ServerErrorMiddleware
print('最终我们当前app的对象是:',app)
return app
完成app初始化创建之后,我开始打点,我们从上面知道,当我们的完成了app初始化之后,当有一个请求进来的话,则默认的会按类的方式请求处理我们的ASGI协议,此时我们找到Starlette中的__call__进行打点:
发现他调用的是:
self.middleware_stack = self.build_middleware_stack()
也就是不断的进行self.build_middleware_stack() 循环嵌套的调用处理!!! 所以我们也在build_middleware_stack进行打点:
默认启动的是也会调用,但是我们这里仅仅是分析请求,所以忽略启动时候打点,直接继续下一步,尝试发起一个请求分析:
这里地方就是把当前Starlette初始化的对象我附加到scope对象里面去,动态添加一个app对象 这样就可以通过传递当前app对象到下一个的scope["app"]进行获取当前app对象。
且当前self里面路由对象包含我们的当前路由列表,如下图所示:
此时继续F7下一步的,则会跑到我们的逆序后的ServerErrorMiddleware里面去执行,‘ __call__’, 因为我们
await self.middleware_stack(scope, receive, send)
第一次初始化完成后返回就是ServerErrorMiddleware的对象,所以我们在进行ServerErrorMiddleware的对象执行(scope, receive, send)的时候就会自动的跑到ServerErrorMiddleware里面去执行‘ __call__’,此时我们分析ServerErrorMiddleware中实现asgi协议的‘ __call__’处理内容:
因为我们的是http类型,所以它就继续往下执行了:
然后此时观察:当我们的执行ServerErrorMiddleware中间件处理的时候没有异常的话,那么此时都又会转到下一个CORSMiddleware中间件去执行CORSMiddleware中间件的实现asgi协议的‘ __call__’处理:
然后到ExceptionMiddleware中间件里面*的实现asgi协议的‘ __call__’处理:
再传递一次消息
然后到Router路由类里面*的实现asgi协议的‘ __call__’处理:
把当前路由实例化对象内嵌到的scope["router"]进行:
然后查看我们当前内嵌进去路由对象就是我们开始初始化时候的路由对象,
(PS:ID不一样了因为重启多次)
然后开始循环匹配路由,具体是请求哪一个路由的接口:
此时会进入具体路由类内部进行匹配:
正则匹配:
此时请求默认‘/’匹配到:
返回到循环匹配内,:
更新对应 scope.update(child_scope)信息后:
然后继续往下调用具体route.handle(scope, receive, send)的处理:
然后再一次进行请求信息处理,
观察此时具体路由对象中系信息明细:
再看此时里面的self信息为:
然后又跑到了Homepage里面定义的函数中:
而由于我们的Homepage是一个HTTPEndpoint类型的对象,也实现具体ASGI协议,所有它又一次跑:
然后自动调用类方式中__await__进行迭代:
然后最后跑到dispatch再一次的解析对应信息:
此时会进行等待整个处理响应,回调到这个处理中,
然后继续下一步则就是具体德等待业务逻辑处理,进入到dispatch进行正事的处理
在然后就是各种请求对象解析封装处理和解析:
然后则是对端点类里面函数,进行具体调用逻辑处理:
最后处理完成后,直接return退出整个循环匹配过程,和业务处理过程
再然后又跑到了ExceptionMiddleware中间里面,
然后再一下一步就是CORSMiddleware中间件中:
再然后下一步就是ServerErrorMiddleware中间件中::
再然后进入一个ProxyHeadersMiddleware中间件中,很奇怪这个不知道为啥有:
再然后进入uvicorn中的RequestResponseCycle中进行处理:
在到asyns实践里面处理
后面都已经脱离开了整个框架了!!!!就不在继续分析!!!!!!!!!!!!!!!!!
所以套娃实现示例如上图所示!!!!
以上仅仅是个人结合自己的实际需求,做学习的实践笔记!如有笔误!欢迎批评指正!感谢各位大佬!
END
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
公众号:微信搜【程序员小钟同学】
小钟同学 | 文 【欢迎一起学习交流】| QQ:308711822
原文作者:程序员小钟同学;如有侵权,请联系管理员删除。