资讯专栏INFORMATION COLUMN

python 最快 web 框架 Sanci 快速入门

snifes / 1872人阅读

摘要:详细信息可以看下这个问题先在说下我的部署方式使用部署配置文件启动方式总结试用了下,把之前的一个聊天机器人从改成了。预告下一篇将介绍如何使用一步一步创建一个聊天机器人。

简介

Sanic 是一个和类Flask 的基于Python3.5+的web框架,它编写的代码速度特别快。
除了像Flask 以外,Sanic 还支持以异步请求的方式处理请求。这意味着你可以使用新的 async/await 语法,编写非阻塞的快速的代码。

关于 asyncio 包的介绍,请参考之前的一篇文章 python并发2:使用asyncio处理并发

Github 地址 是 https://github.com/channelcat/sanic,感兴趣的可以去贡献代码。

既然它说速度特别快,我们先看下官方提供的 基准测试结果。

Sanic基准测试

这个测试的程序运行在 AWS 实例上,系统是Ubuntu,只使用了一个进程。

Sanic 的开发者说他们的灵感来自于这篇文章 uvloop: Blazing fast Python networking。

那我们就有必要看下uvloop是个什么库。

uvloop

uvloop 是 asyncio 默认事件循环的替代品,实现的功能完整,切即插即用。uvloop是用CPython 写的,建于libuv之上。
uvloop 可以使 asyncio 更快。事实上,它至少比 nodejs、gevent 和其他 Python 异步框架要快两倍 。基于 uvloop 的 asyncio 的速度几乎接近了 Go 程序的速度。

安装 uvloop

uvloop 还只能在 *nix 平台 和 Python3.5+以上版本使用。
使用pip安装:

pip install uvloop

在 asyncio 代码中使用uvloop 也很简单:

import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

这得代码使得对任何asyncio.get_event_loop() 的调用都将返回一个uvloop实例。

详细的uvloop 介绍可以看下原文:uvloop: Blazing fast Python networking。

uvloop的github地址是https://github.com/MagicStack/uvloop。

现在我们开始学习Sanic:

安装 Sanic
pip install sanic
创建第一个 sanic 代码
from sanic import Sanic
from sanic.response import text

app = Sanic(__name__)

@app.route("/")
async def test(request):
    return text("Hello world!")

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

运行代码: python main.py, 现在打开浏览器访问 http://0.0.0.0:8000,你会看到 hello world!

如果你熟悉Flask,你会发现,这个语法简直和Flask一模一样。

路由(Routing)

路由用于把一个函数绑定到一个 URL。下面是一些基本的例子:

@app.route("/")
def index():
    return text("Index Page")

@app.route("/hello")
def hello():
    return text("Hello World")

当然,你还可以动态的变化URL的某些部分,还可以为一个函数指定多个规则。

变量规则

通过把 URL 的一部分标记为 就可以在 URL 中添加变量。标记的 部分会作为关键字参数传递给函数。通过使用 ,可以 选择性的加上一个转换器,为变量指定特定的类型,如果传入的类型错误,Sanic会抛出NotFound异常。请看下面的例子:

from sanic.response import text

@app.route("/tag/")
async def tag_handler(request, tag):
    return text("Tag - {}".format(tag))

@app.route("/number/")
async def integer_handler(request, integer_arg):
    return text("Integer - {}".format(integer_arg))

@app.route("/number/")
async def number_handler(request, number_arg):
    return text("Number - {}".format(number_arg))

@app.route("/person/")
async def person_handler(request, name):
    return text("Person - {}".format(name))

@app.route("/folder/")
async def folder_handler(request, folder_id):
    return text("Folder - {}".format(folder_id))
HTTP 请求类型

默认情况下,我们定义的URL只支持GET 请求,@app.route装饰器提供了一个可选参数methods,这个参数允许传入所有HTTP 方法。
例如:

from sanic.response import text

@app.route("/post", methods=["POST"])
async def post_handler(request):
    return text("POST request - {}".format(request.json))

@app.route("/get", methods=["GET"])
async def get_handler(request):
    return text("GET request - {}".format(request.args))

也可以简写为:

from sanic.response import text

@app.post("/post")
async def post_handler(request):
    return text("POST request - {}".format(request.json))

@app.get("/get")
async def get_handler(request):
    return text("GET request - {}".format(request.args))
add_route 方法

除了@app.route装饰器,Sanic 还提供了 add_route 方法。

@app.route 只是包装了 add_route方法。

from sanic.response import text

# Define the handler functions
async def handler1(request):
    return text("OK")

async def handler2(request, name):
    return text("Folder - {}".format(name))

async def person_handler2(request, name):
    return text("Person - {}".format(name))

# Add each handler function as a route
app.add_route(handler1, "/test")
app.add_route(handler2, "/folder/")
app.add_route(person_handler2, "/person/", methods=["GET"])
URL 构建

