查看原文
其他

Sanic源码剖析

howie6879 老胡的储物柜 2022-06-01

Sanic是一个可以使用 async/await语法编写项目的异步非阻塞框架,它写法类似于 Flask,但使用了异步特性,而且还使用 uvloop作为事件循环,其底层使用的是 libuv,从而使 Sanic的速度优势更加明显。

本章,我将和大家一起看看 Sanic里面的运行机制是怎样的,它的 RouterBlueprint等是如何实现的。

如果你有以下的需求:

  • 想深入了解Sanic,迫切想知道它的运行机制

  • 直接阅读源码,做一些定制

  • 学习

将Sanic-0.1.2阅读完后的一些建议,我觉得你应该有以下基础再阅读源码才会理解地比较好:

  • 理解装饰器,见附录

  • 理解协程

Sanic-0.1.2 的核心文件如下:

  1. .

  2. ├── __init__.py

  3. ├── blueprints.py

  4. ├── config.py

  5. ├── exceptions.py

  6. ├── log.py

  7. ├── request.py

  8. ├── response.py

  9. ├── router.py

  10. ├── sanic.py

  11. ├── server.py

  12. └── utils.py

通过运行下面的示例,这些文件都会被我们看到它的作用,拭目以待吧,为了方便诸位的理解,我已将我注解的一份 Sanic代码上传到了 github,见sanic_annotation。

simple_server.py

让我们从simple_server开始吧,代码如下:

  1. from sanic_0_1_2.src import Sanic

  2. from sanic_0_1_2.src.response import json


  3. app = Sanic(__name__)



  4. @app.route("/")

  5. async def test(request):

  6.    return json({"test": True})



  7. app.run(host="0.0.0.0", port=8000)

或许你直接把sanic_annotation项目直接clone到本地比较方便调试+理解:

  1. git clone https://github.com/howie6879/sanic_annotation

  2. cd sanic_annotation/sanic_0_1_2/examples/

那么,现在一切准备就绪,开始阅读吧。

前两行代码导入包:

  • Sanic:构建一个 Sanic 服务必须要实例化的类

  • json:以json格式返回结果,实际上是HTTPResponse类,根据实例化参数content_type的不同,构建不同的实例,如:

    • text: content_type="text/plain; charset=utf-8"

    • html: content_type="text/html; charset=utf-8"

实例化一个 Sanic对象, app=Sanic(__name__),可见sanic.py,我已经在这个文件里面做了一些注释,这里也详细说下 Sanic类:

  • route():装饰器,构建uri和视图函数的映射关系,调用Router().add()方法

  • exception():装饰器,和上面差不多,不过针对的是错误处理类Handler

  • middleware():装饰器,针对中间件

  • register_blueprint():注册视图的函数,接受第一个参数是视图类 blueprint,再调用该类下的 register方法实现将此蓝图下的 routeexceptionmiddleware统一注册到 app.routeapp.exceptionapp.exception

  • handle_request():这是一个很重要的异步函数,当服务启动后,如果客户端发来一个有效的请求,会自动执行 on_message_complete函数,该函数的目的是异步调用 handle_request函数, handle_request函数会回调 write_response函数, write_response接受的参数是此uri请求对应的视图函数,比如上面demo中,如果客户端请求'/',那么这里 write_response就会接受 json({"test":True}),然后进一步处理,再返回给客户端

  • run():Sanic服务的启动函数,必须执行,实际上会继续调用 server.serve函数,详情下面会详细讲

  • stop():终止服务

其实上面这部分介绍已经讲了Sanic基本的运行逻辑,如果你理解了,那下面的讲解对你来说是轻轻松松,如果不怎么明白,也不要紧,这是只是一个大体的介绍,跟着步骤来,也很容易理解,继续看代码:

  1. # 此处将路由 / 与视图函数 test 关联起来

  2. @app.route("/")

  3. async def test(request):

  4.    return json({"test": True})

app.route,上面介绍过,随着Sanic服务的启动而启动,可定义参数 uri,methods

目的是为 url的 path和视图函数对应起来,构建一对映射关系,本例中 Sanic.router类下的 Router.routes=[]

会增加一个名为 Route的 namedtuple,如下:

  1. [Route(handler=<function test at 0x10a0f6488>, methods=None, pattern=re.compile('^/$'), parameters=[])]

看到没, uri'/' 和视图函数 test对应起来了,如果客户端请求 '/',当服务器监听到这个请求的时候, handle_request可以通过参数中的 request.url来找到视图函数 test并且执行,随即生成视图返回

那么这里 write_response就会接受视图函数test返回的 json({"test":True})

说下 Router类,这个类的目的就是添加和获取路由对应的视图函数,把它想象成 dict或许更容易理解:

  • add(self, uri, methods, handler):添加一个映射关系到self.routes

  • get(self, request):获取request.url对应的视图函数

