快速开始
在安装Sanic之前,让我们一起来看看Python在支持异步的过程中,都经历了哪些比较重大的更新。
首先是Python3.4版本引入了asyncio
,这让Python有了支持异步IO的标准库,而后3.5版本又提供了两个新的关键字async/await
,目的是为了更好地标识异步IO,让异步编程看起来更加友好,最后3.6版本更进一步,推出了稳定版的asyncio
,从这一系列的更新可以看出,Python社区正迈着坚定且稳重的步伐向异步编程靠近。
安装
Sanic是一个支持 async/await
语法的异步无阻塞框架,这意味着我们可以依靠其处理异步请求的新特性来提升服务性能,如果你有Flask
框架的使用经验,那么你可以迅速地使用Sanic
来构建出心中想要的应用,并且性能会提升不少,我将同一服务分别用Flask和Sanic编写,再将压测的结果进行对比,发现Sanic编写的服务大概是Falsk
的1.5倍。
仅仅是Sanic的异步特性就让它的速度得到这么大的提升么?是的,但这个答案并不标准,更为关键的是Sanic使用了uvloop
作为asyncio
的事件循环,uvloop
由Cython编写,它的出现让asyncio
更快,快到什么程度?这篇文章中有介绍,其中提出速度至少比 nodejs、gevent 和其他Python异步框架要快两倍,并且性能接近于用Go编写的程序,顺便一提,Sanic的作者就是受这篇文章影响,这才有了Sanic。
怎么样?有没有激起你学习Sanic的兴趣,如果有,就让我们一起开始学习吧,在开始之前,你只需要有一台安装了Python的电脑即可。
说明:由于Windows下暂不支持安装uvloop,故在此建议使用Mac或Linux
虚拟环境
程序世界一部分是对应着现实的,在生活中,我们会在不同的环境完成不同的任务,比如在厨房做饭、卧室休息,分工极其明确。
其实用Python编写应用服务也是如此,它们同样希望应用服务与开发环境是一对一的关系,这样做的好处在于,每个独立的环境都可以简洁高效地管理自身对应服务所依赖的第三方库,如若不然,各个服务都安排在同一环境,这样不仅会造成管理上的麻烦,还会使第三方库之间产生冲突。
通过上面的叙述,我们是不是可以得出这样一个核心观点:应该在不同的环境下做不同的事,以此类推,写项目的时候,我们也需要为每个不同的项目构建一个无干扰的的环境,发散思维,总结一下:
不同的项目,需要为其构建不同的虚拟环境,以免互相干扰
构建虚拟环境的工具很多,如下:
…...
以上三个工具都可以快速地帮助我们构建当前需要的Python环境,如果你之前没有使用过,可直接点开链接进行下载,如果你正在使用其它的环境管理工具,也不要紧,因为不论你使用哪一种方式,我们最终目的都是针对一个新项目构建一个新的环境。
安装配置好之后,简单看看官方提供的使用方法,就可以开始了,比如我本机使用的是venv
(python3.5以后官方推荐使用这个venv来管理虚拟环境),安装完成后可以很方便地创建一个虚拟环境,比如这里使用Python3.6来作为本书项目的默认环境:
cd ~/
# 新建一个python3.6环境
python3 -m venv pyenv
# 安装好之后 输入下面命令进入名为python36的环境
cd pyenv/
source bin/activate
# 查看版本
python -V
若安装速度比较慢,可以考虑换国内源,比如 国内镜像,至于为什么选择python3.6作为默认环境,一是因为Sanic只支持Python3.5+,二则是我们构建的项目最终是要在生产环境下运行的,所以建议最好安装Python3.6下稳定版本的asyncio
。
安装Sanic
Python安装第三方模块都是利用pip
工具进行安装,这里也不例外,首先进入上一步我们新建的 python3.6
虚拟环境,然后安装:
# 安装Sanic,请先使用 source activate python36 进入虚拟环境
pip install sanic
# 如果不想使用uvloop和ujson 可以这样安装
SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true pip install sanic
通过上面的命令,你就可以在 python3.6
虚拟环境中安装Sanic以及其依赖的第三方库了,若想查看Sanic是否已经正确安装,可以进入终端下对应的虚拟环境,启动Python解释器,导入Sanic库:
python
>>>
>>> import sanic
如果没有出现错误,就说明你已经正确地安装了Sanic,请继续阅读下一节,了解下如何利用Sanic来构建一个Web项目吧。
开始
我们将正式使用Sanic来构建一个web项目,让我们踏出第一步,利用Sanic来编写一个返回Hello World!
字符串的服务程序。
新建一个文件夹sanicweb
:
$ mkdir sanicweb
$ cd sanicweb/
$ pwd
/Users/junxi/pyenv/sanicweb
创建一个sanic例子,保存为 main.py
:
from sanic import Sanic
from sanic.response import text
app = Sanic()
@app.route("/")
async def index(request):
return text('Hello World!')
if __name__ == "__main__":
app.run(host="0.0.0.0", port=9000)
运行main.py
,然后访问地址http://127.0.0.1:9000/
$ curl -X GET http://127.0.0.1:9000/
Hello World!
这样我们就完成了第一个sanic例子。
接下来,你将逐渐地了解到Sanic的一些基本用法,如路由的构建、接受请求数据以及返回响应的内容等。
路由
路由允许用户为不同的URL端点指定处理程序函数。
实例:
from sanic.response import json
@app.route("/")
async def index(request):
return json({ "hello": "world" })
url http://server.url/被访问(服务器的基本url),最终/被路由器匹配到处理程序函数,测试,然后返回一个JSON对象。
必须使用async def语法来定义Sanic处理函数,因为它们是异步函数。
请求参数
要指定一个参数,可以用像这样的角引号<PARAM>包围它。请求参数将作为关键字参数传递给路线处理程序函数。
实例:
@app.router('/tag/<tag>')
async def tag_handler(request, tag):
return text('Tag - {}'.format(tag))
重启服务,输入地址http://127.0.0.1:9000/tag/python
进行访问
$ curl -X GET http://127.0.0.1:9000/tag/python
Tag - python
为参数指定类型,在参数名后面添加(:类型)。如果参数不匹配指定的类型,Sanic将抛出一个不存在的异常,导致一个404页面
@app.route('/number/<integer_arg:int>')
async def integer_handler(request, integer_arg):
return text('Integer - {}'.format(integer_arg))
@app.route('/number/<number_arg:number>')
async def number_handler(request, number_arg):
return text('Number - {}'.format(number_arg))
@app.route('/person/<name:[A-z]+>')
async def person_handler(request, name):
return text('Person - {}'.format(name))
@app.route('/folder/<folder_id:[A-z0-9]{0,4}>')
async def folder_handler(request, folder_id):
return text('Folder - {}'.format(folder_id))
测试结果如下:
$ curl -X GET http://127.0.0.1:9000/number/1
Integer - 1
$ curl -X GET http://127.0.0.1:9000/number/asds
Error: Requested URL /number/asds not found
$ curl -X GET http://127.0.0.1:9000/number/12.0
Number - 12.0
$ curl -X GET http://127.0.0.1:9000/person/junxi
Person - junxi
$ curl -X GET http://127.0.0.1:9000/person/123
Error: Requested URL /person/123 not found
$ curl -X GET http://127.0.0.1:9000/folder/img
Folder - img
$ curl -X GET http://127.0.0.1:9000/folder/img1
Folder - img1
$ curl -X GET http://127.0.0.1:9000/folder/images
Error: Requested URL /folder/images not found
$ curl -X GET http://127.0.0.1:9000/folder/2018
Folder - 2018
请求类型
路由装饰器接受一个可选的参数,方法,它允许处理程序函数与列表中的任何HTTP方法一起工作。
实例1:
@app.route('/post1', methods=['POST'])
async def post_handler(request):
return text('POST request - {}'.format(request.json))
@app.route('/get1', methods=['GET'])
async def get_handler(request):
return text('GET request - {}'.format(request.args))
实例2:
@app.post('/post2')
async def post_handler(request):
return text('POST request - {}'.format(request.json))
@app.get('/get2')
async def get_handler(request):
return text('GET request - {}'.format(request.args))
测试结果:
$ curl -X GET http://127.0.0.1:9000/get1?name=junxi
GET request - {'name': ['junxi']}
$ curl -X GET http://127.0.0.1:9000/get2?name=junxi
GET request - {'name': ['junxi']}
$ curl -H "Content-type: application/json" -X POST -d '{"name":"junxi", "gender":"male"}' http://127.0.0.1:9000/post1
POST request - {'name': 'junxi', 'gender': 'male'}
$ curl -H "Content-type: application/json" -X POST -d '{"name":"junxi", "gender":"male"}' http://127.0.0.1:9000/post2
POST request - {'name': 'junxi', 'gender': 'male'}
增加路由
实例:
async def handler1(request):
return text('ok')
async def handler2(request, name):
return text('Folder - {}'.format(name))
async def personal_handler2(request, name):
return text('Person - {}'.format(name))
app.add_route(handler1, '/test1')
app.add_route(handler2, '/folder2/<name>')
app.add_route(personal_handler2, '/personal2/<name:[A-z]>', methods=['GET'])
测试结果:
$ curl -X GET http://127.0.0.1:9000/test1
ok
$ curl -X GET http://127.0.0.1:9000/folder2/aaa
Folder - aaa
$ curl -X GET http://127.0.0.1:9000/personal2/A
Person - A
$ curl -X GET http://127.0.0.1:9000/personal2/a
Person - a
url_for
Sanic提供了一个urlfor方法,根据处理程序方法名生成url。避免硬编码url路径到您的应用程序
实例:
@app.router("/")
async def index(request):
url = app.url_for('post_handler', post_id=5)
return redirect(url)
@app.route('posts/<post_id>')
async def post_handler(request, post_id):
return text('Post - {}'.format(post_id))
给url_for的关键字参数不是请求参数,它将包含在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以便构建一个URL。如果没有提供一个参数,或者一个参数与指定的类型不匹配,就会抛出一个URLBuildError
可以将多值参数传递给url
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
# /posts/5?arg_one=one&arg_one=two
经过测试访问/
我们会发现,url跳转到了/posts/5
,并打印出来Post - 5 的结果。
redirect
是从sanic.response
导入的方法,用于处理url的重定向。
网络套接字路由
WebSocket routes
websocket
可以通过装饰路由实现
实例:
@app.websocket('/feed')
async def feed(request, ws):
while True:
data = 'hello!'
print('Sending:' + data)
await ws.send(data)
data = await ws.recv()
print('Received:', data)
另外,添加websocket路由方法可以代替装饰器
async def feed(request, ws):
pass
app.add_websocket_route(my_websocket_handler, '/feed')
请求
request
常用类型
当一个端点收到一个HTTP请求时,路由功能被传递给一个 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
。如果该URL被解析,则args
字典将如下所示:{'key1': ['value1'], 'key2': ['value2']}
。请求的query_string
变量保存未解析的字符串值。
from sanic.response import json
@app.route("/query_string")
def query_string(request):
return json({ "parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string })
-
raw