如果可以匹配URL,那么Sanic可以生成URL吗?当然可以,url_for() 函数就是用于构建指定函数的URL的。它把函数名称作为第一个参数,其余参数对应URL中的变量,例如:

@app.route("/")
async def index(request):
    # generate a URL for the endpoint `post_handler`
    url = app.url_for("post_handler", post_id=5)
    # the URL is `/posts/5`, redirect to it
    return redirect(url)


@app.route("/posts/")
async def post_handler(request, post_id):
    return text("Post - {}".format(post_id))

未定义变量会作为URL的查询参数:

url = app.url_for("post_handler", post_id=5, arg_one="one", arg_two="two")
# /posts/5?arg_one=one&arg_two=two

# 支持多值参数
url = app.url_for("post_handler", post_id=5, arg_one=["one", "two"])
# /posts/5?arg_one=one&arg_one=two
使用蓝图(Blueprint)

Sanic也提供了和Flask 类似的 Blueprint。
Blueprint有以下用途:

把一个应用分解为一套蓝图。这是针对大型应用的理想方案:一个项目可以实例化一个 应用,初始化多个扩展,并注册许多蓝图。

在一个应用的 URL 前缀和(或)子域上注册一个蓝图。 URL 前缀和(或)子域的参数 成为蓝图中所有视图的通用视图参数(缺省情况下)。

使用不同的 URL 规则在应用中多次注册蓝图。

通过蓝图提供模板过滤器、静态文件、模板和其他工具。蓝图不必执行应用或视图 函数。

blueprint 示例
from sanic import Sanic
from sanic.response import json
from sanic import Blueprint

bp = Blueprint("my_blueprint")

@bp.route("/")
async def bp_root(request):
    return json({"my": "blueprint"})
    
app = Sanic(__name__)
app.blueprint(bp)

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

Sanic 使用 app.blueprint() 方法注册blueprint。

使用蓝图注册全局中间件
@bp.middleware
async def print_on_request(request):
    print("I am a spy")

@bp.middleware("request")
async def halt_request(request):
    return text("I halted the request")

@bp.middleware("response")
async def halt_response(request, response):
    return text("I halted the response")
使用蓝图处理异常
@bp.exception(NotFound)
def ignore_404s(request, exception):
    return text("Yep, I totally found the page: {}".format(request.url))
使用蓝图处理静态文件

第一个参数指向当前的Python包
第二个参数是静态文件的目录

bp.static("/folder/to/serve", "/web/path")
使用url_for

如果要创建页面链接,可以和通常一样使用 url_for() 函数,只是要把蓝图名称作为端点的前缀,并且用一个点( . )来 分隔:

@blueprint_v1.route("/")
async def root(request):
    url = app.url_for("v1.post_handler", post_id=5) # --> "/v1/post/5"
    return redirect(url)


@blueprint_v1.route("/post/")
async def post_handler(request, post_id):
    return text("Post {} in Blueprint V1".format(post_id))
操作请求数据

对于web 应用来说对客户端向服务器发送的数据做出相应很重要,在Sanic中由传入的参数 request来提供请求信息。

为什么不像Flask 一样提供一个全局变量 request?

Flask 是同步请求,每次请求都有一个独立的新线程来处理,这个线程中也只处理这一个请求。而Sanic是基于协程的处理方式,一个线程可以同时处理几个、几十个甚至几百个请求,把request作为全局变量显然会比较难以处理。

Request 对象常用参数有

json(any) json body
from sanic.response import json

@app.route("/json")
def post_json(request):
    return json({ "received": True, "message": request.json })
args(dict) URL请求参数

?key1=value1&key2=value2 将转变为

{"key1": ["value1"], "key2": ["value2"]}
raw_args(dict) 和args 类似

?key1=value1&key2=value2 将转变为

{"key1": "value1", "key2": "value2"}
form(dict)处理 POST 表单请求,数据是一个字典 body(bytes)处理POST 表单请求,数据是一个字符串

其他参数还有:

file

ip

app

url

scheme

path

query_string

详细信息参考文档: Request Data

关于响应

Sanic使用response 函数创建响应对象。

文本 response.text("hello world")