最后一行, app.run(host="0.0.0.0",port=8000),Sanic 下的 run函数,启动一个 http server,主要是启动 run里面的 serve函数,参数如下:

  1. try:

  2.    serve(

  3.        host=host,

  4.        port=port,

  5.        debug=debug,

  6.        # 服务开始后启动的函数

  7.        after_start=after_start,

  8.        # 在服务关闭前启动的函数

  9.        before_stop=before_stop,

  10.        # Sanic(__name__).handle_request()

  11.        request_handler=self.handle_request,

  12.        # 默认读取Config

  13.        request_timeout=self.config.REQUEST_TIMEOUT,

  14.        request_max_size=self.config.REQUEST_MAX_SIZE,

  15.    )

  16. except:

  17.    pass

让我们将目光投向server.py,这也是Sanic框架的核心代码:

  • serve():里面会创建一个TCP服务的协程,然后通过 loop.run_forever()运行这个事件循环,以便接收客户端请求以及处理相关事件,每当一个新的客户端建立连接服务就会创建一个新的 Protocol实例,接受请求与返回响应离不开其中的 HttpProtocol,里面的函数支持接受数据、处理数据、执行视图函数、构建响应数据并返回给客户端

  • HttpProtocol: asyncio.Protocol的子类,用来处理与客户端的通信,我在server.py里写了对应的注释

至此,Sanic 服务启动了

不要小看这一个小小的demo,执行一下,竟然涉及到下面这么多个文件,让我们总结一下:

  • sanic.py

  • server.py

  • router.py

  • request.py

  • response.py

  • exceptions.py

  • config.py

  • log.py

除去 __init__.py, Sanic项目一共就10个文件,这个小demo不显山不露水地竟然用到了8个,虽然其中几个没有怎么用到,但也足够说明,你如果理解了这个demo, Sanic的运行逻辑以及框架代码你已经了解地很深入了

blueprints.py

这个例子看完,我们就能轻易地明白什么是 blueprints,以及 blueprints的运行方式,代码如下:

  1. from sanic_0_1_2.src import Sanic

  2. # 引入Blueprint

  3. from sanic_0_1_2.src import Blueprint

  4. from sanic_0_1_2.src.response import json, text


  5. app = Sanic(__name__)

  6. blueprint = Blueprint('name', url_prefix='/my_blueprint')

  7. blueprint2 = Blueprint('name2', url_prefix='/my_blueprint2')



  8. @blueprint.route('/foo')

  9. async def foo(request):

  10.    return json({'msg': 'hi from blueprint'})



  11. @blueprint2.route('/foo')

  12. async def foo2(request):

  13.    return json({'msg': 'hi from blueprint2'})



  14. app.register_blueprint(blueprint)

  15. app.register_blueprint(blueprint2)


  16. app.run(host="0.0.0.0", port=8000, debug=True)

让我们从这两行开始:

  1. blueprint = Blueprint('name', url_prefix='/my_blueprint')

  2. blueprint2 = Blueprint('name2', url_prefix='/my_blueprint2')

显然, blueprint以及 blueprint2是 Blueprint根据不同的参数生成的不同的实例对象,接下来要干嘛?没错,分析blueprints.py:

  • BlueprintSetup:蓝图注册类

  • add_route:添加路由到app

  • add_exception:添加对应抛出的错误到app

  • add_middleware:添加中间件到app

  • Blueprint:蓝图类,接收两个参数:name(蓝图名称) url_prefix 该蓝图的url前缀

  • route:路由装饰器,将会生成一个匿名函数到self.deferred_functions列表里稍后一起处理注册到app里

  • middleware:同上

  • exception:同上

  • record:注册一个回调函数到self.deferred_functions列表里面,

  • makesetupstate:实例化BlueprintSetup

  • register:注册视图,实际就是注册route、middleware、exception到app,此时会利用makesetupstate返回的BlueprintSetup示例进行对于的add_*一系列操作,相当于Sanic().route()效果

请看下 route和 register函数,然后再看下面的代码:

  1. # 生成一个匿名函数到self.deferred_functions列表里 包含三个参数 handler(foo), uri, methods

  2. @blueprint.route('/foo')

  3. async def foo(request):

  4.    return json({'msg': 'hi from blueprint'})



  5. @blueprint2.route('/foo')

  6. async def foo2(request):

  7.    return json({'msg': 'hi from blueprint2'})


  8. # 上一个例子说过这个函数,Sanic().register_blueprint() 注册蓝图

  9. app.register_blueprint(blueprint)

  10. app.register_blueprint(blueprint2)

怎么样,现在来看,是不是很轻松,这一行 app.run(host="0.0.0.0",port=8000,debug=True)服务启动代码不用多说吧?

总结

看到这里,相信你已经完全理解了 Sanic的运行机制,虽然还有 middleware&exception的注册以及调用机制没讲,但这和 route的运行机制一样,如果你懂了 route那么这两个也很简单。

如果诸位一遍没怎么看明白,这里我建议可以多看几遍,多结合编辑器 Debug下源码,坚持下来,会发下 Sanic真的很简单,当然,这只是第一个小版本的 Sanic,和目前的版本相比,不论是代码结构的复杂程度以及功能对比,都有很大差距,毕竟, Sanic一直在开源工作者的努力下,慢慢成长。

本人技术微末,若有错误,请指出,不胜感激.

  • 注解地址:sanic_annotation - 点击阅读原文

  • 博客地址:http://blog.howie6879.cn/post/31/

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存