Sanic 源码阅读 - 基于0.1.2

Sanic 源码阅读 - 基于0.1.2

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

如果你:

  • 想深入了解Sanic,迫切想知道它的运行机制
  • 直接阅读源码,做一些定制
  • 学习

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

Sanic-0.1.2 的核心文件如下:

.
├── __init__.py
├── blueprints.py
├── config.py
├── exceptions.py
├── log.py
├── request.py
├── response.py
├── router.py
├── sanic.py
├── server.py
└── utils.py

通过运行下面的示例,这些文件都会被我们看到它的作用,拭目以待吧。

simple_server.py

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


from sanic_0_1_2.src import Sanic
from sanic_0_1_2.src.response import json

app = Sanic(__name__)


@app.route("/")
async def test(request):
    return json({"test": True})


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

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

git clone https://github.com/howie6879/sanic_annotation
cd sanic_annotation/sanic_0_1_2/examples/

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

前两行代码导入包:

  • Sanic:构建一个 Sanic 服务必须要实例化的类
  • json:以json格式返回结果,实际上是HTTPResponse类,根据实例化参数content_type的不同,构建不同的实例,如:
    • textcontent_type="text/plain; charset=utf-8"
    • htmlcontent_type="text/html; charset=utf-8"

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

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

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

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

  • register_blueprint():注册视图的函数,接受第一个参数是视图类blueprint,再调用该类下的register方法实现将此蓝图下的route、exception、middleware统一注册到app.route、app.exception、app.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基本的运行逻辑,如果你理解了,那下面的讲解对你来说是轻轻松松,如果不怎么明白,也不要紧,这是只是一个大体的介绍,跟着步骤来,也很容易理解,继续看代码:

# 此处将路由 / 与视图函数 test 关联起来
@app.route("/")
async def test(request):
    return json({"test": True})

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

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

会增加一个名为Routenamedtuple,如下:

[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函数,参数如下:


try:
    serve(
        host=host,
        port=port,
        debug=debug,
        # 服务开始后启动的函数
        after_start=after_start,
        # 在服务关闭前启动的函数
        before_stop=before_stop,
        # Sanic(__name__).handle_request()
        request_handler=self.handle_request,
        # 默认读取Config
        request_timeout=self.config.REQUEST_TIMEOUT,
        request_max_size=self.config.REQUEST_MAX_SIZE,
    )
except:
    pass

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

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

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

至此,Sanic 服务启动了

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

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

blueprints.py

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


from sanic_0_1_2.src import Sanic
# 引入Blueprint
from sanic_0_1_2.src import Blueprint
from sanic_0_1_2.src.response import json, text

app = Sanic(__name__)
blueprint = Blueprint('name', url_prefix='/my_blueprint')
blueprint2 = Blueprint('name2', url_prefix='/my_blueprint2')


@blueprint.route('/foo')
async def foo(request):
    return json({'msg': 'hi from blueprint'})


@blueprint2.route('/foo')
async def foo2(request):
    return json({'msg': 'hi from blueprint2'})


app.register_blueprint(blueprint)
app.register_blueprint(blueprint2)

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

让我们从这两行开始:


blueprint = Blueprint('name', url_prefix='/my_blueprint')
blueprint2 = Blueprint('name2', url_prefix='/my_blueprint2')

显然,blueprint以及blueprint2Blueprint根据不同的参数生成的不同的实例对象,接下来要干嘛?没错,分析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列表里面,
    • make_setup_state:实例化BlueprintSetup
    • register:注册视图,实际就是注册route、middleware、exception到app,此时会利用make_setup_state返回的BlueprintSetup示例进行对于的add_***一系列操作,相当于Sanic().route()效果

请看下routeregister函数,然后再看下面的代码:

# 生成一个匿名函数到self.deferred_functions列表里 包含三个参数 handler(foo), uri, methods
@blueprint.route('/foo')
async def foo(request):
    return json({'msg': 'hi from blueprint'})


@blueprint2.route('/foo')
async def foo2(request):
    return json({'msg': 'hi from blueprint2'})

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

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

总结

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

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

注解地址:sanic_annotation
博客地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容