html response.html("

hello world

")

json response.json({"hello": "world"})

file response.file("/srv/www/hello.txt")

streaming

from sanic import response

@app.route("/streaming")
async def index(request):
    async def streaming_fn(response):
        response.write("foo")
        response.write("bar")
    return response.stream(streaming_fn, content_type="text/plain")

redirect response.file("/json")

raw response.raw("raw data")

如果想修改响应的headers可以传入headers 参数

from sanic import response

@app.route("/json")
def handle_request(request):
    return response.json(
        {"message": "Hello world!"},
        headers={"X-Served-By": "sanic"},
        status=200
    )
配置管理

应用总是需要一定的配置的。根据应用环境不同,会需要不同的配置。比如开关调试 模式、设置密钥以及其他依赖于环境的东西。
Sanic 的设计思路是在应用开始时载入配置。你可以在代码中直接硬编码写入配置,也可以使用配置文件。

不管你使用何种方式载入配置,都可以使用 Sanic 的 config 属性来操作配置的值。 Sanic 本身就使用这个对象来保存 一些配置,扩展也可以使用这个对象保存配置。同时这也是你保存配置的地方。

配置入门

config 实质上是一个字典的子类,可以像字典一样操作:

app = Sanic("myapp")
app.config.DB_NAME = "appdb"
app.config.DB_USER = "appuser"

也可以一次更新多个配置:

db_settings = {
    "DB_HOST": "localhost",
    "DB_NAME": "appdb",
    "DB_USER": "appuser"
}
app.config.update(db_settings)
从对象导入配置
import myapp.default_settings

app = Sanic("myapp")
app.config.from_object(myapp.default_settings)

这里是我写的聊天机器人的真实配置示例:https://github.com/gusibi/momo/

使用配置文件

如果把配置放在一个多带带的文件中会更有用。理想情况下配置文件应当放在应用包的 外面。这样可以在修改配置文件时不影响应用的打包与分发
常见用法如下:

app = Sanic("myapp")
app.config.from_envvar("MYAPP_SETTINGS")

首先从 myapp.default_settings 模块载入配置,然后根据 MYAPP_SETTINGS 环境变量所指向的文件的内容重载配置的值。在 启动服务器前,在 Linux 或 OS X 操作系统中,这个环境变量可以在终端中使用 export 命令来设置:

$ export MYAPP_SETTINGS=/path/to/config_file
$ python myapp.py
部署

Sanic 项目还不是特别成熟,现在部署比较简陋。对Gunicorn的支持也不完善。
详细信息可以 看下这个问题 Projects built with sanic?

先在说下我的部署方式

使用 supervisord 部署

supervisord 配置文件: https://github.com/gusibi/momo/blob/master/supervisord.conf

启动 方式

supervisord -c supervisor.conf
总结

试用了下Sanic,把之前的一个聊天机器人从Flask 改成了 Sanic。不得不说,如果你有Flask经验,大致看一下Sanic文档就可以直接上手了。
并且Sanic 的速度比Flask 快很多,只是Sanic配套的包还是太少,用于生产环境有一定的风险。

最后对聊天微信聊天机器人感兴趣的可以看下https://github.com/gusibi/momo。

预告

下一篇将介绍如何使用 Sanic 一步一步创建一个 聊天机器人。

参考链接

uvloop: Blazing fast Python networking

Sanic Githu 地址

Sanic 文档


最后,感谢女朋友支持。

>欢迎关注 >请我喝芬达
彩蛋

魔魔是我们家巴哥的名字
贴一张魔魔的照片结束本篇文章。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38644.html

相关文章

  • python并发3:使用asyncio编写服务器

    摘要:上一篇我们介绍了包,以及如何使用异步编程管理网络应用中的高并发。倒排索引保存在本地一个名为的文件中。运行示例如下这个模块没有使用并发,主要作用是为使用包编写的服务器提供支持。 asyncio 上一篇我们介绍了 asyncio 包,以及如何使用异步编程管理网络应用中的高并发。在这一篇,我们主要介绍使用 asyncio 包编程的两个例子。 async/await语法 我们先介绍下 asyn...

    rollback 评论0 收藏0
  • 后端API从入门到放弃指北

    摘要:菜鸟教程框架中文手册入门目标使用搭建通过对数据增删查改没了纯粹占行用的拜 后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一峰:理解RESTful架构 阮一峰:RESTful API 设计指南 RESTful API指南 依赖注入 D...

    Jeffrrey 评论0 收藏0
  • 后端API从入门到放弃指北

    摘要:菜鸟教程框架中文手册入门目标使用搭建通过对数据增删查改没了纯粹占行用的拜 后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一峰:理解RESTful架构 阮一峰:RESTful API 设计指南 RESTful API指南 依赖注入 D...

    sf190404 评论0 收藏0
  • 后端API从入门到放弃指北

    摘要:菜鸟教程框架中文手册入门目标使用搭建通过对数据增删查改没了纯粹占行用的拜 后端API入门学习指北 了解一下一下概念. RESTful API标准] 所有的API都遵循[RESTful API标准]. 建议大家都简单了解一下HTTP协议和RESTful API相关资料. 阮一峰:理解RESTful架构 阮一峰:RESTful API 设计指南 RESTful API指南 依赖注入 D...

    Airmusic 评论0 收藏0
  • 100 个基本 Python 面试问题第二部分(41-60)

    摘要:回到目录评论区抽粉丝送书啦欢迎大家在评论区提出意见和建议抽两位幸运儿送书,实物图如下开发从入门到精通内容简介案例教学。 ? 作者主页:海拥 ? 作者简介:?CSDN...

    Tikitoo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<