|
|
目錄 |
|
|
|
|
|
1.1 |
|||||||
|
|
|
|
|
|
|
|
1.2 |
|||||||
|
|
|
|
|
|
|
|
1.3 |
|||||||
|
|
|
|
|
|
|
|
客户端部分 |
1.4 |
||||||
|
|
|
|
|
|
|
|
1.4.1 |
|||||||
|
|
|
|
|
|
|
|
|
|
1.4.1.1 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.2 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.3 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.4 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.5 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.6 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.7 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.8 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.9 |
|||||
|
|
|
|
|
|
||
|
|
1.4.1.10 |
|||||
|
|
|
|
|
|||
|
|
1.4.1.11 |
|||||
|
|
|
|
|
|||
|
|
1.4.1.12 |
|||||
|
|
|
1.4.1.13 |
||||
|
|
||||||
|
|
|
1.4.1.14 |
||||
|
|
||||||
|
|
|
|
||||
|
|
1.4.1.15 |
|||||
|
|
|
|
||||
|
|
1.4.1.16 |
|||||
|
|
|
|
||||
|
|
1.4.1.17 |
|||||
|
|
|
1.4.1.18 |
||||
|
|
||||||
|
|
|
1.4.1.19 |
||||
|
|
||||||
|
|
|
|
||||
|
|
1.4.1.20 |
|||||
|
|
|
|
||||
|
|
1.4.1.21 |
|||||
|
|
|
1.4.1.22 |
||||
|
|
||||||
|
|
|
|
|
|
|
|
1
|
|
|
1.4.1.23 |
|||||
|
|
1.4.1.24 |
||||||
|
|
|
|
|
|
|
|
|
|
|
1.4.1.25 |
||||||
|
|
|
|
|
|
|
|
|
|
|
1.4.1.26 |
||||||
|
|
|
|
|
|
|
|
|
|
|
1.4.1.27 |
||||||
|
|
|
|
|
|
|
|
|
|
|
1.4.1.28 |
||||||
|
|
|
|
|
|
|
|
|
|
|
1.4.1.29 |
||||||
|
|
|
|
|
|
|
|
|
|
1.4.2 |
|||||||
|
|
|
|
|
|
|
|
|
服务端部分 |
1.5 |
|||||||
|
|
|
|
|
|
|
||
|
1.5.1 |
|||||||
|
|
|
|
|
|
|
||
|
|
1.5.1.1 |
||||||
|
|
|
|
|
|
|||
|
|
1.5.1.2 |
||||||
|
|
|
|
|
|
|||
|
|
1.5.1.3 |
||||||
|
|
|
|
|
|
|||
|
|
1.5.1.4 |
||||||
|
|
|
|
|
|
|||
|
|
1.5.1.5 |
||||||
|
|
|
|
|
|
|||
|
|
|
1.5.1.5.1 |
|||||
|
|
|
|
1.5.1.5.2 |
||||
|
|
|
||||||
|
|
|
|
1.5.1.5.3 |
||||
|
|
|
||||||
|
|
|
|
1.5.1.5.4 |
||||
|
|
|
||||||
|
|
|
|
1.5.1.6 |
||||
|
|
|||||||
|
|
|
|
|
||||
|
|
1.5.1.7 |
||||||
|
|
|
|
|
||||
|
|
1.5.1.8 |
||||||
|
|
|
|
|
||||
|
1.5.2 |
|||||||
|
|
|
|
|
||||
|
|
1.5.2.1 |
||||||
|
|
|
|
|
||||
|
|
1.5.2.2 |
||||||
|
|
|
|
|
||||
|
|
1.5.2.3 |
||||||
|
|
|
|
|
||||
|
|
1.5.2.4 |
||||||
|
|
|
|
|
||||
|
|
1.5.2.5 |
||||||
|
|
|
|
|
||||
|
|
1.5.2.6 |
||||||
|
|
|
|
|
||||
|
|
1.5.2.7 |
||||||
|
|
|
|
|
|
|
|
|
2
|
1.5.2.8 |
||
1.5.2.9 |
|||
|
|
|
|
1.5.2.10 |
|||
|
|
|
|
1.5.2.11 |
|||
|
|
|
|
1.5.2.12 |
|||
|
1.5.2.13 |
||
|
1.5.2.14 |
||
|
|
||
1.5.2.15 |
|||
|
1.5.2.16 |
||
|
|
||
1.5.2.17 |
|||
|
1.5.2.18 |
||
|
|
||
1.5.2.19 |
|||
|
1.5.2.20 |
||
|
1.5.2.21 |
||
|
1.5.2.22 |
||
|
1.5.2.23 |
||
|
1.5.2.24 |
||
|
1.5.2.25 |
||
|
1.5.2.26 |
||
|
1.5.2.27 |
||
|
|
||
1.5.2.28 |
|||
|
1.5.2.29 |
||
|
1.5.2.30 |
||
|
1.5.2.31 |
||
|
1.5.2.32 |
||
|
1.5.2.33 |
||
|
|
||
1.5.2.34 |
|||
|
|
||
1.5.2.35 |
|||
|
1.5.2.36 |
||
|
1.5.2.37 |
||
|
|
|
|
3
|
|
1.5.3 |
|||
|
1.5.4 |
||||
|
|
|
|
|
|
|
1.5.5 |
||||
|
|
|
|
|
|
|
1.5.6 |
||||
|
|
|
|
|
|
|
1.5.7 |
||||
|
|
|
|
|
|
其他工具包 |
1.6 |
||||
|
|
|
|
|
|
|
1.6.1 |
||||
|
|
|
|
|
|
|
|
1.6.1.1 |
|||
|
|
|
1.6.1.2 |
||
|
|
||||
|
|
|
|
||
|
|
1.6.1.3 |
|||
|
|
|
1.6.1.4 |
||
|
|
||||
|
|
|
|
||
|
1.6.2 |
||||
|
|
|
|
|
|
|
|
1.6.2.1 |
|||
|
|
|
|
||
|
|
1.6.2.2 |
|||
|
|
|
|
||
|
|
1.6.2.3 |
|||
|
|
|
|
||
|
1.6.3 |
||||
|
|
|
|
|
|
|
|
1.6.3.1 |
|||
|
|
|
1.6.3.2 |
||
|
|
||||
|
|
|
1.6.3.3 |
||
|
|
||||
|
|
|
1.6.4 |
||
|
|||||
|
|
|
|
|
|
|
|
1.6.4.1 |
|||
|
|
|
1.6.4.2 |
||
|
|
||||
|
|
1.7 |
|||
|
|
|
|
||
|
1.7.1 |
||||
|
|
|
|
||
|
1.7.2 |
||||
|
|
|
|
||
|
1.7.3 |
||||
|
|
|
|
||
|
1.7.4 |
||||
|
|
|
|
||
|
1.7.5 |
||||
|
|
|
|
||
|
1.7.6 |
||||
|
|
|
|
||
|
1.7.7 |
||||
|
|
|
|
|
|
4
|
|
1.7.8 |
|||
|
1.7.9 |
||||
|
|
|
|
|
|
|
1.7.10 |
||||
|
|
|
|
|
|
|
1.7.11 |
||||
|
|
|
|
|
|
|
1.7.12 |
||||
|
|
|
|
||
|
1.7.13 |
||||
|
|
1.8 |
|||
各种杂项 |
|||||
|
|
|
|
||
|
1.8.1 |
||||
|
|
|
|
||
|
1.8.2 |
||||
|
|
|
|
||
|
1.8.3 |
||||
|
|
|
|
||
1.9 |
|||||
|
|
|
|
|
|
5
Introduction
aiohttp 中文文档
自己试着翻译,如有不当的地方请随时反馈~。:wink:
如果你是在GitHub上浏览,可以移步至GitBook或Love2.io中获得更好的阅读体 验。
进度:
基本翻译完成~,可能有很多不合理的地方,如有看到方便的话请随时反馈交流~, 谢谢~。
快速浏览目录:
6
前言
前言
这是我在学习过程中翻译出的第一本"书",带给和我一样英语方面有些欠缺又想要 学习查阅一些aiohttp相关方面知识的小伙伴。
在阅读中如果发现任何错误/不合适的翻译/奇怪的语句/错误的代码都可以可以 在GitHub发起Issue,我会尽快更正~。
翻译时官方文档还是旧版,现在变成了新版,内容上95%都是一样的,新版的目录 索引做的更好,我会尽快更新到最新版。
8
简介及快速开始
aiohttp是一个为Python提供异步HTTP 客户端/服务端编程,基于asyncio(Python用 于支持异步编程的标准库)的异步库。
核心功能:
同时支持服务端WebSockets组件和客户端WebSockets组件,开箱即用呦。 web服务器具有中间件,信号组件和可插拔路由的功能。
aiohttp库安装:
$ pip install aiohttp
你可能还想安装更快的cchardet库来代替chardet进行解码: $ pip install
cchardet
对于更快的客户端API DNS解析方案,aiodns是个很好的选择,极力推荐: $ pip
install aiodns
快速开始:
客户端例子:
9
简介及快速开始
import aiohttp import asyncio import async_timeout
async def fetch(session, url): with async_timeout.timeout(10):
async with session.get(url) as response: return await response.text()
async def main():
async with aiohttp.ClientSession() as session: html = await fetch(session, 'http://python.org') print(html)
loop = asyncio.get_event_loop() loop.run_until_complete(main())
服务端例子:
from aiohttp import web
async def handle(request):
name = request.match_info.get('name', "Anonymous")
text = "Hello, " + name return web.Response(text=text)
app = web.Application() app.router.add_get('/', handle) app.router.add_get('/{name}', handle)
web.run_app(app)
注意:
10
简介及快速开始
这篇文档的所有例子都是利用 async/await 语法来完成的,此语法介绍请 看PEP 492,此语法仅Python 3.5+有效。 如果你使用的是Python 3.4, 请将 await替换成yield from,将async 替换成带有 @corotine装饰器的def. 比如:
async def coro(...):
ret = await f()
应替换为:
@asyncio.coroutine
def coro(...):
ret = yield from f()
服务端指南:
源码:
该项目托管在Github.
如果你发现了一个bug或有一些改善的建议请随时提交。
该库使用Travis进行持续集成。
程序依赖:
Python 3.4.2+
chardet
multidict
async_timeout
yarl
11
简介及快速开始
可通过下面命令的安装:
$ pip install cchardet
可选aiodns进行DNS快速解析。极力推荐。 $ pip install aiodns
交流渠道:
随时在这里交流你的问题和想法。
gitter 聊天
我们还支持Stack Overflow. 在你的问题上添加aiohttp标签即可。
贡献
请在写一个PR前阅读下贡献须知。
作者和授权
aiohttp 大部分由 Nikolay Kim 和 Andrew Svetlov编写.
使用 Apache 2 授权并可随意使用。
随时在GitHub上提交PR来改善此项目。
对后续不再兼容的更改所采用的策略
一般的更改aiohttp 保持向后兼容.
在废弃某些公开API(方法,类,函数参数等.)后仍保证可以使用这些被废弃的API至 少一年半的时间直到某新版本完全弃用。
所有废弃的东西都会反映在文档中并给出已废弃提示。
有时我们会因一些必须要做的理由而打破某些我们定的规则。大多数原因是有只能 通过修改主要API解决的BUG出现,但我们会尽可能不让这种事情发生。
12
基本及进阶使用
客户端使用
发起请求
让我们从导入aiohttp模块开始:
import aiohttp
好啦,我们来尝试获取一个web页面。比如我们来获取下GitHub的时间轴。
async with aiohttp.ClientSession() as session:
async with session.get('https://api.github.com/events') as r
esp:
print(resp.status)
print(await resp.text())
我们现在有了一个 会话(session) 对象,由ClientSession对象赋值而来,还有一 个变量 resp ,它其实是ClientResponse对象。我们可以从这个响应对象中获取 我们任何想要的信息。协程方法 ClientSession.get() 的主要参数接受一个
HTTP URL。
发起HTTP POST请求我们可以使用协程方法ClientSession.post():
session.post('http://httpbin.org/post', data=b'data')
其他的HTTP方法也同样支持:
session.put('http://httpbin.org/put', data=b'data') session.delete('http://httpbin.org/delete') session.head('http://httpbin.org/get') session.options('http://httpbin.org/get') session.patch('http://httpbin.org/patch', data=b'data')
14
基本及进阶使用
注意:
不要为每个请求都创建一个会话。大多数情况下每个应用程序只需要一个会话 就可以执行所有的请求。 每个会话对象都包含一个连接池,可复用的连接和持
发起JSON请求:
每个会话的请求方法都可接受json参数。
async with aiohttp.ClientSession() as session:
async with session.post(json={'test': 'object'})
默认情况下会话(session)使用Python标准库里的json模块解析json信息。但还可使 用其他的json解析器。可以给ClientSession指定json_serialize参数来实现:
import ujson
async with aiohttp.ClientSession(json_serialize=ujson.dumps) as session:
async with session.post(json={'test': 'object'})
传递URL中的参数:
你可能经常想在URL中发送一系列的查询信息。如果你手动构建他们,这些信息会 以键值对的形式出现在?后面,比如: httpbin.org/get?key=val 。请求对象允 许你使用dict(字典,python中的数据类型)发送它们,使用 params 参数即可。 例如: 如果你要把 key1=value1,key2=value2 放到 httpbin.org/get 后面, 你可以用下面的方式:
params = {'key1': 'value1', 'key2': 'value2'} async with session.get('http://httpbin.org/get',
params=params) as resp:
assert str(resp.url) == 'http://httpbin.org/get?key2=value2& key1=value1'
15
基本及进阶使用
看,URL已经被正确的编码啦。 同键不同值的并联字典(MultiDict) 也同样支 持。 可使用带有两个tuples(元组,python中的数据类型)的list(列表,python中的数 据类型)来构建:
params = [('key', 'value1'), ('key', 'value2')]
async with session.get('http://httpbin.org/get',
params=params) as r:
assert str(r.url) == 'http://httpbin.org/get?key=value2&key=
value1'
同样也允许你传递str(字符串)给params,但要小心一些不能被编码的字 符。 + 就是一个不能被编码的字符:
async with session.get('http://httpbin.org/get',
params='key=value+1') as r:
assert str(r.url) == 'http://httpbin.org/get?key=value+1
'
注意:
aiohttp会在发送请求前标准化URL。 域名部分会用IDNA 编码,路径和查询条 件会重新编译(requoting)。 比如: URL('http://example.com/путь%30? a=%31') 会被转化
为URL('http://example.com/%D0%BF%D1%83%D1%82%D1%8C/0?a=1') 如果
服务器需要接受准确的表示并不要求编译URL,那标准化过程应是禁止的。 禁 止标准化可以使用 encoded=True :
await session.get(URL('http://example.com/%30', encoded=True
))
警告:
传递params时不要用 encode=True ,这俩参数不能同时使用。
获取响应内容
16
基本及进阶使用
我们可以读取服务器的响应内容。想想我们获取GitHub时间轴的例子:
async with session.get('https://api.github.com/events') as resp:
print(await resp.text())
这样会打印出类似于下面的信息:
.
aiohttp 将会自动解码内容。你可以为text()方法指定编码(使用encoding参数):
await
获取二进制响应内容
你也可以以字节形式获取响应,这样得到的就不是文本了:
print(await resp.read())
..
gzip 和 defalte 传输编码会自动解码。 你也可以使其支持 brotli 传输编码
的解码,只需安装brotlipy即可。
获取JSON响应内容
以防你需要处理JSON数据,内置了一个JSON解码器:
async with session.get('https://api.github.com/events') as resp: print(await resp.json())
如果JSON解码失败,json()方法将会抛出一个异常。你还可以在调用json()时指定 编码器和解码器函数。
17
基本及进阶使用
注意:
这些方法会读出内存中所有响应的内容。如果你要读非常多的数据,考虑使用 流式响应方法进行读取。请看之后的文档。
获取流式响应内容
read(), json(), text()等方法使用起来很方便,但也要注意谨慎地使用。上述方法会 将所有的响应内容加载到内存。举个例子,如果你要下载几个G的文件,这些方法 还是会将所有内容都加载到内存,内存会表示"臣妾做不到啊~"(如果内存不够的 话)。作为代替你可以用content属性。content其实是 aiohttp.StreamReader类的 实例。 gzip 和 deflate 传输编码同样会自动解码。
async with session.get('https://api.github.com/events') as resp: await resp.content.read(10)
一般情况下你可以使用下列模式将内容保存在一个文件中:
with open(filename, 'wb') as fd:
while True:
chunk = await resp.content.read(chunk_size)
if not chunk:
break
fd.write(chunk)
在使用content读了数据后,就不要在用read(), json(), text()了。
获取请求信息
ClientResponse(客户端响应)对象含有request_info(请求信息),主要是url和 headers信息。 raise_for_status结构体上的信息会被复制给ClientResponseError实
例。
自定义Headers
18
基本及进阶使用
如果你需要给某个请求添加HTTP头,可以使用headers参数,传递一个dict对象即 可。 比如,如果你想给之前的例子指定
import json
url = 'https://api.github.com/some/endpoint' payload = {'some': 'data'}
headers =
await session.post(url, data=json.dumps(payload), headers=headers)
自定义Cookies
发送你自己的cookies给服务器,你可以为ClientSession对象指定cookies参数:
url = 'http://httpbin.org/cookies' cookies = {'cookies_are': 'working'}
async with ClientSession(cookies=cookies) as session: async with session.get(url) as resp:
assert await resp.json() == { "cookies": {"cookies_are": "working"}}
注意:
访问 httpbin.org/cookies 会看到以JSON形式返回的cookies。查阅会话 中的cookies请看ClientSession.cookie_jar。
发起更复杂的POST请求
一般来说,如果你想以表单形式发送一些数据 - 就像HTML表单。那么只需要简单 的将一个dict通过data参数传递就可以。传递的dict数据会自动编码:
19
基本及进阶使用
payload = {'key1': 'value1', 'key2': 'value2'} async with session.post('http://httpbin.org/post',
data=payload) as resp:
print(await resp.text())
{
...
"form": { "key2": "value2", "key1": "value1"
},
...
}
如果你想发送非表单形式的数据你可用 str(字符串) 代替 dict(字典) 。这些数 据会直接发送出去。 例如,GitHub API v3 接受JSON编码POST/PATCH数据:
import json
url = 'https://api.github.com/some/endpoint' payload = {'some': 'data'}
async with session.post(url, data=json.dumps(payload)) as resp:
...
上传多部分编码文件:
url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}
await session.post(url, data=files)
你也可以显式地设置文件名,文件类型:
20
基本及进阶使用
url = 'http://httpbin.org/post' data = FormData() data.add_field('file',
open('report.xls', 'rb'), filename='report.xls',
await session.post(url, data=data)
如果你把一个文件对象传递给data参数,aiohttp会自动将其以流的形式上传。查 看StreamReader以获取支持的格式信息。
参见:
流式上传
aiohttp 支持多种形式的流式上传,允许你直接发送大文件而不必读到内存。
下面是个简单的例子,提供类文件对象即可:
with
await session.post('http://httpbin.org/post', data=f)
或者你也可以使用aiohttp.streamer对象:
21
基本及进阶使用
@aiohttp.streamer
def file_sender(writer, file_name=None):
with open(file_name, 'rb') as f: chunk = f.read(2**16) while chunk:
yield from writer.write(chunk) chunk = f.read(2**16)
# 之后你可以使用’file_sender‘传递给data:
async with session.post('http://httpbin.org/post', data=file_sender(file_name='huge_file'))
as resp:
print(await resp.text())
同样可以使用StreamReader对象.
我们来看下如何把来自于另一个请求的内容作为文件上传并计算其SHA1值:
async def feed_stream(resp, stream): h = hashlib.sha256()
while True:
chunk = await resp.content.readany() if not chunk:
break h.update(chunk) stream.feed_data(chunk)
return h.hexdigest()
resp = session.get('http://httpbin.org/post') stream = StreamReader()
loop.create_task(session.post('http://httpbin.org/post', data=st ream))
file_hash = await feed_stream(resp, stream)
22
基本及进阶使用
因为响应对象的content属性是一个 StreamReader 实例,所以你可以将get和post 请求连在一起用:
r = await session.get('http://python.org')
await session.post('http://httpbin.org/post',
data=r.content)
上传预压缩过的数据
上传一个已经压缩过的数据,需要为Headers中的
async def my_coroutine(session, headers, my_data):
data = zlib.compress(my_data)
headers =
async with session.post('http://httpbin.org/post',
data=data,
headers=headers)
pass
ClientSession可以在多个请求之间共享cookies:
async with aiohttp.ClientSession() as session: await session.get(
'http://httpbin.org/cookies/set?my_cookie=my_value')
filtered = session.cookie_jar.filter_cookies('http://httpbin
.org')
assert filtered['my_cookie'].value == 'my_value'
async with session.get('http://httpbin.org/cookies') as r: json_body = await r.json()
assert json_body['cookies']['my_cookie'] == 'my_value'
你也可以为所有的会话请求设置headers:
23
基本及进阶使用
async with aiohttp.ClientSession( headers={"Authorization": "Basic bG9naW46cGFzcw=="}) as sess
ion:
async with session.get("http://httpbin.org/headers") as r: json_body = await r.json()
assert json_body['headers']['Authorization'] == \ 'Basic bG9naW46cGFzcw=='
ClientSession支持持久连接和连接池,可直接使用,不需要额外操作。
安全cookies
ClientSession中的默认的aiohttp.CookiesJar使用的是严苛模式,RFC 2109明确 禁止使用ip地址形式的URL携带cookies信息。比如: http://127.0.0.1:80/cookie 这样 很好,不过有些时候我们测试时需要允许携带cookies。在aiohttp.CookiesJar中传 递unsafe=True来实现这一效果:
jar = aiohttp.CookieJar(unsafe=True)
session = aiohttp.ClientSession(cookie_jar=jar)
使用虚假Cookie Jar
有时不想处理cookie。这时可以在会话中使用aiohttp.DummyCookieJar来达到目 的。
jar = aiohttp.DummyCookieJar()
session = aiohttp.ClientSession(cookie_jar=jar)
使用连接器
想要调整请求的传输层你可以为ClientSession及其同类组件传递自定义的连接 器。例如:
24
基本及进阶使用
conn = aiohttp.TCPConnector()
session = aiohttp.ClientSession(connector=conn)
注解:
不要给多个会话对象使用同一个连接器,某一会话对象拥有其所有权。
参见:
查看连接器部分了解更多不同的连接器类型和配置选项信息。
限制连接池的容量
限制同一时间打开的连接数可以传递limit参数:
conn = aiohttp.TCPConnector(limit=30)
这样就将总数限制在30.
默认情况下是100.
如果你不想有限制,传递0即可:
conn = aiohttp.TCPConnector(limit=0)
限制同一时间在同一个端点(( host , port , is_ssl ) 3者都一样的情况)打开的 连接数可指定limit_per_host参数:
conn = aiohttp.TCPConnector(limit_per_host=30)
这样会限制在30. 默认情况下是0(也就是不做限制)。
使用自定义域名服务器
25
基本及进阶使用
底层需要aiodns支持:
from aiohttp.resolver import AsyncResolver
resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"]) conn = aiohttp.TCPConnector(resolver=resolver)
为TCP sockets添加SSL控制:
默认情况下aiohttp总会对使用了HTTPS协议(的URL请求)查验其身份。但也可 将verify_ssl设置为 False 让其不检查:
r = await session.get('https://example.com', verify_ssl=False)
如果你需要设置自定义SSL信息(比如使用自己的证书文件)你可以创建一 个ssl.SSLContext实例并传递到ClientSession中:
sslcontext = ssl.create_default_context(
r = await session.get('https://example.com', ssl_context=sslcont
ext)
如果你要验证自签名的证书,你也可以用之前的例子做同样的事,但是用的是
load_cert_chain():
sslcontext = ssl.create_default_context(
sslcontext.load_cert_chain('/path/to/client/public/device.pem', '/path/to/client/private/device.jey') r = await session.get('https://example.com', ssl_context=sslcont
ext)
SSL验证失败时抛出的错误:
aiohttp.ClientConnectorSSLError:
26
基本及进阶使用
try:
await session.get('https://expired.badssl.com/')
except aiohttp.ClientConnectorSSLError as e:
assert isinstance(e, ssl.SSLError)
aiohttp.ClientConnectorCertificateError:
try:
await session.get('https://wrong.host.badssl.com/')
except aiohttp.ClientConnectorCertificateError as e:
assert isinstance(e, ssl.CertificateError)
如果你需要忽略所有SSL的错误:
aiohttp.ClientSSLError:
try:
await session.get('https://expired.badssl.com/') except aiohttp.ClientSSLError as e:
assert isinstance(e, ssl.SSLError)
try:
await session.get('https://wrong.host.badssl.com/') except aiohttp.ClientSSLError as e:
assert isinstance(e, ssl.CertificateError)
你还可以通过SHA256指纹验证证书:
27
基本及进阶使用
#Attempt to connect to https://www.python.org
#with a pin to a bogus certificate:
bad_fingerprint = b'0'*64 exc = None
try:
r = await session.get('https://www.python.org', fingerprint=bad_fingerprint)
except aiohttp.FingerprintMismatch as e: exc = e
assert exc is not None
assert exc.expected == bad_fingerprint
#www.python.org cert's actual fingerprint assert exc.got == b'...'
注意这是以DER编码的证书的指纹。如果你的证书是PEM编码,你需要转换成DER 格式:
openssl x509
注解:
提示: 从16进制数字转换成二进制字节码,你可以用binascii.unhexlify().
TCPConnector中设置的verify_ssl, fingerprint和ssl_context都会被当做默认的 verify_ssl, fingerprint和ssl_context,ClientSession或其他同类组件中的设置会覆
盖默认值。
警告:
verify_ssl 和 ssl_context是互斥的。 MD5和SHA1指纹虽不赞成使用但是是支
持的 - 这俩是非常不安全的哈希函数。
Unix 域套接字
如果你的服务器使用UNIX域套接字你可以用UnixConnector:
28
基本及进阶使用
conn = aiohttp.UnixConnector(path='/path/to/socket')
session = aiohttp.ClientSession(connector=conn)
代理支持
aiohttp 支持 HTTP/HTTPS形式的代理。你需要使用proxy参数:
async with aiohttp.ClientSession() as session: async with session.get("http://python.org",
proxy="http://some.proxy.com") as res
p:
print(resp.status)
同时支持认证代理:
async with aiohttp.ClientSession() as session: proxy_auth = aiohttp.BasicAuth('user', 'pass') async with session.get("http://python.org",
proxy="http://some.proxy.com", proxy_auth=proxy_auth) as resp:
print(resp.status)
也可将代理的验证信息放在url中:
session.get("http://python.org",
proxy="http://user:pass@some.proxy.com")
与requests(另一个广受欢迎的http包) 不同,aiohttp默认不会读取环境变量中的 代理值。但你可以通过传递 trust_env=True 来让aiohttp.ClientSession读取
HTTP_PROXY或HTTPS_PROXY环境变量中的代理信息(不区分大小写)。
async with aiohttp.ClientSession() as session:
async with session.get("http://python.org", trust_env=True)
as resp:
print(resp.status)
29
基本及进阶使用
查看响应状态码
我们可以查询响应状态码:
async with session.get('http://httpbin.org/get') as resp:
assert resp.status == 200
获取响应头信息
我们可以查看服务器的响应信息, ClientResponse.headers使用的数据类型 是CIMultiDcitProxy:
>>> resp.headers
这是一个特别的字典,它只为HTTP头信息而生。根据 RFC 7230,HTTP头信息中 的名字是不分区大小写的。同时也支持多个不同的值对应同一个键。
所以我们可以通过任意形式访问它:
>>>
>>>
所有的header信息都是由二进制数据转换而来,使用带有 surrogateescape 选项
用ClientReponse.resp.raw_headers来查看原形:
30
基本及进阶使用
>>>resp.raw_headers ((b'SERVER', b'nginx'),
(b'DATE', b'Sat, 09 Jan 2016 20:28:40 GMT'),
获取响应cookies:
如果某响应包含一些Cookies,你可以很容易地访问他们:
url = 'http://example.com/some/cookie/setting/url'
async with session.get(url) as resp:
print(resp.cookies['example_cookie_name'])
注意:
响应中的cookies只包含重定向链中最后一个请求中的
获取响应历史
如果一个请求被重定向了,你可以用history属性查看其之前的响应:
>>>resp = await session.get('http://example.com/some/redirect/'
)
>>>resp
<ClientResponse(http://example.com/some/other/url/) [200]>
>>>resp.history (<ClientResponse(http://example.com/some/redirect/) [301]>,)
如果没有重定向或 allow_redirects 设置为 False ,history会被设置为空。
31
基本及进阶使用
使用WebSockets
aiohttp提供开箱即用的客户端websocket。 你需要使
用aiohttp.ClientSession.ws_connect()协程对象。它的第一个参数接受URL,返 回值是ClientWebSocketResponse,这样你就可以用响应的方法与websocket服 务器进行通信。
session = aiohttp.ClientSession()
async with session.ws_connect('http://example.org/websocket') as ws:
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT: if msg.data == 'close cmd':
await ws.close() break
else:
await ws.send_str(msg.data + '/answer') elif msg.type == aiohttp.WSMsgType.CLOSED:
break
elif msg.type == aiohttp.WSMsgType.ERROR: break
你只能使用一种读取方式(例如 await ws.receive() 或者 async for msg in
ws: )和写入方法,但可以有多个写入任务,写入任务也是异步完成的
(ws.send_str('data') )。
设置超时
默认情况下每个IO操作有5分钟超时时间。可以通过给ClientSession.get()及其同 类组件传递 timeout 来覆盖原超时时间:
async with session.get('https://github.com', timeout=60) as r:
...
32
基本及进阶使用
None 或者 0 则表示不检测超时。 还可通过调用async_timeout.timeout上下文
管理器来为连接和解析响应内容添加一个总超时时间:
import async_timeout
with async_timeout.timeout(0.001):
async with session.get('https://github.com') as r: await r.text()
注意:
超时时间是累计的,包含如发送情况,重定向,响应解析,处理响应等所有操
作在内...
愉快地结束:
当一个包含 ClientSession 的 async with 代码块的末尾行结束时(或直接调用
了.close() ),因为asyncio内部的一些原因底层的连接其实没有关闭。在实际使 用中,底层连接需要有一个缓冲时间来关闭。然而,如果事件循环在底层连接关闭 之前就结束了,那么会抛出一个 资源警告: 存在未关闭的传输(通道)
( ResourceWarning: unclosed transport ),如果警告可用的话。 为了避免这种 情况,在关闭事件循环前加入一小段延迟让底层连接得到关闭的缓冲时间。 对于非
SSL的 ClientSession , 使用0即可( await asyncio.sleep(0) ):
async def read_website():
async with aiohttp.ClientSession() as session:
async with session.get('http://example.org/') as respons
e:
await response.read()
loop = asyncio.get_event_loop() loop.run_until_complete(read_website())
#
33
基本及进阶使用
对于使用了SSL的 ClientSession , 需要设置一小段合适的时间:
...
#Wait 250 ms for the underlying SSL connections to close loop.run_until_complete(asyncio.sleep(0.250)) loop.close()
合适的时间因应用程序而异。
当asyncio内部的运行机制改变时就可以让aiohttp去等待底层连接关闭在退出啦,上 面这种额外的方法总会废弃啦。你也可以跟进问题#1925来参与改进。
34
客户端部分参考
客户端会话(Client Session):
客户端会话(Client Session)是比较推荐使用的发起HTTP请求的接口。
会话(Session)封装有一个连接池(连接器实例),默认支持持久连接。除非你要连接 非常多不同的服务器,否则建议你在你的应用程序中只使用一个会话(Session),这 样有利于连接池.
使用案例:
import aiohttp import asyncio
async def fetch(client):
async with client.get('http://python.org') as resp: assert resp.status == 200
return await resp.text()
async def main():
async with aiohttp.ClientSession() as client: html = await fetch(client) print(html)
loop = asyncio.get_event_loop() loop.run_until_complete(main())
新增于0.17版本。
客户端会话(ClientSession)支持使用上下文管理器在结束时自动关闭。
class aiohttp.ClientSession(\, connector=None, loop=None, cookies=None, headers=None, skip_auto_headers=None, auth=None, json_serialize=json.dumps, version=aiohttp.HttpVersion11, cookie_jar=None, read_timeout=None, conn_timeout=None, raise_for_status=False, connector_owner=True, auto_decompress=True, proxies=None)*
该类用于创建客户端会话以及发起请求。
(参数)Parameters:
35
客户端部分参考
connector (aiohttp.connector.BaseConnector) – 基础连接器(BaseConnector)
的子类实例,用于支持连接池。
loop –
<a
TP请求。
如果loop为None,则从connector中获取(如果也有的话)。 一般用asyncio.get_event_loop()来获取事件循环。
自2.0版本后已被弃用。(2.0版本发布日是2017/3/20,之前的文档内写明 遭到弃用的还是会保留一年半。)
cookies (dict) – 发送请求时所携带的cookies(可选)。
headers –
所有请求发送时携带的HTTP头(可选)。
docs.python.org/3/library/collections.abc.html#collections.a
bc.Mapping">Mapping</a>.(例如: <a href="https://docs.python.o
rg/3/library/stdtypes.html#dict">dict</a>, <a href="https://
multidict.readthedocs.io/en/stable/multidict.html#multidict.
CIMultiDict">CIMultiDict</a>)
skip_auto_headers –
跳过会自动产生的headers值.
auth(aiohttp.BasicAuth) – 表示 HTTP基本认证的对象。 version –
使用的HTTP版本,默认是HTTP 1.1。 新增于0.21版本。
cookie_jar –
36
客户端部分参考
Cookie jar ,AbstractCookieJar的实例。
默认情况下每个会话实例都有自己的私有cookie jar用于自动处理coo kies,但用户也可使用自己的jar重新定义其行为。
在代理模式下不会处理cookies。
如果不需要cookie处理,可以传递一个aiohttp.helper.DummyCook ieJar实例。
新增于0.22版本。
json_serialize (可调用对象) –
可调用的Json序列化对象,默认是json.dumps()函数。
raise_for_status (布尔类型) –
每完成一个响应就自动调用 ClientResponse.raise_for_status( ), 默认是False.
新增于2.0版本。
read_timeout (浮点数) –
请求操作的超时时间。read_timeout是累计自所有的请求操作(请求, 重定向,响应,数据处理)。默认情况下是 5\*60秒(5分钟)。传递None或0来禁 用超时检测。
conn_timeout (浮点数) –
建立连接的超时时间(可选)。0或None则禁用超时检测。
connector_owner (布尔类型) –
会话关闭时一同关闭连接器实例。
传递False让构造器允许在多个会话间共享连接池,不过cookies等信息
是不共享的。
新增于2.1版本。
auto_decompress (布尔类型) –
37
客户端部分参考
自动解压响应体。 新增于2.3版本。
trust_env(布尔类型) –
若该参数设置为True则从环境变量HTTP_PROXY / HTTPS_PROXY中获 取代理信息。
新增于2.3版本。
closed
若会话已关闭则返回True,否则返回False。
该属性只读。
connector
返回aiohttp.connector.BaseConnector的派生实例对象,connector通常
用于会话操作
该属性只读。
cookie_jar
返回会话中的cookies,是AbstractCookieJar的实例对象。
返回的对象可以用于访问和修改cookie jar中的内容。
该属性只读。
新增于1.0版本。
requote_redirect_url
aiohttp默认会重新编译传入的urls,如果服务器需要未编译的url。将其设置 为False来禁用编译。
新增于2.1版本。
注意:
设置后会影响这之后的所有请求。
loop
返回用于会话创建的循环(loop)实例对象。
该属性只读。
38
客户端部分参考
coroutine
Parameters:
method (字符串) – HTTP方法,接受字符串。 url – 请求URL, 接受字符串或URL对象。
params –
可传入Maaping对象,有键值对的元组或字符串,会在发送新请求 时作为查询字符串发送。不会对之后的重定向请求也传入查询字符串。(该参数可 选)
允许传入的值参考:
collections.abc.Mapping : dict, aiohttp.Multi
Dict 或 aiohttp.MultiDictProxy
collections.abc.Iterable: tuple 或 list
已被url编码好的str(aiohttp不能对str进行url编码)
data – 放在请求体中的数据,接受字典(dcit), 字节或类文件对象。(该参数可 选)
json – 任何可被json解码的python对象(该参数可选)。注: json不能与data参数
同时使用。
headers (字典) – 发送请求时携带的HTTP头信息。(该参数可选) skip_auto_headers – 跳过会自动生成的headers信息。
接受str或istr。(该参数可选)
auth(aiohttp.BasicAuth) – 接受HTTP基本认证对象。(该参数可选)
allow_redirects(布尔类型) – 如果设为False,则不允许重定向。默认是True。
(该参数可选)
compress (布尔类型) – 如果请求内容要进行deflate编码压缩可以设为True。
39
客户端部分参考
None。(该参数可选)
chunked (整数) – 允许使用分块编码传输。开发者想使用分块数据流时,用它
expect100(布尔类型) 服务器返回100时将等待响应(返回100表示服务器正在处 理数据)。默认是False。(该参数可选)
read_until_eof (布尔类型)
proxy – 代理URL,接受字符串或URL对象(该参数可选)
proxy_auth(aiohttp.BaicAuth) – 传入表示HTTP代理基础认证的对象。(该参数
可选)
timeout(整数) – 覆盖会话的超时时间。
verify_ssl(布尔类型) – 对HTTPS请求验证SSL证书(默认是验证的)。如果某些
网站证书无效的话也可禁用。
新增于2.3版本。
fringerprint (字节) – 传递想要验证的证书(使用DER编码的证书)的SHA256值来
验证服务器是否可以成功匹配。对证书固定非常有用。
警告: 不赞成使用不安全的MD5和SHA1哈希值。
新增于2.3版本。
ssl_context (ssl.SLLContext) –
ssl上下文(管理器)用于处理HTTPS请求。(该参数可选)
ssl_context 用于配置证书授权通道,支持SSL选项等作用。
新增于2.3版本。
proxy_headers(abc.Mapping) – 如果该参数有提供内容,则会将其做为HTTP headers发送给代理。
新增于2.3版本。
40
客户端部分参考
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.0版本新增的内容: 增加 proxy 和 proxy_auth 参数。添加 timeout 参
数。
1.1版本修改的内容: URLs可以是字符串和URL对象。 coroutine
该方法会进行GET请求。
kwargs用于指定些request的参数。
*Parameters:
url - 请求的URL, 字符串或URL对象。
allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True。
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.1版本修改的内容: URLs可以是字符串和URL对象。
coroutine
该方法会执行POST请求。
kwargs用于指定些request的参数。
*Parameters:
url - 请求的URL, 字符串或URL对象。
data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)。
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.1版本修改的内容: URLs可以是字符串和URL对象。
coroutine
该方法会执行PUT请求。
kwargs用于指定些request的参数。
*Parameters:
url - 请求的URL, 字符串或URL对象。
data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)。
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.1版本修改的内容: URLs可以是字符串和URL对象。
41
客户端部分参考
coroutine
该方法会执行DELETE请求。
kwargs用于指定些request的参数。
*Parameters:
url - 请求的URL, 字符串或URL对象。
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.1版本修改的内容: URLs可以是字符串和URL对象。
coroutine
该方法执行HEAD请求。
kwargs用于指定些request的参数。
*Parameters:
url - 请求的URL, 字符串或URL对象。
allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True。
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.1版本修改的内容: URLs可以是字符串和URL对象。
coroutine
该方法执行options请求。
kwargs用于指定些request的参数。
*Parameters:
url - 请求的URL, 字符串或URL对象。
allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True。
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.1版本修改的内容: URLs可以是字符串和URL对象。
coroutine
该方法执行patch请求。
kwargs用于指定些request的参数。
*Parameters:
url - 请求的URL, 字符串或URL对象。
42
客户端部分参考
data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)。
返回ClientResponse:
返回一个客户端响应(client response)对象。
1.1版本修改的内容: URLs可以是字符串和URL对象。
coroutine
创建一个websocket连接。返回ClientWebSocketResponse对象。
Parameters:
url - Websocket服务器url, str或URL对象。 protocols(元组) - websocket 协议。
timeout(浮点数) - 超过超时时间后关闭websocket。默认是10秒。
receive_timeout(浮点数) - 超过超时时间后不在从websocket接受消息。默认是 None(无限时间)。
auth (aiohttp.BasicAuth) - 表示HTTP基础认证的对象。(该参数可选) autoclose(布尔类型)- 从服务器接受完消息后自动关闭websocket. 如果该参数 为False,那么需要手动进行关闭。
autoping(布尔类型) - 当从服务器收到ping信息后自动回复pong信息。 heartbeat(浮点数) - 每到心跳时间则发送ping信息并等待pong信息想要,如果 没有收到pong信息则关闭连接。
origin(字符串) - 发送到服务器的origin信息。
proxy(字符串) - 代理URL,接受字符串或URL对象(该参数可选)。
proxy_auth(aiohttp.BasicAuth) - 表示代理HTTP基础认证的对象。(该参数可
选)
verify_ssl(布尔类型) - 对HTTPS请求验证SSL证书(默认是验证的)。如果某些 网站证书无效的话也可禁用。(该参数可选) 新增于2.3版本。
fringerprint (字节) – 传递想要验证的证书(使用DER编码的证书)的SHA256值来
验证服务器是否可以成功匹配。对证书固定非常有用。
警告: 不赞成使用不安全的MD5和SHA1哈希值。 新增于2.3版本。
ssl_context (ssl.SLLContext) –
43
客户端部分参考
ssl上下文(管理器)用于处理HTTPS请求。(该参数可选)
ssl_context 用于配置证书授权通道,支持SSL选项等作用。
新增于2.3版本。
proxy_headers(abc.Mapping) – 如果该参数有提供内容,则会将其做为HTTP headers发送给代理。
新增于2.3版本。
compress (整数) -
`!此处有疑问!`
原文:
```
Enable
ort.
0 for disable, 9 to 15 for window bit supp ort. Default value is 0.
```
```
由于没用过websocket这里不是很懂,从源码来看是一个head
ers中的信息。可自行查看https://tools.ietf.org/html/rfc7692#secti
```
新增于2.3版本。
coroutine close()
关闭底层连接器。
释放所有占用的资源。
detach()
从会话中分离出连接器但不关闭连接器。(之前说过每个会话拥有某连接器的 所有权。)
会话会切换到关闭状态。
44
客户端部分参考
基本API。
我们鼓励使用客户端会话(ClientSession)但同时也提供一个可以更简单的发起HTTP 请求的协程方法。
基本API对于不需要持久连接(keepaliving), cookies和复杂的连接附件(如SSL证书) 的HTTP请求来说是比较好用的。
coroutine aiohttp.request(method, url, \, params=None, data=None, json=None, headers=None, cookies=None, auth=None, allow_redirects=True, max_redirects=10,
异步的执行HTTP请求。返回一个响应对象(ClientResponse或其衍生对象)
(参数)Parameters:
method (字符串) - HTTP方法,接受字符串。 url - 请求URL, 接受str或URL对象。
params (字典) – 与原URL组合成带有查询条件的新URL。(该参数可选) data – 放在请求体中的数据,接受字典(dcit), 字节或类文件对象。(该参数可 选)
json – 任何可被json解码的python对象(改参数可选)。注: json不能与data参数
同时使用。
headers (字典) – 发送请求时携带的HTTP头信息。(该参数可选) cookies (字典) - 请求时携带的cookies。(该参数可选) auth(aiohttp.BasicAuth) – 用于表示HTTP基础认证的对象。(该参数可选) allow_redirects(布尔类型) – 如果设为False,则不允许重定向。默认是True。
(该参数可选)
version (aiohttp.protocols.HttpVersion) - 请求时选用的HTTP版本。(该参数可
选)
compress (布尔类型) – 如果请求内容要进行deflate编码压缩可以设为True。
chunked (整数) – 允许使用分块编码传输。开发者想使用分块数据流时,用它
45
客户端部分参考
expect100(布尔类型) 服务器返回100时将等待响应(返回100表示服务器正在处 理数据)。默认是False。(该参数可选)
connector(aiohttp.connector.BaseConnector) - 接受BaseConnector的子类实
例用于支持连接池。
read_until_eof (布尔类型)
loop -
事件循环(event loop)
用于处理HTTP请求。如果参数为None,将会用asyncio.get_event_loop
()获取。 2.0版本后不赞成使用。
返回ClientResponse:
返回一个客户端响应对象。 小例子:
import aiohttp
async def fetch():
async with aiohttp.request('GET', 'http://python.org/') as r esp:
assert resp.status == 200 print(await resp.text())
1.1版本修改的内容: URLs可以是字符串和URL对象。
连接器
连接器用于支持aiohttp客户端API传输数据。 这俩是标准连接器:
1.TCPConnector 用于使用TCP连接(包括HTTP和HTTPS连接)。
2.UnixConnector 用于使用UNIX连接(大多数情况下都用于测试的目的)。 所有的连接器都应是BaseConnector的子类。
46
客户端部分参考
BaseConnector
class aiohttp.BaseConnector(\, keepalive_timeout=15, force_close=False,
limit=100, limit_per_host=0, enable_cleanup_closed=False, loop=None)*
所有连接器的基类。
(参数)Parameters:
keepalive_timeout(浮点数) - 释放后到再次使用的超时时间(可选)。禁用keep- alive可以传入0,或使用force_close=True。
limit(整数) - 并发连接的总数。如果为None则不做限制。(默认为100)
limit_per_host - 向同一个端点并发连接的总数。同一端点是具有相同 (host, port, is_ssl)信息的玩意 x 3! 如果是0则不做限制。(默认为0) force_close(布尔类型) - 连接释放后关闭底层网络套接字。(该参数可选) enable_cleanup_closed (布尔类型) - 一些SSL服务器可能会没有正确的完成 SSL关闭过程,这种时候asyncio会泄露SSL连接。如果设置为True,aiohttp会
在两秒后额外执行一次停止。此功能默认不开启。
loop -
事件循环(event loop)
用于处理HTTP请求。如果参数为None,将会用asyncio.get_event_loop( )获取。
2.0版本后不赞成使用。
closed
只读属性,如果连接器关闭则返回True。
force_close
只读属性,如果连接释放后关闭网络套接字则返回True否则返回False。
新增于0.16版本。
limit
并发连接的总数。如果是0则不做限制。默认是100.
limit_per_host
向同一个端点并发连接的总数。同一端点是具有相同 (host, port, is_ssl)信息 的玩意 x 3! 如果是None则不做限制。(默认为0)
只读属性。
47
客户端部分参考
close()
关闭所有打开的连接。
新增于2.0版本。
coroutine connect(request)
从连接池中获取一个空闲的连接,如果没有空闲则新建一个连接。
如果达到限制(limit)的最大上限则挂起直到有连接处于空闲为止。
(参数)Parameters:
request (aiohttp.client.ClientRequest) - 发起连接的请求对象。
返回:
返回连接(Connection)对象。
coroutine _create_connection(req)
建立实际连接的抽象方法,需被子类覆盖。
TCPConnector
class aiohttp.TCPConnector(\, verify_ssl=True, fingerprint=None,
use_dns_cache=True, ttl_dns_cache=10, family=0, ssl_context=None,
local_addr=None, resolver=None, keepalive_timeout=sentinel, force_close=False, limit=100, limit_per_host=0, enable_cleanup_closed=False, loop=None)*
用于使用TCP处理HTTP和HTTPS的连接器。
如果你不知道该用什么连接器传输数据,那就用它吧。
TCPConnector继承于BaseConnector.
接受BaseConnector所需的所有参数和几个额外的TCP需要的参数。
(参数)Parameters:
verify_ssl (布尔类型) –
对HTTPS请求验证SSL证书(默认是验证的)。如果某些网站证书无效的话也可
禁用。(该参数可选)
2.3版本后不赞成通过ClientSession.get()方法传递该参数。
fingerprint (字节码) -
48
客户端部分参考
传递所期望的SHA256值(使用DER编码)来验证服务器是否可以成功匹配。对证 书固定非常有用。
警告: 不赞成使用不安全的MD5和SHA1哈希值。 新增于0.16版本。
2.3版本后不赞成通过ClientSession.get()方法传递该参数。
use_dns_cache (布尔类型) -
使用内部缓存进行DNS查找,默认为True。
这个选项可能会加速建立连接的时间,有时也会些副作用。 新增于0.17版本。
自1.0版本起该参数默认为True。
ttl_dns_cache -
查询过的DNS条目的失效时间,None表示永不失效。默认是10秒。
默认情况下DNS会被永久缓存,一些环境中的一些HOST对应的IP地址会在特定 时间后改变。可以使用这个参数来让DNS刷新。
新增于2.0.8版本。
limit (整数) - 并发连接的总数。如果为None则不做限制。(默认为100)
limit_per_host - 向同一个端点并发连接的总数。同一端点是具有相同 (host, port, is_ssl)信息的玩意 x 3! 如果是0则不做限制。(默认为0)
resolver (aiohttp.abc.AbstructResolver) - 传入自定义的解析器实例。默认是 aiohttp.DefaultResolver(如果aiodns已经安装并且版本>1.1则是异步的)。
自定义解析器可以配置不同的解析域名的方法。
1.1版本修改的内容: 默认使用aiohttp.ThreadResolver, 异步版本在某
些情况下会解析失败。
family (整数) -
49
客户端部分参考
代表TCP套接字成员,默认有IPv4和IPv6.
IPv4使用的是socket.AF_INET, IPv6使用的是socket.AF_INET6. 0.18版本修改的内容: 默认是0,代表可接受IPv4和IPv6。可以传入socke t.AF_INET或socket.AF_INET6来明确指定只接受某一种类型。
ssl_context (ssl.SSLContext) -
ssl上下文(管理器)用于处理HTTPS请求。(该参数可选)
ssl_context 用于配置证书授权通道,支持SSL选项等作用。
local_addr (元组) -
包含(local_host, local_post)的元组,用于绑定本地socket。
新增于0.21版本。
force_close (布尔类型) - 连接释放后关闭底层网络套接字。(该参数可选)
enable_cleanup_closed(元组)(这里原文应该写错了,应该是布尔类型,不管
是之前的文档还是源码都是接受的布尔值。) -
一些SSL服务器可能会没有正确的完成SSL关闭过程,这种时候asyncio会泄
露SSL连接。如果设置为True,aiohttp会在两秒后额外执行一次停止。此功能默 认不开启。
verify_ssl
如果返回True则会进行ssl证书检测。
该属性只读。
ssl_context
返回用于https请求的ssl.SSLContext实例,该属性只读。
family
TCP套接字成员, 比如socket.AF_INET 或 socket.AF_INET6。
该属性只读。
50
客户端部分参考
dns_cache
如果DNS缓存可用的话返回True,否则返回False。
该属性只读。
新增于0.17版本。
cached_hosts
如果dns缓存可用,则返回已解析的域名缓存。
该属性只读,返回的类型为types.MappingProxyType。
新增于0.17版本。
fingerprint
返回传入的DER格式证书的MD5,SHA1或SHA256哈希值 ,如果没有的话会
返回None.
该属性只读。
新增于0.16版本。
clear_dns_cache(self, host=None, port=None)
清除内部DNS缓存。
如果host和port指定了信息会删除指定的这个,否则清除所有的。
新增于0.17版本。
UnixConnector
class aiohttp.UnixConnector(path, , conn_timeout=None, keepalive_timeout=30,
limit=100, force_close=False, loop=None)*
Unix 套接字连接器。
使用UnixConnector发送HTTP/HTTPS请求。底层通过UNIX套接字传输。
UNIX套接字对于测试并快速在同一个主机上的进程间建立连接非常方便。
UnixConnector继承于BaseConnector。
使用:
conn = UnixConnector(path='/path/to/socket')
session = ClientSession(connector=conn)
async with session.get('http://python.org') as resp:
51
客户端部分参考
接受所有BaseConnector的参数,还有一个额外的参数:
(参数)Parameters: path(字符串) - Unix套接字路径。
path
返回UNIX套接字路径,该属性只读。
Connection
class aiohttp.Connection
连接器对象中封装的单个连接。
终端用户不要手动创建Connection实例,但可调用BaseConnector.connect()来 获取Connection实例,这个方法是协程方法。
closed
只读属性,返回布尔值。如果该连接已关闭,释放或从某一连接器分离则返回Ture
。
loop
返回处理连接的事件循环。
transport
返回该连接的传输通道。
close()
关闭连接并强制关闭底层套接字。
release()
从连接器中将该连接释放。
底层套接字并未关闭,如果超时时间(默认30秒)过后该连接仍可用则会被重 新占用。
detach()
将底层套接字从连接中分离。
底层套接字并未关闭, 之后调用close()或release()也不会有空闲连接池对该
52
客户端部分参考
套接字有其他操作。
响应对象(Response object)
class aiohttp.ClientResponse
ClientSession.requests() 及其同类成员的返回对象。
用户不要创建ClientResponse的实例,它是由调用API获得的返回。
ClientResponse支持async上下文管理器:
resp = await client_session.get(url) async with resp:
assert resp.status == 200
这样退出后会自动释放。(详情看release()协程方法)
此语法于0.18版本开始支持。
version
返回响应的版本信息,是HttpVersion实例对象。
status
返回响应的HTTP状态码(整数),比如: 200。
reason
返回响应的HTTP叙述(字符串),比如"OK"。
method
返回请求的HTTP方法(字符串)。
url
53
客户端部分参考
返回请求的URL(URL对象)。
connection
返回处理响应的连接。
content
包含响应主体(StreamReader)的载体流。支持多种读取方法。服务器使用分块传 输编码时同样允许分块接受。
读取时可能会抛出aiohttp.ClientPayloadError,这种情况发生在响应对象 在接受所有的数据前就关闭了或有任何传输编码相关的错误如错误的压缩数据所造 成的不规则分块编码。
cookies
响应中的HTTP
kie)
headers
返回响应的HTTP头信息,是一个大小写不敏感的并联字典(CIMultiDictProxy)
。
raw_headers
返回原始HTTP头信息,未经编码,格式是键值对形式。
content_type
54
客户端部分参考
注意
charset
返回请求的主体的编码。
history
返回包含所有的重定向请求(都是ClientResponse对象,最开始的请求在最前面 )的序列,如果没用重定向则返回空序列。
close()
关闭响应和底层连接。
要关闭持久连接请看release().
coroutine read()
以字节码形式读取所有响应内容。
如果读取数据时得到一个错误将会关闭底层连接,否则将会释放连接。
如果不可读则会抛出aiohttp.ClientResponseError错误。
返回响应内容的字节码。
参见:
close(), release().
coroutine release()
一般不需要调用release。当客户端接受完信息时,底层连接将会自动返回到 连接池中。如果载体中的内容没有全部读完,连接也会关闭。
raise_for_status()
如果响应的状态码是400或更高则会抛出aiohttp.ClientResponseError
如果小于400则什么都不会做。
55
客户端部分参考
coroutine text(encoding=None)
读取响应内容并返回解码后的信息。
没有会用chardet获取。
如果有cchardet会优先使用cchardet。
如果读取数据时得到一个错误将会关闭底层连接,否则将会释放连接。
Parameters: encoding(字符串) - 指定以该编码方式解码内容,None则自动 获取编码方式(默认为None)。
Return 字符串: 解码后的内容。
注意
这两种方法都会拖慢执行效率。如果知道页面所使用的编码直接指定是比较好 的做法:
```
await
```
coroutine json(*, encoding=None, loads=json.loads,
content_type='application/json')
以JSON格式读取响应内容,解码和解析器可由参数指定。如果数据不能read则 会直接结束。
如果encoding为None,会使用cchardet或chardet获取编码。
Parameters:
encoding (字符串) - 传入用于解码内容的编码名,或None自动获取。 loads (可调用对象) - 用于加载JSON数据,默认是json.loads. content_type (字符串) -
Returns:
返回使用loads进行JSON编码后的数据,如果内容为空或内容只含空白
56
客户端部分参考
则返回None。
request_info
存放有headers和请求URL的nametuple(一种方便存放数据的扩展类,存在 于collections模块中。)属于aiohttp.RequestInfo实例。
ClientWebSocketResponse
使用协程方法aiohttp.ws_connect()或aiohttp.ClientSession.ws_connect()连接使 用websocket的服务器,不要手动来创建ClientWebSocketResponse。
class aiohttp.ClientWebSocketResponse
用于处理客户端websockets的类
closed
如果close()已经调用过或接受了CLOSE消息则返回True。
该属性只读。
protocol
调用start()后选择的websocket协议。
如果服务器和客户端所选协议不一致则是None。
get_extra_info(name, default=None)
返回从连接的transport中读取到的额外的信息。
exception()
如果有错误则返回那个错误,否则返回None。
ping(message=b'')
向服务器发送PING.
Parameters: message –
字符串或字节码。(可选)
coroutine send_str(data)
向服务器发送文本消息。
Parameters: data (字符串) – 要发送的消息.
Raises: 如果不是字符串会抛出TypeError错误。
57
客户端部分参考
coroutine send_bytes(data)
向服务器发送二进制消息。
Parameters: data – 要发送的消息。
Raises: 如果不是字节码,字节数组或memoryview将抛出TypeError错误。
coroutine send_json(data, *, dumps=json.dumps)
向服务器发送json字符串。
Parameters:
data – 要发送的消息.
dumps (可调用对象)
象。默认是json.dumps()
Raises:
RuntimeError – 如果连接没有启动或已关闭会抛出这个错误。 ValueError – 如果数据不是可序列化的对象会抛出这个错误。 TypeError – 如果由dumps调用后返回的不是字符串会抛出这个错误。
coroutine close(*, code=1000, message=b'')
用于向服务器发起挥手(CLOSE)信息,请求关闭连接。它会等待服务器响 应。这是一个异步方法,所以如果要添加超时时间可以用asyncio.wait()或 asyncio.wait_for()包裹。
Parameters:
code (整数) – 关闭状态码。
message –
(可选)
coroutine receive()
等待服务器传回消息并在接受后将其返回。
此方法隐式的处理PING, PONG, CLOSE消息。(不会返回这些消息)
Returns: WSMessage
coroutine receive_str()
调用receive()并判断该消息是否是文本。
Return 字符串: 服务器传回的内容。
Raises: 如果消息是二进制则会抛出TypeError错误。
58
客户端部分参考
coroutine receive_bytes()
调用receive()并判断该消息是否是二进制内容。
Return 字符串: 服务器传回的内容。
Raises: 如果消息是文本则会抛出TypeError错误。
coroutine receive_json(*, loads=json.loads)
调用receive_str()并尝试将JSON字符串转成Python中的dict(字典)。
Parameters:
loads (可调用对象) – 任何可接受字符串并返回字典的可调用对象。默认是 json.loads()
Return dict:
返回处理过的JSON内容。
Raises:
如果消息是二进制则会抛出TypeError错误。
如果不是JSON消息则会抛出ValueError错误。
RequestInfo
class aiohttp.RequestInfo
存放请求URL和头信息的namedtuple,使用ClientResponse.request_info属性
可访问。
url
请求的URL,是yarl.URL实例对象。
method
请求时使用的HTTP方法,如'GET', 'POST',是个字符串。
headers
请求时携带的HTTP头信息,是multidict.CIMultiDict 实例对象。
BasicAuth
59
客户端部分参考
class aiohttp.BasicAuth(login, password='', encoding='latin1')
用于协助进行HTTP基础认证的类。
Parameters:
login (字符串) - 用户名。 password (字符串) – 密码。
encoding (字符串) – 编码信息(默认是'latin1')。
一般在客户端 API中使用,比如给ClientSession.request()指定auth参数。
classmethod decode(auth_header, encoding='latin1')
解码HTTP基本认证信息。
Parameters:
auth_header (字符串) – 需要解码的 Authorization 头数据。 encoding (字符串) – (可选) 编码信息(默认是‘latin1’)
Returns:
返回解码后的认证信息。
classmethod from_url(url)
从URL中获取用户名和密码。
Returns: 返回BasicAuth,如果认证信息未提供则返回None。新增于2.3版 本。
encode()
将认证信息编码为合适的 Authorization头数据。
Returns: 返回编码后的认证信息。
CookieJar
class aiohttp.CookieJar(\, unsafe=False, loop=None)*
cookie jar实例对象可用在ClientSession.cookie_jar中。
jar对象包含用来存储内部cookie数据的Morsel组件。
可查看保存的cookies数量:
len(session.cookie_jar)
60
客户端部分参考
也可以被迭代:
for cookie in session.cookie_jar:
print(cookie.key)
print(cookie["domain"])
该类提供collections.abc.Iterable, collections.abc.Sized 和 aiohttp.AbstractCookieJar中的方法接口。
提供符合RFC 6265规定的cookie存储。
Parameters:
unsafe (布尔类型) – (可选)是否可从IP(的HTTP请求)中接受cookies。
oop (布尔类型) – 一个事件循环实例。请看aiohttp.abc.AbstractCookieJar。
2.0版本后不赞成使用。
update_cookies(cookies, response_url=None)
Parameters:
cookies – 需要collections.abc.Mapping对象(如dict, SimpleCookie) 或包含
cookies的可迭代键值对对象。
response_url (字符串) – cookies所属的URL,如果要共享cookies则不要填。 标准的cookies应是与服务器URL成对出现,只在向该服务器请求时被发送出 去,如果是共享的则会发送给所有的服务器。
filter_cookies(request_url)
返回与request_url相匹配的cookies,和能给这个URL携带的cookie(一般是 设置为None也就是共享的cookie)。
Parameters: response_url (字符串) – 需要筛选的URL。
Returns: 返回带有给定URL cookies的http.cookies.SimpleCookie。
save(file_path)
以pickle形式将cookies信息写入指定路径。
Parameters: file_path – 要写入的路径。字符串或pathlib.Path实例对象。
load(file_path)
从给定路径读取被pickle的cookies信息。
61
客户端部分参考
Parameters: file_path – 要导入的路径, 字符串或pathlib.Path实例对象。
class aiohttp.DummyCookieJar(\, loop=None)*
假人cookie jar,用于忽略cookie。
在一些情况下是很有用的: 比如写爬虫时不需要保存cookies信息,只要一直下 载内容就好了。
将它的实例对象传入即可使用:
jar = aiohttp.DummyCookieJar()
session = aiohttp.ClientSession(cookie_jar=DummyCookieJar())
Client exceptions
异常等级制度在2.0版本有较大修改。aiohttp只定义连接处理部分和服务器没有 正确响应的异常。一些由开发者引起的错误将使用python标准异常如ValueError, TypeError。
读取响应内容可能会抛出ClientPayloadError异常。该异常表示载体编码时有些 问题。比如有无效的压缩信息,不符合分块编码要求的分块内容或内容与content- length指定的大小不一致。
所有的异常都可当做aiohttp模块使用。
exception aiohttp.ClientError
所有客户端异常的基类。
继承于Exception。
class aiohttp.ClientPayloadError
该异常只会因读取响应内容时存在下列错误而抛出:
1. 存在无效压缩信息。
2. 错误的分块编码。
3.
继承于ClientError.
exception aiohttp.InvalidURL
不合理的URL会抛出此异常。比如不含域名部分的URL。
继承于ClientError和ValueError。
62
客户端部分参考
url
返回那个无效的URL, 是yarl.URL的实例对象。
Response errors
exception aiohttp.ClientResponseError
这个异常有时会在我们从服务器得到响应后抛出。
继承于ClientError
request_info
该属性是RequestInfo实例对象,包含请求信息。
code
该属性是响应的HTTP状态码。如200.
message
该属性是响应消息。如"OK"
headers
该属性是响应头信息。数据类型是列表。
history
该属性存储失败的响应内容,如果该选项可用的话,否则是个空元组。
元组中的ClientResponse对象用于处理重定向响应。
class aiohttp.WSServerHandshakeError
Web socket 服务器响应异常。
继承于ClientResponseError
class aiohttp.WSServerHandshakeError
Web socket 服务器响应异常。
继承于ClientResponseError
class aiohttp.ContentTypeError
无效的content type。
继承于ClientResponseError
新增于2.3版本。
63
客户端部分参考
Connection errors
class aiohttp.ClientConnectionError
该类异常与底层连接的问题相关。
继承于ClientError。
class aiohttp.ClientOSError
连接错误的子集,与OSError属同类异常。
继承于ClientConnectionError和OSError.
class aiohttp.ClientConnectorError
连接器相关异常。
继承于ClientOSError
class aiohttp.ClientProxyConnectionError
继承于ClientConnectonError。(由源码知继承于ClientConnectorError原文写错
了。)
class aiohttp.ServerConnectionError
继承于ClientConnectonError。(由源码知继承于ClientConnectionError原文写错
了。)
class aiohttp.ClientSSLError
继承于ClientConnectonError。(由源码知继承于ClientConnectorError原文写错
了。)
class aiohttp.ClientConnectorSSLError
用于响应ssl错误。
继承于ClientSSLError和ssl.SSLError
class aiohttp.ClientConnectorCertificateError
用于响应证书错误。
继承于 ClientSSLError 和 ssl.CertificateError
class aiohttp.ServerDisconnectedError
服务器无法连接时抛出的错误。
继承于ServerDisconnectionError。
message
包含部分已解析的HTTP消息。(可选)
64
客户端部分参考
class aiohttp.ServerTimeoutError
进行服务器操作时超时,如 读取超时。
继承于ServerConnectionError 和 asyncio.TimeoutError。
class aiohttp.ServerFingerprintMismatch
无法与服务器指纹相匹配时的错误。
继承于ServerDisconnectionError。
异常等级图:
ClientError
ClientResponseError
ContentTypeError
WSServerHandshakeError
ClientHttpProxyError
ClientConnectionError
ClientOSError
ClientConnectorError
ClientSSLError
65
客户端部分参考
ClientConnectorCertificateError
ClientConnectorSSLError
ClientProxyConnectionError
ServerConnectionError
ServerDisconnectedError
ServerTimeoutError
ServerFingerprintMismatch
ClientPayloadError
66
客户端部分参考
InvalidURL
67
服务端快速习得指南
服务端指南
准备使用aiohttp但不知道如何开始?这里有一些小例子来快速熟悉下。接下来我们 一起来试着开发一个小投票系统。
如果你想改进或与之对比学习,可以查看demo source 来获取全部源码。
准备好我们的开发环境 首先检查下python版本:
$python
我们需要python 3.5.0及以上版本。
假设你已经安装好aiohttp库了。你可以用以下命令来查询当前aiohttp库的版本。
$python3
项目结构与其他以python为基础的web项目大同小异:
68
服务端快速习得指南
.
├── README.rst └── polls
├── Makefile ├── README.rst
├── aiohttpdemo_polls
│├── __init__.py
│├── __main__.py
│├── db.py
│├── main.py
│├── routes.py
│├── templates
│├── utils.py
│└── views.py ├── config
│└── polls.yaml ├── images
│└── example.png ├── setup.py
├── sql
│├── create_tables.sql
│├── install.sh
│└── sample_data.sql └── static
└── style.css
开始用aiohttp构建我们的第一个应用程序
该指南借鉴了Django投票系统指南。
创建应用程序
aiohttp的服务端程序都是 aiohttp.web.Application 实例对象。用于创建信
号,连接路由等。
使用下列代码可以创建一个应用程序:
69
服务端快速习得指南
from aiohttp import web
app = web.Application()
web.run_app(app, host='127.0.0.1', port=8080)
将其保存在 aiohttpdemo_polls/main.py 然后开启服务器: $ python3 main.py
你会在命令行中看到如下输出:
======== Running on http://127.0.0.1:8080 ========
(Press CTRL+C to quit)
在浏览器中打开 http://127.0.0.1:8080 或在命令行中使用 curl : $ curl
localhost:8080
啊咧,出现了404: Not Found. 呃...因为我们并没有创建路由和和展示页面。
创建视图
我们来一起创建第一个展示页面(视图)。我们先创建个文
件aiohttpdemo_polls/views.py 然后写入:
from aiohttp import web
async def index(request):
return web.Response(text='Hello Aiohttp!')
index 就是我们创建的展示页,然后我们创建个路由连接到这个展示页上。我们
来把路由放在 aiohttpdemo_polls/routes.py 文件中(将路由表和模型分开写 是很好的实践。创建实际项目时可能会有多个同类文件,这样分开放可以让自己很 清楚。):
70
服务端快速习得指南
from views import index
def setup_routes(app): app.router.add_get('/', index)
我们还要在 main.py 中调用 setup_routes 。
from aiohttp import web
from routes import setup_routes
app = web.Application() setup_routes(app)
web.run_app(app, host='127.0.0.1', port=8080)
然后我们重新开启服务器,现在我们从浏览器中访问:
$curl
啊哈!成功了!我们现在应该有了一个如下所示的目录结构:
.
├── .. └── polls
├── aiohttpdemo_polls
│├── main.py
│├── routes.py
│└── views.py
使用配置文件
aiohttp不需要任何配置文件,也没有内置支持任何配置架构。 但考虑到这些事实:
1.99%的服务器都有配置文件。
2.其他的同类程序(除了以Python为基础的像Django和Flask的)都不会将配置文件
71
服务端快速习得指南
作为源码的一部分。
比如Nginx将配置文件保存在 /etc/nginx文件夹里。
mongo则保存在 /etc/mongodb.conf里。
3.使用配置文件是公认的好方法,在部署产品时可以预防一些小错误。 所以我们建议用以下途径(进行配置文件):
1.将配置信息写在yaml文件中。(json或ini都可以,但yaml最好用。)
2.在一个预先设定好的目录中加载yaml。
3.拥有能通过命令行来设置配置文件的功能。如: ./run_app
4.对要加载的字典执行严格检测,以确保其数据类型符合预期。可以使用: trafaret, colander 或 JSON schema等库。
以下代码会加载配置文件并设置到应用程序中:
# load config from yaml file in current dir
conf = load_config(str(pathlib.Path('.') / 'config' / 'polls.yam l'))
app['config'] = conf
构建数据库
准备工作
这份指南中我们使用最新版的 PostgreSQL 数据库。 你可访问以下链接下载:
http://www.postgresql.org/download/
数据库架构
我们使用 SQLAlchemy 来写数据库架构。我们只要创建两个简单的模块
——question 和 choice :
72
服务端快速习得指南
import sqlalchemy as sa
meta = sa.MetaData()
question - sq.Table( 'question', meta,
sa.Column('id', sa.Integer, nullable=False), sa.Column('question_text', sa.String(200), nullable=False), sa.Column('pub_date', sa.Date, nullable=False),
# Indexes #
sa.PrimaryKeyConstraint('id', name='question_id_pkey')
)
choice = sa.Table( 'choice', meta,
sa.Column('id', sa.Integer, nullable=False), sa.Column('question_id', sa.Integer, nullable=False), sa.Column('choice_text', sa.String(200), nullable=False), sa.Column('votes', server_default="0", nullable=False),
# Indexes #
sa.PrimayKeyConstraint('id', name='choice_id_pkey'), sa.ForeignKeyContraint(['question_id'], [question.c.id],
name='choice_question_id_fkey', ondelete='CASCADE'),
)
你会看到如下数据库结构:
第一张表 question: |question| |id| |question_text| |pub_date|
第二张表 choice: |choice| |id| |choice_text| |votes| |question_id|
创建连接引擎
为了从数据库中查询数据我们需要一个引擎实例对象。假设 conf 变量是一个带有 连接信息的配置字典, Postgre s会使用异步的方式完成该操作:
73
服务端快速习得指南
async def init_pg(app): conf = app['config']
engine = await aiopg.sa.create_engine( database=conf['database'], user=conf['user'], password=conf['password'], host=conf['host'], port=conf['host'], minsize=conf['minsize'], maxsize=conf['maxsize'])
app['db'] = engine
最好将连接数据库的函数放在 on_startup 信号中:
app.on_startup.append(init_pg)
关闭数据库
程序退出时一块关闭所有的资源接口是一个很好的做法。 使用on_cleanup信号来 关闭数据库接口:
async def close_pg(app): app['db'].close()
await app['db'].wait_closed()
app.on_cleanup.append(close_pg)
使用模板
我们来添加些更有用的页面:
74
服务端快速习得指南
@aiohttp_jinja2.template('detail.html') async def poll(request):
async with request['db'].acquire() as conn: question_id = request.match_info['question_id'] try:
question, choices = await db.get_question(conn,
question_i
d)
except db.RecordNotFound as e:
raise web.HTTPNotFound(text=str(e)) return {
'question': question,
'choices': choices
}
编写页面时使用模板是很方便的。我们返回带有页面内容的字
典, aiohttp_jinja2.template 装饰器会用 jinja2 模板加载它。 当然我们要先安装下 aiohttp_jinja2 :
$ pip install aiohttp_jinja2
安装完成后我们使用时要适配下:
import aiohttp_jinja2 import jinja2
aiohttp_jinja2.setup(
app, loader=jinja2.PackageLoader('aiohttpdemo_polls', 'templ ates'))
我们将其放在 polls/aiohttpdemo_polls/templates 文件夹中。
静态文件
75
服务端快速习得指南
每个web站点都有一些静态文件: 图片啦,JavaScript,CSS文件啦等等。 在生产环 境中处理这些静态文件最好的方法是使用NGINX或CDN服务做反向代理。 但在开 发环境中使用aiohttp服务器处理静态文件是很方便的。
只需要简单的调用一个信号即可:
app.router.add_static('/static/',
path=str(project_root / 'static'),
name='static')
project_root表示根目录。
使用中间件
中间件是每个web处理器必不可少的组件。它的作用是在处理器处理请求前预处理 请求以及在得到响应后发送出去。
我们下面来实现一个用于显示漂亮的404和500页面的简单中间件。
def setup_middlewares(app):
error_middleware = error_pages({404: handle_404,
500: handle_500})
app.middlewares.append(error_middleware)
中间件(middleware)本身是一个接受应用程序(application)和后续处理器(next
handler)的加工厂。
中间件工厂返回一个与web处理器一样接受请求并返回响应的中间件处理器。
下面实现一个用于处理HTTP异常的中间件:
76
服务端快速习得指南
def error_pages(overrides):
async def middleware(app, handler): async def middleware_handler(request):
try:
response = await handler(request) override = overrides.get(response.status) if override is None:
return response else:
return await override(request, response) except web.HTTPException as ex:
override = overrides.get(ex.status) if override is None:
raise else:
return await override(request, ex) return middleware_handler
return middleware
这些 overrides(handle_404和handle_500) 只是简单的用 Jinja2 模板渲染:
async def handle_404(request, response):
response = aiohttp_jinja2.render_template('404.html', request, {})
return response
async def handle_500(request, response):
response = aiohttp_jinja2.render_template('500.html', request, {})
return response
详情看 Middlewares.
77
服务端快速习得指南
78
服务端使用
服务端使用
启动一个简单地Web服务器
部署web服务器首先要创建一个 请求处理器(request handler)。
请求处理器可以是协程方法也可以是普通方法,它只有一个用于接受 Request 实 例对象的参数,之后会返回 Response 实例对象:
from aiohttp import web
async def hello(request):
return web.Response(text="Hello, world")
再之后我们需要创建应用(Appliaction)并将请求处理器配置到应用的路由,注意 选择合适的HTTP方法和请求路径:
app = web.Application() app.router.add_get('/', hello)
我们调用 run_app() 来启动应用:
web.run_app(app)
最后,我们访问 http://localhost:8080/ 来查看内容。
扩展
可查看 优雅地关闭(Graceful shutdown)一节了解 run_app() 做了什么以及如 何从头开始实现复杂服务器的初始化/关闭。
使用命令行接口(CLI)
79
服务端使用
aiohttp.web 带有一个基于TCP/IP的基本命令行接口,用于快速启动一个应用
(Application)。
$python
package.module:init_func 应是一个可导入,调用的对象,有一个接受包含命 令行参数列表的参数,配置好之后返回 Application 对象:
def init_func(argv):
app = web.Application()
app.router.add_get("/", index_handler)
return app
使用处理器
处理器对象可以被任意调用,它只接受 Request 实例对象,并且返
回StreamResponse 的派生对象实例(如 Response ):
def handler(request):
return web.Response()
它还可以是协程方法,这样 aiohttp.web 会等待处理:
async def handler(request): return web.Response()
处理器必须被注册在路由上才能使用:
app.router.add_get('/', handler)
app.router.add_post('/post', post_handler)
app.router.add_put('/put', put_handler)
add_route() 同样支持使用通配符方法:
80
服务端使用
app.router.add_route('*', '/path', all_handler)
之后可以使用 Request.method 来获知请求所使用的HTTP方法。
默认情况下, add_get() 也会接受HEAD请求,并像使用GET请求一样返回响应 头。你也可以禁用它:
app.router.add_get('/', handler, allow_head=False)
如果处理器不能被调用,服务器将会返回405。
注意
根据 RFC 7231 aiohttp 2.0版本后做了接受HEAD请求的调整,使用之前版本并且 用 add_get() 添加的请求,如果使用HEAD方法访问会返回405。
如果处理器会写入很多响应体内容,你可以在执行HEAD方法时跳过处理响应体内 容以提高执行效率。
使用资源和路由
内部路由是一个资源列表。 资源是路由表的入口,相当于所请求的URL。 每个资 源至少有一个路由。 路由负责调用web处理器进行处理。
UrlDispatcher.add_get() / UrlDispatcher.add_post() 及其同类方法是
UrlDispatcher.add_route( )的一种简便写法。 同样 UrlDispatcher.add_route() 是 UrlDispatcher.add_resource()
和Resource.add_route() 的一种简便写法。
resource = app.router.add_resource(path, name=name) route = resource.add_route(method, handler) return route
扩展:
81
服务端使用
查看 Router refactoring in 0.21 获取更多信息。
使用可变形资源
资源可以是可变的路径。比如,如果某一路径是 '/a/{name}/c' ,那 '/a/b/c', '/a/1/c','/a/etc/c' ,这样的路径就都可以匹配到。 可变部分需要用 {标识字 符} 这样的形式,标识字符的作用是让处理器可以匹配到这个值。使
用Request.match_info 来完成这一操作:
async def variable_handler(request): return web.Response(
text="Hello, {}".format(request.match_info['name']))
resource = app.router.add_resource('/{name}') resource.add_route('GET', variable_handler)
默认情况下,每个可变部分所使用的正则表达式是 [^{}/]+ 。
当然你也可以自定义正则表达式 {标识符: 正则} :
resource = app.router.add_resource(r'/{name:\d+}')
注意:
正则表达式匹配的是非百分号编码的URL(request.raw_path)字符。比如 空格编 码后是%20。
RFC 3986规定可在路径中的字符是:
82
服务端使用
allowed |
= unreserved / |
/":" / "@" / "/"
|
unreserved |
= ALPHA / DIGIT / |
||
|
|
|
|
|
= "!" / "$" / "&" / "'" / "(" / ")" |
||||
|
|
|
|
|
|
|
/ "*" / "+" / "," / ";" / "=" |
|
|
使用命名资源进行反向引用URL
路由中可以指定name:
resource = app.router.add_resource('/root', name='root')
之后可以访问和构建此资源的URL:
>>>request.app.router['root'].url_for().with_query({"a": "b", "
c": "d"}) URL('/root?a=b&c=d')
带有可变形部分的资源:
app.router.add_resource(r'/{user}/info',
这时传入那部分即可:
>>>
....with_query("a=b") '/john_doe/info?a=b'
将处理器放到类中使用
83
服务端使用
如上所述,处理器可以是全局函数或协同程序:
async def hello(request):
return web.Response(text="Hello, world")
app.router.add_get('/', hello)
但有时将具有同样逻辑的一组路由放到类中是很方便很有用的。 不 过 aiohttp.web 并未有任何该方面的规范,开发者可任意使用:
class Handler:
def __init__(self): pass
def handle_intro(self, request):
return web.Response(text="Hello, world")
async def handle_greeting(self, request):
name = request.match_info.get('name', "Anonymous") txt = "Hello, {}".format(name)
return web.Response(text=txt)
handler = Handler()
app.router.add_get('/intro', handler.handle_intro) app.router.add_get('/greet/{name}', handler.handle_greeting)
基础视图类
aiohttp.web 提供django风格的基础试图类。
你可以从 View 类中继承,并自定义http请求的处理方法:
84
服务端使用
class MyView(web.View): async def get(self):
return await get_resp(self.request)
async def post(self):
return await post_resp(self.request)
处理器应是一个协程方法,且只接受self参数,并且返回标准web处理器的响应对 象。请求对象可使用 View.request 中取出。
当然还需要在路由中注册:
app.router.add_route('*', '/path/to', MyView)
这样 /path/to 既可使用GET也可使用POST进行请求,不过其他未指定的HTTP 方法会抛出405错误。
资源视图
所有在路由中注册的资源都可使用 UrlDispatcher.resources() 查看:
for resource in app.router.resources():
print(resource)
同样,有 name 的资源可以用 UrlDispatcher.named_resources() 来查看:
for name, resource in app.router.named_resources().items(): print(name, resource)
UrlDispatcher.routes() 新增于 0.18版本.
UrlDispatcher.named_routes() 新增于 0.19版本.
0.21版本后请使用 UrlDispatcher.named_routes() /
UrlDispatcher.routes() 来代替 UrlDispatcher.named_resources() / UrlDispatcher.resources() 。
85
服务端使用
其他注册路由的方式
上述例子使用常规方式添加新路由: app.router.add_get(...) 之类.
aiohttp 还提供两种其他方法: 路由表和路由装饰器。
路由表和Django中一样:
async def handle_get(request):
...
async def handle_post(request):
...
app.router.add_routes([web.get('/get', handle_get),
web.post('/post', handle_post),
使用 aiohttp.web.get() 或 aiohttp.web.post() 定义处理器,并放到一个 列表中然后传给 add_routes 。
扩展:
RouteDef reference.
路由装饰器有点像Flask风格:
86
服务端使用
routes = web.RouteTableDef()
@routes.get('/get')
async def handle_get(request):
...
@routes.post('/post')
async def handle_post(request):
...
app.router.add_routes(routes)
首先是要创建一个 aiohttp.web.RouteTableDef 对象。
该对象是一个类列表对象,额外提
供aiohttp.web.RouteTableDef.get() , aiohttp.web.RouteTableDef.post () 这些装饰器来注册路由。
最后调用 add_routes() 添加到应用的路由里。
扩展:
RouteTableDef reference.
这三种方法都是一样的,你可以自行选择喜欢的方式或者混着用也行。
新增于2.3版本。
Web 处理器中的取消操作
警告:
web处理器中每一条await语句都可被取消,这种情况一般发生在客户端还没有完全读 取响应体然后中断了连接。
与著名WSGI架构 如Flask和Django在这点上有所不同。
87
服务端使用
在处理GET请求时,代码可能会从数据库或其他web资源中获取数据,这些查询可 能很慢。 这时候取消查询是最好的: 该连接已经被抛弃了,没有理由再浪费时间和 资源(内存等)进行查询,已经没有机会响应了。
不过在POST请求时可能会有些不好,POST请求常常需要将信息存进数据库,不 管该连接是否被抛弃了。
预防被取消掉可以用下列几种方法:
使用 asyncio.shield() 来进行存进数据库的处理。 开启一个存入数据库的协程任务。
使用 aiojobs 或其他第三方库。
asyncio.shield() 挺不错的,唯一的缺点是你需要分清哪些是需要得到保护的 代码哪些不是。
比如这样的代码就不靠谱:
async def handler(request):
await asyncio.shield(write_to_redis(request))
await asyncio.shield(write_to_postgres(request))
return web.Response('OK')
如果保存到redis时触发了取消,那之后的 write_to_postgres 将不会被执行。
生成一个新的任务更不靠谱,这样不能等待:
async def handler(request): request.loop.create_task(write_to_redis(request)) return web.Response('OK')
write_to_redis 所产生的错误并不能被捕获,这样会导致有很多asyncio的 Future异常的日志消息并且是不可恢复的,因为Task被销毁而不是挂起了。
此外,在按照优雅地关闭(Graceful shutdown)中的进行关闭操作
时, aiohttp 并不会等待这些任务完成,所以你还要找个机会来释放这些重要的 数据。
88
服务端使用
另一方面, aiojobs 提供一个生成新任务并且能等待结果(等此类操作)的
API。 aiojobs 会将所有待完成的操作存入内部数据结构中,并且可以优雅地终 止这些操作:
from aiojobs.aiohttp import setup, spawn
async def coro(timeout):
await asyncio.sleep(timeout) # do something in background
async def handler(request): await spawn(request, coro()) return web.Response()
app = web.Application() setup(app) app.router.add_get('/', handler)
所有未完成的任务会被终止并由 aiohttp.web.Application.on_cleanup 信号发 送出去。
使用 @atomic 装饰器可以使整个处理器都防止有取消操作:
from aiojobs.aiohttp import atomic
@atomic
async def handler(request):
await write_to_db() return web.Response()
app = web.Application() setup(app) app.router.add_post('/', handler)
这样避免了整个async处理器函数执行时被取消, write_to_db 也不会被中断。
自定义路由准则
89
服务端使用
有时,比起简单的使用HTTP方法和路径注册路由,你可能会有其他更复杂些的规 则。
尽管 UrlDispatcher 不直接提供额外的准则,但你可以这样做:
class AcceptChooser:
def __init__(self): self._accepts = {}
async def do_route(self, request):
for accept in request.headers.getall('ACCEPT', []): acceptor = self._accepts.get(accept)
if acceptor is not None:
return (await acceptor(request)) raise HTTPNotAcceptable()
def reg_acceptor(self, accept, handler): self._accepts[accept] = handler
async def handle_json(request):
# do json handling
async def handle_xml(request):
# do xml handling
chooser = AcceptChooser()
app.router.add_get('/', chooser.do_route)
chooser.reg_acceptor('application/json', handle_json) chooser.reg_acceptor('application/xml', handle_xml)
静态文件的处理
处理静态文件( 图片,JavaScripts, CSS文件等)最好的方法是使用反向代理,像是 nginx或CDN服务。
90
服务端使用
但就开发来说, aiohttp 服务器本身可以很方便的处理静态文件。
只需要通过 UrlDispatcher.add_static() 注册个新的静态路由即可:
app.router.add_static('/prefix', path_to_static_folder)
当访问静态文件的目录时,默认服务器会返回 HTTP/403 Forbidden(禁止访问)。 使用 show_index 并将其设置为 True 可以显示出索引:
app.router.add_static('/prefix', path_to_static_folder, show_ind
ex=True)
当从静态文件目录访问一个符号链接(软链接)时,默认服务器会响应 HTTP/404 Not Found(未找到)。使用 follow_symlinks 并将其设置为 True 可以让服务器 使用符号链接:
app.router.add_static('/prefix', path_to_static_folder, follow_s
ymlinks=True)
如果你想允许缓存清除,使用 append_version 并设为 True 。
缓存清除会对资源文件像JavaScript 和 CSS文件等的文件名上添加一个hash后的 版本。这样的好处是我们可以让浏览器无限期缓存这些文件而不用担心这些文件是 否发布了新版本。
app.router.add_static('/prefix', path_to_static_folder, append_v
ersion=True)
模板
aiohttp.web 并不直接提供模板读取,不过可以使用第三方库
aiohttp_jinja2 ,该库是由 aiohttp 作者维护的。 使用起来也很简单。首先 我们用 aiohttp_jinja2.setup() 来设置下 jinja2 环境:
91
服务端使用
app = web.Application()
aiohttp_jinja2.setup(app,
loader=jinja2.FileSystemLoader('/path/to/templates/folder'))
然后将模板引擎应用到处理器中。最简单的方式是使
用aiohttp_jinja2.templates() 装饰器:
@aiohttp_jinja2.template('tmpl.jinja2') def handler(request):
return {'name': 'Andrew', 'surname': 'Svetlov'}
如果你更喜欢Mako模板引擎,可以看看 aiohttp_mako 库。
返回JSON 响应
使用 aiohttp.web.json_response() 可以返回JSON响应:
def handler(request): data = {'some': 'data'}
return web.json_response(data)
这个方法返回的是 aiohttp.web.Response 实例对象,所以你可以做些其他的 事,比如设置cookies。
处理用户会话
你经常想要一个可以通过请求存储用户数据的仓库。一般简称为会话。
aiohttp.web 没有内置会话,不过你可以使用第三方库 aiohttp_session 来提
供会话支持:
92
服务端使用
import asyncio import time import base64
from cryptography import fernet from aiohttp import web
from aiohttp_session import setup, get_session, session_middlewa re
from aiohttp_session.cookie_storage import EncryptedCookieStorag e
async def handler(request):
session = await get_session(request)
last_visit = session['last_visit'] if 'last_visit' in sessio n else None
text = 'Last visited: {}'.format(last_visit) return web.Response(text=text)
def make_app():
app = web.Application()
#secret_key must be 32
return app
web.run_app(make_app())
处理HTTP表单
aiohttp 直接提供HTTP表单支持。
如果表单的方法是 “GET”(
),则要使用 Request.query 获取数据。 如果是“POST”则
用Request.post() 或 Request.multipart() Request.post() 接受标明
为
据()。它会将数据存进一个临时字典中。如果指定了,超出
93
服务端使用
了的话会抛出 ValueError 异常,这时使用 Request.multipart() 是更好的选 择,尤其是在上传大文件时。
小例子: 由以下表单发送的数据:
<form action="/login" method="post"
<label for="login">Login</label>
<input id="login" name="login" type="text" value="" autofocu
s/>
<label for="password">Password</label>
<input id="password" name="password" type="password" value="
"/>
<input type="submit" value="login"/> </form>
可以用以下方法获取:
async def do_login(request):
data = await request.post()
login = data['login']
password = data['password']
文件上传
aiohttp.web 内置处理文件上传的功能。
首先呢我们要确保
标签的有 enctype 元素并且设置为了
94
服务端使用
<form action="/store/mp3" method="post"
<label for="mp3">Mp3</label>
<input id="mp3" name="mp3" type="file" value=""/>
<input type="submit" value="submit"/> </form>
之后你可以在请求处理器中接受这个文件,它变成了一个 FileField 实例对 象。 FileFiled 包含了该文件的原信息。
async def store_mp3_handler(request):
#注意,如果是个很大的文件不要用这种方法。
data = await request.post()
mp3 = data['mp3']
#.filename 包含该文件的名称,是个字符串。
filename = mp3.filename
#.file 包含该文件的内容。
mp3_file = data['mp3'].file
content = mp3_file.read()
return web.Response(body=content, headers=MultiDict(
注意例子中的警告。通常 Reuest.post() 会把所有数据读到内容,可能会引起 OOM(out of memory 内存炸了)错误。你可以用 Request.multipart() 来避免这 种情况,它返回的是 multipart 读取器。
95
服务端使用
async def store_mp3_handler(request):
reader = await request.multipart()
#/!\ 不要忘了这步。(至于为什么请搜索 Python 生成器/异步)/!\
mp3 = await reader.next()
filename = mp3.filename
#
size = 0
with
while True:
chunk = await mp3.read_chunk() # 默认是8192个字节。 if not chunk:
break
size += len(chunk) f.write(chunk)
return web.Response(text='{} sized of {} successfully stored
'
''.format(filename, size))
使用WebSockets
aiohttp.web 直接提供WebSockets支持。
在处理器中创建一个 WebSocketResponse 对象即可设置 WebSocket ,之后即可 进行通信:
96
服务端使用
async def websocket_handler(request):
ws = web.WebSocketResponse() await ws.prepare(request)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT: if msg.data == 'close':
await ws.close() else:
await ws.send_str(msg.data + '/answer') elif msg.type == aiohttp.WSMsgType.ERROR:
print('ws connection closed with exception %s' % ws.exception())
print('websocket connection closed')
return ws
websockets处理器需要用HTTP GET方法注册:
app.router.add_get('/ws', websocket_handler)
从WebSocket 中读取数据( await ws.receive() )必须在请求处理器内部完 成,不过写数据( ws.send_str(...) ),关闭( await ws.close() )和取消
操作可以在其他任务中完成。 详情看 FAQ 部分。
aiohttp.web 隐式地使用 asyncio.Task 处理每个请求。
注意:
虽然aiohttp仅支持不带诸如长轮询的WebSocket不过如果我们有维护一个基于 aiohttp的SockJS包,用于部署兼容SockJS服务器端的代码。
警告
97
服务端使用
不要试图从websocket中并行地读取数据,
aiohttp.web.WebSocketResponse.receive()不能在分布在两个任务中同时调用。
请看 FAQ 部分了解解决方法。
异常
aiohttp.web 定义了所有HTTP状态码的异常。
每个异常都是 HTTPException 的子类和某个HTTP状态码。 同样还都
是Response 的子类,所以就允许你在请求处理器中返回或抛出它们。 请看下面 这些代码:
async def handler(request):
return aiohttp.web.HTTPFound('/redirect')
async def handler(request):
raise aiohttp.web.HTTPFound('/redirect')
每个异常的状态码是根据RFC 2068规定来确定的:
异常等级图:
HTTPException
HTTPSuccessful
*200 - HTTPOk
*201 - HTTPCreated
*202 - HTTPAccepted
*203 - HTTPNonAuthoritativeInformation
*204 - HTTPNoContent
*205 - HTTPResetContent
*206 - HTTPPartialContent HTTPRedirection
*300 - HTTPMultipleChoices
*301 - HTTPMovedPermanently
*302 - HTTPFound
98
服务端使用
*303 - HTTPSeeOther
*304 - HTTPNotModified
*305 - HTTPUseProxy
*307 - HTTPTemporaryRedirect
*308 - HTTPPermanentRedirect HTTPError
HTTPClientError
* 400 - HTTPBadRequest
*401 - HTTPUnauthorized
*402 - HTTPPaymentRequired
*403 - HTTPForbidden
*404 - HTTPNotFound
*405 - HTTPMethodNotAllowed
*406 - HTTPNotAcceptable
*407 - HTTPProxyAuthenticationRequired
*408 - HTTPRequestTimeout
*409 - HTTPConflict
*410 - HTTPGone
*411 - HTTPLengthRequired
*412 - HTTPPreconditionFailed
*413 - HTTPRequestEntityTooLarge
*414 - HTTPRequestURITooLong
*415 - HTTPUnsupportedMediaType
*416 - HTTPRequestRangeNotSatisfiable
*417 - HTTPExpectationFailed
*421 - HTTPMisdirectedRequest
*422 - HTTPUnprocessableEntity
*424 - HTTPFailedDependency
*426 - HTTPUpgradeRequired
*428 - HTTPPreconditionRequired
*429 - HTTPTooManyRequests
*431 - HTTPRequestHeaderFieldsTooLarge
*451 - HTTPUnavailableForLegalReasons HTTPServerError
*500 - HTTPInternalServerError
*501 - HTTPNotImplemented
*502 - HTTPBadGateway
*503 - HTTPServiceUnavailable
*504 - HTTPGatewayTimeout
*505 - HTTPVersionNotSupported
99
服务端使用
*506 - HTTPVariantAlsoNegotiates
*507 - HTTPInsufficientStorage
*510 - HTTPNotExtended
*511 - HTTPNetworkAuthenticationRequired
所有的异常都拥有相同的结构:
HTTPNotFound(*, headers=None, reason=None,
body=None, text=None, content_type=None)
如果没有指定headers,默认是响应中的headers。 其中 HTTPMultipleChoices, HTTPMovedPermanently, HTTPFound, HTTPSeeOther, HTTPUseProxy, HTTPTemporaryRedirect 的结构是下面这样的:
HTTPFound(location, *, headers=None, reason=None, body=None, text=None, content_type=None)
location参数的值会写入到HTTP头部的Location中。
HTTPMethodNotAllowed 的结构是这样的:
HTTPMethodNotAllowed(method, allowed_methods, *, headers=None, reason=None,
body=None, text=None, content_type=None)
method是不支持的那个方法,allowed_methods是所支持的方法。
数据共享
aiohttp.web 不推荐使用全局变量进行数据共享。每个变量应在自己的上下文中 而不是全局可用的。 因
此, aiohttp.web.Application 和 aiohttp.web.Request 提
供collections.abc.MutableMapping(类字典对象) 来存储数据。
将类全局变量存储到Application实例对象中:
100
服务端使用
app['my_private_key'] = data
之后就可以在web处理器中获取出来:
async def handler(request):
data = request.app['my_private_key']
如果变量的生命周期是一次请求,可以在请求中存储。
async def handler(request):
request['my_private_key'] = "data"
...
对于中间件和信号处理器存储需要进一步被处理的数据特别有用。 为了避免与其
他aiohttp 所写的程序和其他第三方库中的命名出现冲突,请务必写一个独一无 二的键名。 如果你要发布到PyPI上,使用你公司的名称或url或许是个不错的选 择。(如 org.company.app)
中间件
aiohttp.web 提供一个强有力的中间件组件来编写自定义请求处理器。
中间件是一个协程程序帮助修正请求和响应。下面这个例子是在响应中添加'wink'字 符串:
from aiohttp.web import middleware
@middleware
async def middleware(request, handler): resp = await handler(request) resp.text = resp.text + ' wink' return resp
(对于流式响应和websockets该例子不起作用)
101
服务端使用
每个中间件需要接受两个参数,一个是请求实例另一个是处理器,中间件需要返回 响应内容。
创建 Application 时,可以通过 middlewares 参数传递中间件过去:
app = web.Application(middlewares=[middleware_1,
middleware_2])
内部会将单个请求处理器处理的结果经由所传入的中间件处理器倒序的处理一遍。 因为 middlewares(中间件) 都是协程程序,所以它们可以执行 await 语句以进 行如从数据库中查询等操作。 middlewares(中间件) 常常会调用处理器,不过 也可以不调用,比如用户访问了没有权限访问的资源则显示403Forbidden页面或者 抛出 HTTPForbidden 异常。 处理器中也可能抛出异常,比如执行一些预处理或后 续处理(HTTP访问资源控制等)。
下列代码演示中间件的执行顺序:
102
服务端使用
from aiohttp import web
def test(request): print('Handler function called') return web.Response(text="Hello")
@web.middleware
async def middleware1(request, handler): print('Middleware 1 called') response = await handler(request) print('Middleware 1 finished') return response
@web.middleware
async def middleware2(request, handler): print('Middleware 2 called') response = await handler(request) print('Middleware 2 finished') return response
app = web.Application(middlewares=[middleware1, middleware2]) app.router.add_get('/', test)
web.run_app(app) Produced output:
Middleware 1 called
Middleware 2 called
Handler function called
Middleware 2 finished
Middleware 1 finished
例子
通常中间件用于部署自定义错误页面。下面的例子将会使用JSON响应(JSON REST)显示404错误页面。
103
服务端使用
import json
from aiohttp import web
def json_error(message): return web.Response(
body=json.dumps({'error':
@web.middleware
async def error_middleware(request, handler): try:
response = await handler(request) if response.status == 404:
return json_error(response.message) return response
except web.HTTPException as ex: if ex.status == 404:
return json_error(ex.reason) raise
app = web.Application(middlewares=[error_middleware])
旧式中间件
2.3之前的版本中间件需要一个返回中间件协同程序的外部中间件处理工厂。2.3之 后就不在需要这样做了,使用 @middleware 装饰器即可。 旧式中间件(使用外部 处理工厂不使用 @middleware 装饰器)仍然支持。此外,新旧两个版本可以混 用。 中间件工厂是一个用于在调用中间件之前做些其他操作的协同程序,下面例子 是一个简单中间件工厂:
async def middleware_factory(app, handler): async def middleware_handler(request):
resp = await handler(request) resp.text = resp.text + ' wink' return resp
return middleware_handler
104
服务端使用
中间件工厂要接受两个参数: app实例对象和请求处理器最后返回一个新的处理器。
注意:
外部中间件工厂和内部中间件处理器会处理每一个请求。 中间件工厂要返回一个与
信号
信号组件新增于0.18版本。
尽管中间件可以自定义之前和之后的处理行为,但并不能自定义响应中的行为。所 以信号量由此而生。
比如,中间件只能改变没有预定义HTTP头的响应的HTTP 头(看 prepare() ), 但有时我们需要一个可以改变流式响应和WebSockets HTTP头的钩子。所以我们 可以用 on_response_prepare 信号来充当这个钩子:
async def on_prepare(request, response):
app.on_response_prepare.append(on_prepare)
此外,你也可以用 on_startup 和 on_cleanup 信号来捕获应用开启和释放时的 状态。
请看以下代码:
105
服务端使用
from aiopg.sa import create_engine
async def create_aiopg(app): app['pg_engine'] = await create_engine(
user='postgre',
database='postgre',
host='localhost',
port=5432,
password=''
)
async def dispose_aiopg(app): app['pg_engine'].close()
await app['pg_engine'].wait_closed()
app.on_startup.append(create_aiopg) app.on_cleanup.append(dispose_aiopg)
信号处理器不要返回内容,一般用于修改传入的可变对象。 信号处理器会循环执 行,它们会一直累积。如果处理器是异步方式,在调用下个之前会一直等待。
警告:
信号API目前是临时状态,在未来可能会改变。
信号注册和发送方式基本不会被,不过信号对象的创建可能会变。只要你不创建新 信号只用已经存在的信号量那基本不受影响。
嵌套应用
子应用用来完成某些特定的功能。比如我们有一个项目,有独立的业务逻辑和其他 功能如管理功能和调试工具。
管理功能是一个独立于业务逻辑的功能,但使用的时候要在URL中加上如 /admi 这样的前缀。 因此我们可以创建名为 admin的子应用,我们可以
用add_subapp() 来完成:
106
服务端使用
admin = web.Application()
#setup admin routes, signals and middlewares
app.add_subapp('/admin/', admin)
主应用和子应用间的中间件和信号是一个环一样的结构。 也就是说如果请 求 '/admin/something' 会先调用主应用的中间件然后在调用子应用
( admin.middlewares )的中间件。 信号也同样。 所有注册的基础信号
如on_startup,on_shutdown,on_cleanup 都会给子应用也注册一份。只不过传
递的参数是子应用。 子应用也可以嵌套子应用。 子应用也可以使用Url反向引用, 只不过会带上前缀:
admin = web.Application()
admin.router.add_get('/resource', handler, name='name')
app.add_subapp('/admin/', admin)
url = admin.router['name'].url_for()
这样我们得到的url是 '/admin/resource' 。 如果主应用想得到子应用的Url反向 引用,可以这样:
admin = web.Application()
admin.router.add_get('/resource', handler, name='name')
app.add_subapp('/admin/', admin) app['admin'] = admin
async def |
handler(request): # main application's handler |
||
|
|
|
|
admin |
= request.app['admin'] |
||
|
|
|
|
url = |
admin.router['name'].url_for() |
流控制
107
服务端使用
aiohttp.web 有复杂的流控制机制来应对底层TCP套接字写入缓存。 问题是: 默 认情况下TCP套接字使用Nagle算法来输出缓存,但这种算法对于流式数据协议如 HTTP不是很理想。
Web服务器的响应是以下几种状态其中一个:
1.CORK(tcp_cork设置为True)。这个选项不会发送一部分TCP/IP帧。当这个 选项被(再次)清除时会发送所有已经在队列中的片段帧。因为会把各种小帧 聚合起来发送,所以这种方式对发送大量片段数据非常理想。 如果操作系统不 支持CORK模式(不管是socket.TCP_CORK还是socket.TCP_NOPUSH)那 该模式与Nagle模式一样。一般来说是windows系统不支持此模式。
2.NODELAY(tcp_nodelay设置为True)。这个选项会禁用Nagle算法。选用这 个那么无论数据多小都会尽快发出去,即使是很小的数据。该模式对发送少量 数据非常理想。
3.Nagle算法(tcp_cork和tcp_nodelay都为False)。该模式会先缓存数据,直到 达到预定的数据大小后再一起发送。如果要发送HTTP数据应该避免使用这个 模式除非你确定要使用它。
默认情况下,流数据( StreamResponse ),标准响应( Response 和http异常及 其派生类)和websockets( WebSocketResponse )使用NODELAY模式,静态文 件处理器使用CORK模式。
可以使用 set_tcp_cork( )方法和 set_tcp_nodelay() 方法手动切换。
使用Expect头
aiohttp.web支持使用Expect头。默认是HTTP/1.1 100 Continue,如果Expect头不
是
器 , Expect处理器 会先于中间件和路由处理器被调用。 Expect处理器 可以返
回None ,返回 None 则会继续执行(调用中间件和路由处理器)。如果返回的 是 StreamResponse 实例对象,之后请求处理器则使用该返回对象作为响应内 容。也可以抛出 HTTPException 的子类对象。抛出错误的时候之后的处理将不会 进行,客户端将会接受一个适当的http响应。
注意:
108
服务端使用
如果服务器不能理解或不支持的Expect域中的任何期望值,则服务器必须返回417 或其他适当的错误码(4xx状态码)。
如果所有的检查通过,在返回前自定义处理器需要会将 HTTP/1.1 100 Continue 写入。
下面是一个自定义 Expect处理器 的例子:
async def check_auth(request):
if request.version != aiohttp.HttpVersion11: return
if request.headers.get('EXPECT') !=
raise HTTPExpectationFailed(text="Unknown Expect: %s" %
expect)
if request.headers.get('AUTHORIZATION') is None: raise HTTPForbidden()
request.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")
async def hello(request):
return web.Response(body=b"Hello, world")
app = web.Application()
app.router.add_get('/', hello, expect_handler=check_auth)
部署自定义资源
使用 UrlDispatcher.register_resource() 注册自定义的资源。资源实例必须
有AbstractResource 接口。
新增于1.2.1版本。
优雅地关闭
109
服务端使用
停止 aiohttp web 服务器时只关闭打开的连接时不够的。 因为可能会有一
些websockets 或流,在服务器关闭时这些连接还是打开状态。 aiohttp没有内置 如何进行关闭,但开发者可以使用 Applicaiton.on_shutdown 信号来完善这一功 能。 下面的代码是关闭 websocket 处理器的例子:
app = web.Application() app['websockets'] = []
async def websocket_handler(request): ws = web.WebSocketResponse() await ws.prepare(request)
request.app['websockets'].append(ws)
try:
async for msg in ws:
...
finally:
request.app['websockets'].remove(ws)
return ws
信号处理器差不多这样:
async def on_shutdown(app): for ws in app['websockets']:
await ws.close(code=WSCloseCode.GOING_AWAY, message='Server shutdown')
app.on_shutdown.append(on_shutdown)
合适的关闭程序要注意以下死点:
1.不在接受新的连接。注意调
用 asyncio.Server.close() 和 asyncio.Server.wait_closed() 来关
闭。
2.解除 Application.shutdown() 事件。
3.在一小段延迟后调用 Server.shutdown() 关闭已经开启的连接。
4.发出 Application.cleanup() 信号。
110
服务端使用
下列代码演示从开始到结束:
loop = asyncio.get_event_loop() handler = app.make_handler()
f = loop.create_server(handler, '0.0.0.0', 8080) srv = loop.run_until_complete(f) print('serving on', srv.sockets[0].getsockname()) try:
loop.run_forever() except KeyboardInterrupt:
pass finally:
srv.close()
loop.run_until_complete(srv.wait_closed()) loop.run_until_complete(app.shutdown()) loop.run_until_complete(handler.shutdown(60.0)) loop.run_until_complete(app.cleanup())
loop.close()
后台任务
有些时候我们需要执行些异步操作。 甚至需要在请求处理器处理问题时进行一些后 台操作。比如监听消息队列或者其他网络消息/事件资源(ZeroMQ,Redis Pub/Sub,AMQP等)然后接受消息并作出反应。 例如创建一个后台任务,用于在 zmq.SUB套接字上监听ZeroMQ,然后通
过WebSocket(app['websockets']) 处理并转发接收到的消息给客户端。 使
用Application.on_startup 信号注册的后台任务可以让这些任务在应用的请求
处理器执行时一并执行。 比如我们需要一个一次性的任务和两个常驻任务。最好的 方法是通过信号注册:
111
服务端使用
async def listen_to_redis(app): try:
sub = await aioredis.create_redis(('localhost', 6379), l oop=app.loop)
ch, *_ = await sub.subscribe('news')
async for msg in
#Forward message to all connected websockets: for ws in app['websockets']:
ws.send_str('{}: {}'.format(ch.name, msg))
except asyncio.CancelledError: pass
finally:
await sub.unsubscribe(ch.name) await sub.quit()
async def start_background_tasks(app): app['redis_listener'] = app.loop.create_task(listen_to_redis
(app))
async def cleanup_background_tasks(app): app['redis_listener'].cancel() await app['redis_listener']
app = web.Application()
app.on_startup.append(start_background_tasks) app.on_cleanup.append(cleanup_background_tasks) web.run_app(app)
listen_to_redis() 将会一直运行下去。当关闭时发出的 on_cleanup 信号会
调用关闭处理器以关闭它。
处理错误页面
112
服务端使用
404 NotFound和500 Internal Error之类的错误页面可以看Middlewares章节了解详
情,这些都可以通过自定义中间件完成。
基于代理部署服务器
Server Deployment中讨论了基于反向代理服务(像nginx)部署 aiohttp 来用于 生产使用。 如果使用这种方法,就不要在使用scheme, host和remote了。 将正确 的值配置在代理服务器中,之后不管是使用 Forwarded 还是使用旧式的 X-
么aiohttp 应该在使用反向代理的自定义中间件中设置forwarded头的原因。 在 中间件中改变scheme,host和remote可以用 clone() 。
待更新: 添加一个可以很好的配置中间件的第三方项目。
Swagger 支持
CORS 支持
aiohttp.web 本身不支持跨域资源共享(
调试工具箱
开发 aiohttp.web 应用项目时, aiohttp_debugtoolbar 是非常好用的一个调 试工具。
可使用pip进行安装:
$ pip install aiohttp_debugtoolbar
113
服务端使用
之后将 aiohttp_debugtoolba r中间件添加到 aiohttp.web.Applicaiton 中并 调用 aiohttp_debugtoolbar.setup() 来部署:
import aiohttp_debugtoolbar
from aiohttp_debugtoolbar import toolbar_middleware_factory
app = web.Application(middlewares=[toolbar_middleware_factory]) aiohttp_debugtoolbar.setup(app)
愉快的调试起来吧~。
开发工具
可以使用pip安装:
$pip install
* ``runserver`` 提供自动重载,实时重载,静态文件服务和aiohttp_debugto
olbar_integration。
* ``start`` 是一个帮助做繁杂且必须的创建'aiohttp.web'应用的命令。
创建和运行本地应用的文档和指南请看
114
底层服务器搭建
底层服务器
这一节介绍了aiohttp.web的基础底层服务器。
抽象基础
有时候用户不需要更高级的封装,像是 application,routers和signals。 只是需要 一个支持异步调用并且是接受请求返回响应对象的东西。 在aiohttp.web.Server类 中有介绍过一个服务协议工厂
——asyncio.AbstractEventLoop.create_server() ,并可以将数据流桥接到
web处理器以及反馈结果。 底层web处理器应该接收单个 BaseRequest 参数并且 执行下列中的其中一个:
1.返回一个包含HTTP响应体的响应对象。
2.创建一个 StreamResponse 对象,然后可以调
用StreamResponse.prepare() 发送头信息,调
用StreamResponse.write() / StreamResponse.drain() 发送数据块,最
后结束响应。
3.抛出 HTTPException 派生的异常(看Exception部分)。
4.使用 WebSocketResponse
运行基础底层服务器
请看下列代码:
115
底层服务器搭建
import asyncio
from aiohttp import web
async def handler(request): return web.Response(text="OK")
async def main(loop):
server = web.Server(handler)
await loop.create_server(server, "127.0.0.1", 8080) print("======= Serving on http://127.0.0.1:8080/ ======")
#pause here for very long time by serving HTTP requests and
#waiting for keyboard interruption
await asyncio.sleep(100*3600)
loop = asyncio.get_event_loop()
try: loop.run_until_complete(main(loop))
except KeyboardInterrupt: pass
loop.close()
这样我们有了一个返回"OK"标准响应的处理器。
这个处理器经由服务器调用。调用 loop.create_server 创建的网络交流通道,
随后可以访问http://127.0.0.1:8080/来查看。 这个处理器可以接受所有的请求: 不
论GET, POST,
116
服务器端参考
服务器端参考
请求(Request)和基础请求(BaseRequest)
Request 对象中包含所有的HTTP请求信息。
BaseRequest 用在底层服务器中(底层服务器没有应用,路由,信号和中间件)。 Request对象拥有Request.app和Request.match_info属性。 BaseRequest和Reuqest都是类字典对象,以便在中间件和信号处理器中共享数
class aiohttp.web.BaseRequest
version
发起请求的HTTP版本,该属性只读。
返回aiohttp.protocol.HttpVersion实例对象。
method
发起请求的HTTP方法,该属性只读。
返回值为大写字符串,如"GET" ,"POST","PUT"。
url
包含资源的绝对路径的URL实例对象。
注意
如果是不合适的请求(如没有HOST HTTP头信息)则是不可用的。
rel_url
包含资源的相对路径的URL实例对象。
与 .url.relative() 相同。
scheme
表示请求中的协议(scheme)部分。
如果处理方式是SSL则为"https",否则是"http"。
该属性的值可能会被 clone()方法覆盖。
该属性只读,类型为str。
2.3版本时更改内容: Forwarded 和
调用 .clone(scheme=new_scheme) 来设置一个新的值。
117
服务器端参考
扩展
secure
设置 request.url.scheme == 'https' 的快捷方法。
该属性只读,True或者False。
forwarded
包含了所有已编译过的Forwarded头信息的元组。
尽可能做到符合RFC 7239规定:
1. 为每个Forwarded域值添加一个不可变的字典。字典内的元素等同于 Forwarded域值中的数据,该数据是客户端首次遇到的代理时所添加的值。随后的 项是客户端后来遇到的代理所添加的值。
2. 检查每个值是否符合RFC
返回包含一个或多个MappingProxy对象的元组。
host
请求中的主机(Host)名,以此顺序解析:
clone() 方法所设置的值。 HTTP头信息中的HOST。
socket.gtfqdn()
该属性只读,类型为str。
2.3版本时更改内容: Forwarded 和
调用 .clone(host=new_host) 来设置一个新的值。
扩展
remote
初始化HTTP请求的IP地址。
按以下顺序解析:
118
服务器端参考
clone() 方法所设置的值。 已打开的socket的Peer的值。
该属性只读,类型为str。
调用 .clone(remote=new_remote) 来设置一个新的值。
新增于 2.3版本。
扩展
path_qs
包含路径信息和查询字符串的URL(/app/blog?id=10)。
该属性只读,类型为str。
path
包含路径信息但不含有主机(host)或协议(scheme)的
该属性只读,类型为str。
raw_path
包含原始路径信息但不含有主机(host)或协议(scheme)的URL,该路 径可以被编码并且可能包含无效的URL字符,
如/my%2Fpath%7Cwith%21some%25strange%24characters 。
获取解码的URL请看上面那个。
query
带有查询字符串的并联字典。
该属性只读,类型为MultiDictProxy。
query_string
URL中的查询字符串,如 id=10 。
该属性只读,类型为str。
headers
带有所有头信息的并联字典对象,大小写不敏感。
该属性只读,类型为 CIMuliDictProxy。
119
服务器端参考
raw_headers
响应中的头信息,字符未经转换,是一个键值对(key, value)序列。
keep_alive
如果允许与HTTP客户端进行
该属性只读,类型为Bool。
transport
用于处理请求的传输端口(transport),该属性只读。
该属性可以被用在获取客户端 peer的IP地址时。
peername = request.transport.get_extra_info('peername')
if peername is not None:
host, port = peername
loop
用于进行HTTP请求处理的事件循环。
该属性只读,类型为 asyncio.AbstractEventLoop。
新增于2.3版本。
cookies
包含所有请求的cookies信息的并联字典。
该属性只读,类型为 MultiDictProxy。
content
StreamReader实例对象,用于读取请求的主体(BODY)的输入流。
该属性只读。
body_exists
如果请求有HTTP主体(BODY)的话则为True,否则为False。
该属性只读,类型为Bool。
新增于2.3版本。
can_read_body
如果请求的HTTP主体(BODY)可以被读取则为True,否则为Falase。
该属性只读,类型为Bool。
新增于2.3版本。
120
服务器端参考
has_body
如果请求的HTTP主体(BODY)可以被读取则为True,否则为Falase。
该属性只读,类型为Bool。
2.3版本后不赞成使用: 请使用 can_read_body() 代替。
content_type
类型是str,如'text/html'。
注意
charset
请求主体(BODY)使用的编码,该属性只读。
content_length
请求主体(BODY)的长度,该属性只读。
http_range
返回Range HTTP头信息的内容,该属性只读。
返回值为切片(slice)对象,包含开头(.start)的值但不包含结尾 (.stop)的值,步长(.step)为1。
有以下两种使用方式:
1.属性方式(假设我们设置了左右两端,开放的边界更加复杂一些):
rng = request.http_range
with open(filename, 'rb') as f:
f.seek(rng.start)
return
2.切片方式:
121
服务器端参考
return buffer[request.http_range]
新增于 1.2版本。
if_modified_since
类型为
clone(*, method=..., rel_url=..., headers=...)
克隆自己,并将相应的值替换。
创建并返回一个新Request实例对象。如果没有传递任何参数,将会复制一 份一模一样的。如果没有传递某一个参数,那个值与原值一样。
参数:
method (str) - http方法。
rel_url - 使用的url,str或URL对象。
headers - CIMuliDictProxy对象或其他兼容头信息的容器。
返回: 返回克隆的Request对象。
coroutine read()
读取请求主体,返回带有主体内容的bytes对象。
注意
该方法在内部存储已读信息,之后的调用会返回同样的值。
coroutine text()
返回带有主体内容的str对象。
注意
该方法在内部存储已读信息,之后的调用会返回同样的值。
122
服务器端参考
coroutine json(\, loads=json.loads)*
读取请求主体,以JSON形式返回。
此方法的实现是这样的:
async def json(self, *, loads=json.loads):
body = await self.text()
return loads(body)
参数: loads(callable) - 任何接受str并返回JSON内容的可调用对象(默认是 json.loads())。
注意
该方法在内部存储已读信息,之后的调用会返回同样的值。
coroutine multipart(\, reader=aiohttp.multipart.MultipartReader)*
返回一个用于处理即将到来的multipart请求的
aiohttp.multipart.MultipartReader对象。
此方法的实现是这样的:
async def multipart(self, *, reader=aiohttp.multipart.MultipartR
eader):
return reader(self.headers, self._payload)
此方法是协程方法的原因是为了与其他可能的reader方法(协程方式的reader)保 持一致。
警告
该方法并不在内部存储已读信息。也就是说你读完一次之后不能在用它读第二次了。
扩展
123
服务器端参考
coroutine post()
一个从请求主体中读取POST参数的协程方法。
返回带有已解析后的数据的MultiDictProxy实例对象。
如果HTTP方法不是 POST, PUT, PATCH, TRACE 或 DELETE,或者
data,将会返回空的并联字典(multidict)。
注意
该方法在内部存储已读信息,之后的调用会返回同样的值。
coroutine release()
用于释放请求。
未读的HTTP主体将会被清空。
注意
用户代码中不应存在调用release(),aiohttp.web会在内部自行处理。
class aiohttp.web.Request 在web处理器中接受请求信息的Request类。
所有的处理器的第一个参数都要接受Request类的实例对象。
该类派生于BaseRequest,支持父类中所有的方法和属性。还有几个额外的:
match_info
返回AbstractMatchInfo实例对象,内容是路由解析的结果,该属性只读。
注意
属性的具体类型由路由决定,如果app.router是UrlDispatcher,则该属性包含的是 UrlMappingMatchInfo实例对象。
app
返回一个用于调用请求处理器的应用(Application)实例对象。
124
服务器端参考
注意
用户不要手动创建Request实例对象,aiohttp.web会自动完成这些操作。但你可以 使用clone()方法来进行一些修改,比如路径和方法之类的修改。
响应类
目前为止,aiohttp.web提供三个不同的响应类: StreamResponse , Response 和 FileResponse 。
通常你要使用的是第二个也就是 Response 。 StreamResponse 用于流数
据, Response
为了设计考虑, Response 的父类是 StreamResponse 。
从web处理器中进行响应通常的做法是返回一个 Response 实例对象:
def handler(request):
return Response("All right!")
StreamResponse
class aiohttp.web.StreamResponse(\, status=200, reason=None)*
注:
源码中参数多了一个headers。
用于HTTP响应处理的基础类。
提供几个如设置HTTP响应头,cookies,响应状态码和写入HTTP响应主体等方 法。
也就是说你只能在调用 prepare() 之前操作头信息,cookies和状态码。
125
服务器端参考
一旦你调用了 prepare() ,之后任何有关操作都会导致抛
出RuntimeError 异常。
在调用 write_eof() 之后任何 write() 操作也是同样禁止的。
参数:
status (int) - HTTP状态码,默认200。
reason (int) - HTTP原因(reason)。如果该参数为None,则会根据状态码参
数的值进行计算。其他情况请传入用于说明状态码的字符串。
prepared
如果 prepare() 已被调用过,则返回True否则返回False。该属性只读。新 增于0.18版本。
task
一个承载请求处理的任务。
在关闭服务器时对于那些需要长时间运行的请求(流内容,长轮询或web- socket)非常有用。 新增于1.2版本。
status
返回HTTP响应状态码,默认是200,该属性只读。
reason
返回HTTP状态码的说明字符串,该属性只读。
set_status(status, reason=None)
设置状态码和说明字符串。
如果不设置说明字符串则自动根据状态码计算。
keep_alive
默认复制Request.keep_alive的值,该属性只读。
可以通过调用 force_close() 来设为False。
force_close()
compression
如果允许压缩则返回True,否则返回False。该属性只读。
默认是False。
126
服务器端参考
enable_compression(force=None)
开启压缩。
force如果没有设置则使用的压缩编码从请求的
如果force设置了则不会管
chunked
指代是否使用了分块编码,该属性只读。
可以通过调用 enable_chunked_encoding() 开启分块编码。
enable_chunked_encoding()
允许响应使用分块编码。 开启之后没有方法关闭它。允许之后,每 次 write() 都会编码进分块中。
警告
分块编码只能在HTTP/1.1中使用。
content_length和分块编码是相互冲突的。
headers
携带HTTP头信息的CIMultiDict实例对象。
cookies
携带cookies信息的http.cookies.SimpleCookie实例对象。
警告
set_cookie(name, value, *, path='/', expires=None, domain=None,
max_age=None, secure=None, httponly=None, version=None)
方便设置cookies,允许指定一些额外的属性,如max_age等。
参数:
127
服务器端参考
name (str) - cookie名称。
value (str) - cookie值(如果是其他类型的话会尝试转换为str类型) expires - 过期时间(可选)。
domain (str) - cookie主域(可选)。
max_age (int) - 定义cookie的生命时长,以秒为单位。该参数为非负整数。在 经过这些秒后,客户端会抛弃该cookie。如果设置为0则表示立即抛弃。 path (str) - 设置该cookie应用在哪个路径上(可选,默认是'/')。
secure (bool) - 该属性(没有任何值)会让用户代理使用安全协议。用户代理 (或许会在用户控制之下)需要决定安全等级,在适当的时候考虑使用“安 全”cookie。是不是要使用“安全”要考虑从服务器到用户代理的这段历程,“安 全”协议的目的是保证会话在安全情况下进行(可选)。
httponly (bool) - 如果要设置为HTTP only则为True(可选)。
version (int) - 一个十进制数,表示使用哪个版本的cookie管理(可选,默认为 1)。
警告
在HTTP 1.1版本中,expires已不再赞成使用,请使用更简单的
del_cookie(name, *, path='/'. domain=None)
删除某cookie。
参数:
name (str) - cookie名称。
domain (str) - cookie主域(可选)。
path (str) - 该cookie的路径(可选,默认是'/')。 1.0版本修改内容: 修复
对IE11以下版本的过期时间支持。
content_length
content_type**
128
服务器端参考
charset
该值会在调用时被转成小写字符。
last_modified
该属性接受原始字符串,datetime.datetime对象,Unix时间戳(整数或浮点 数),None则表示不设置。 注: 源代码中该方法接受一个参数,文档中并 没有标明。下面是源代码:
@last_modified.setter
def last_modified(self, value): if value is None:
self.headers.pop(hdrs.LAST_MODIFIED, None) elif isinstance(value, (int, float)):
self.headers[hdrs.LAST_MODIFIED] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ce
il(value)))
elif isinstance(value, datetime.datetime): self.headers[hdrs.LAST_MODIFIED] = time.strftime(
"%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple(
))
elif isinstance(value, str): self.headers[hdrs.LAST_MODIFIED] = value
tcp_cork
如果该属性为True则把底层传输端口设置为TCP_CORK(Linux)或 TCP_NOPUSH(FreeBSD和MacOSX)。
使用set_tcp_cork()来为该属性设置新值。
默认为False。
set_tcp_cork(value)
设置tcp_cork的值。
如果为True则会清除tcp_nodelay。
tcp_nodelay
如果该属性为True则把底层传输端口设置为TCP_NODELAY。
使用set_tcp_nodelay()来为该属性设置新值。
129
服务器端参考
默认是True。
set_tcp_nodelay(value)
设置tcp_nodelay的值。
如果为True则会清除tcp_cork。
corotine prepare(request)
参数: request (aiohttp.web.Request) - HTTP请求对象,要响应的对象。
会发送HTTP头信息出去。 在调用了该方法之后你就不要在修改任何头信息 的数据了。
该方法同时会调用on_response_prepare信号所连接的处理器。
新增于0.18版本。
write(data)
用prepare() 。
如果data不是字节(bytes),字节数组(bytearray)或内存查看对象 (memoryview)则会抛出 TypeError 异常。
如果 prepare() 没有调用则会抛出 RuntimeError 异常。
如果 write_eof() 已经被调用则会抛出 RuntimeError 异常。
coroutine drain()
该方法可以让底层传输端口的写缓存器有机会被刷新。
用在写操作时:
resp.write(data)
await resp.drain()
调用 drain() 能让事件循环安排写和刷新缓存器的操作。尤其是在写了一 个很大的数据时,调用(其他)write()时协程不会被启动。
coroutine write_eof()
一个可以作为HTTP响应结束标志的协程方法。
如果需要的话,内部会在完成请求处理后调用这个方法。
调用 write_eof() 后,任何对响应对象的操作都是禁止的。
Response
130
服务器端参考
class aiohttp.web.Response(\, status=200, headers=None, content_type=None,
charset=None, body=None, text=None)*
注:
参数方面文档中与源码不符,源码中如下:
Response(*, body=None, status=200, reason=None, text=None, heade
rs=None, content_type=None, charset=None)
最常用的响应类,继承于 StreamResponse 。
body参数用于设置HTTP响应主体。
实际发送body发生在调用write_eof()时。
参数:
body (bytes) - 响应主体。
status (int) - HTTP状态码,默认200。
headers (collections.abc.Mapping) - HTTP 头信息,会被添加到响应里。 text (str) - 响应主体。
content_type (str) - 响应的内容类型。如果有传入text参数的话则
为text/plain ,否则是
charset (str) - 响应的charset。如果有传入text参数则为
body
存储响应内容或者叫响应主体,该属性可读可写,类型为bytes。
设置body会重新计算content_length的值。
设置body为None会将content_length也一并设置为None,也就是不写入
text
存储响应内容(响应主体),该属性可读可写,类型为str。
设置text会重新计算content_length和body的值。
设置text为None会将content_length也一并设置为None,也就是不写入
WebSocketResponse
131
服务器端参考
class aiohttp.web.WebSocketResponse(\, timeout=10.0, receive_timeout=None,
autoclose=True, autoping=True, heartbeat=None, protocols=(), compress=True)
用于进行服务端处理websockets的类。
调用 prepare() 后你就不能在使用 write() 方法了,但你仍然可以与 websocket客户端通过 send_str() , receive() 等方法沟通。
新增于 1.3.0版本。
autoping (bool) - 自动向发送了PING消息的客户端发送PONG消息,以及自动 处理客户端的PONG响应。需要注意的是该方法不会自动向客户端发送PING消 息,你需要自己调用ping()方法实现。新增于1.3.0版本。
heartbeat (float) - 每一次心跳都发送ping消息并等待pong消息,如果没有接收
到pong响应则关闭连接。
receive_timeout (float) - 接收操作的超时时间。默认是None也就是不限时间。 compress (float) - 允许每一次消息都使用deflate扩展。传入False禁止此功 能。默认是True。(文档中应该写错啦,不应该是float,应该是bool,或者也 应该是int(False和True对应0和1)) 新增于 0.19版本的内容: 该类支
持async for 语句来对即将到来的消息进行迭代: ``` ws = web.WebSocketResponse() await ws.prepare(request)
async for msg in ws: print(msg.data)
*coroutine prepare(request)*
开启websocket。调用此方法后
你就可以使用其他的websockets方法了。
**参数**: request (aiohtt
p.web.Request) - HTTP 请求对象,要响应的对象。
如果websocket握手失败将抛出`
HTTPException`异常。
新增于 0.18版本。
**can_prepare(request)**
检测这个请求是否能够开启websock
et。
如果`can_prepare()`执行成功,
则`prepare()`也会执行成功。
132
服务器端参考
**参数**: request (aiohttp.
web.Request) - HTTP请求对象,要响应的对象。 返回WebSocketReady实例对象。
如果成功的话,WebSocketReady. ok为True,WebSocketReady.protocol是websocket的子协议,由客户端传递被 服务端所接受(一个由WebSocketResponse构造函数所产生的协议序列)。
如果客户端和服务器端的子协议不一
致,WebSocketReady.protocol则为None。
###注意
该方法不会抛出任何异常。
**closed**
如果连接已经关闭或正在关闭则为Tr ue。这时CLOSE消息已经从peer中接受到了。该属性只读。
**close_code**
peer所发送的关闭代码。如果连接是
打开的则为None。该属性只读。
**protocol**
WebSocket子协议,调用`start()
`后才会选择。
如果客户端和服务器端的子协议不一
致,则为None。
**exception()**
返回最后发生的异常,或者None。
**ping(message=b'')**
向peer发送PING消息。
**参数**: message - 可选ping
timeError`异常。
**pong(message=b'')**
发送迷之PONG消息到peer。
133
服务器端参考
**参数**:message - 可选pong
timeError`异常。
*coroutine send_str(data)*
向peer发送TEXT(文本)消息。
**参数**:data (str) - 要发送
的数据。
**可能抛出的异常**:
*RuntimeError - 如果连接未开启或已关闭则抛出。
*TypeError - 如果data不是字符串(str)则抛出。
*coroutine send_bytes(data)*
向peer发送BINARY(二进制)消息。
**参数**: data - 要发送的数据
。
**可能抛出的异常**:
*RuntimeError - 如果连接未开启或已关闭则抛出。
*TypeError - 如果data不是字节(bytes),字节数组(bytearray)或内存查 看对象(memoryview)则会抛出。
*coroutine send_json(data, \*, dumps=json.dum ps)*
向peer发送JSON字符串。
**参数**:
*data - 待发送的数据。
*dumps(callable) - 任何接受某对象并返回JSON字符串的可调用对象(默认是js
on.dumps())。
**可能抛出的异常**:
*RuntimeError - 如果连接未开启或已关闭则抛出。
*ValueError - 如果data不是序列化对象则会抛出。
*TypeError - 如果由dumps参数所传进的对象处理后的值不是str则会抛出。
*corotine close(\*, code=1000, message=b'')*
一个初始化关闭握手消息的协程方法
。
134
服务器端参考
不同任务中的close()调用会被保留
。
**参数**:
*code(int) - 关闭代码。
*message -
。
**可能抛出的异常**:
* RuntimeError - 如果连接未开启则抛出。
*coroutine receive(timeout=None)*
等待peer即将发来的数据消息并返回
它的协程方法。
该方法在PING,PONG和CLOSE处理
时都有调用,但并不返回。
和closing握手。
###注意
该方法只能在请求处理任务中被调用。
**参数**: timeout - 接受操作
的超时时间。会覆盖响应中receive_timeout属性的值。 返回WSMessage实例对象。 如果连接未开启则抛出`RuntimeEr
ror`异常。
*coroutine receive_str(\*, timeout=None)*
调用`receive()`方法,并判断其
消息类型是否为TEXT(文本)。
###注意
该方法只能在请求处理任务中被调用。
**参数**: timeout - 接受操作
的超时时间。会覆盖响应中receive_timeout属性的值。
返回peer发来的消息数据。 如果消息类型是BINARY则会抛出`Ty
peError`异常。
*coroutine receive_bytes(\*, timeout=None)*
135
服务器端参考
调用`receive()`方法,并判断其
消息类型是否为BINARY(二进制)。
###注意
该方法只能在请求处理任务中被调用。
**参数**: timeout - 接受操作
的超时时间。会覆盖响应中receive_timeout属性的值。
返回peer发来的消息数据。 如果消息类型是TEXT则会抛出`Type
Error`异常。
*coroutine receive_json(\*, loads=json.loads, timeout=None)*
调用`receive_str()`方法,并将
JSON字符串转换为Python字典(dict)。
###注意
该方法只能在请求处理任务中被调用。
**参数**:
* loads (callable) - 任何接受str并返回JSON内容的可调用对象(默认是json
.loads())。
* timeout - 接受操作的超时时间。会覆盖响应中receive_timeout属性的值。
返回解析JSON后的dict对象。
**可能抛出的异常**:
*TypeError - 如果消息类型为BINARY则会抛出。
*ValueError - 如果消息数据并不是合法的JSON数据则会抛出。
该方法新增于0.22版本。
###扩展
<a
lob/master/aiohttp%E6%96%87%E6%A1%A3/ServerUsage.md#websockets">
WebSockets处理</a>。
### WebSocketReady
*class aiohttp.web.WebSocketReady*
`WebSocketResponse.can_prepare()`所返回的对象。
可使用bool类型判断:
136
服务器端参考
if not await ws.can_prepare(...): cannot_start_websocket()
**ok**
如果websocket可以建立则返回Tru
e,否则是False。
**protocol**
表示websocket选择的子协议,类型为str。
### json_response
*aiohttp.web.json_response([data, ]\*, text=None, body=None, sta tus=200, reason=None, headers=None, content_type='application/js on', dumps=json.dumps)*
返回内容为JSON数据(默认由json.dumps()转换),并带
有'application/json'信息的响应对象。
##应用和路由器
### 应用
应用(Application)是web服务器的代名词。
要得到完整地可工作例子,你必须创建应用(Application)和路由器(Router)并 且使用`Server`创建服务器套接字作为协议工厂。*Server*可以使用`Applicatio
n.make_handler()`来创建。
应用(Application)中包含一个路由实例对象和一个在应用运行期间被调用的回调列 表。
同时应用(Application)还是一个类字典对象,所以你可以用它作为<a href="http
ohttp%E6%96%87%E6%A1%A3/ServerUsage.md#数据共享">全局共享数据容器</a >,你可以在处理器中使用`Request.app`来访问它:
app = Application() app['database'] = await aiopg.create_engine(**db_config)
async def handler(request): with (await request.app['database']) as conn: conn.execute("DELETE * FROM table")
尽管它是个类字典对象,你也不能用`Application.copy()`来弄个副本。
*class aiohttp.web.Application(\*, logger=<default>, router=None
137
服务器端参考
, middlewares=(), handler_args=None, client_max_size=1024\*\*2, loop=None, debug=...)*
该类继承于dict。 **参数**:
* logger - logging.Logger实例对象,用于存储应用程序的日志。默认值为`log ging.getLogger("aiohttp.web")`
* router - aiohttp.abc.AbstractRouter实例对象,如果是None则默认创建` UrlDispatcher`。
*middlewares - 存放中间件工厂的列表,请看Middlewares一节获取详细信息。
*handler_args - 类字典对象,用于覆盖`Application.make_handler()`中
的关键字参数。
*client_max_size - 客户端请求中携带的数据的最大大小。如果POST请求超过这
个值,将会抛出`HTTPRequestEntityTooLarge`异常。
*loop - 事件循环。自2.0版本后不再赞成使用:在冻结阶段Loop会被自动设置。
*debug - 调试组件。
**router**
返回router实例对象,该属性只读
。
**logger**
返回logging.Logger实例对象(用
于存储应用程序日志)。
**loop**
返回用于处理HTTP请求的<a href=
**debug**
返回布尔值,表示debug组件是否开
启。
**on_response_prepare**
一个在`StreamResponse.prepar e()`执行时发送的信号,触发信号时将请求(request)和响应(response)对象作
为参数传递。在某些情况下很有用,比如说,你想为每个响应都添加一个自定义头信息
。
信号处理器需要具有如下特征:
138
服务器端参考
async def on_prepare(request, response): pass
**on_startup**
一个在应用程序开启时触发的信号。
我们可以捕获这个信号来做一些后台
任务。
信号处理器需要具有如下特征:
async def on_startup(app): pass
###扩展:
<a
lob/master/aiohttp%E6%96%87%E6%A1%A3/ServerUsage.md#后台任务">后台
任务</a>。
**on_shutdown**
一个在应用程序关闭时触发的信号
。
我们可以捕获这个信号来做一些对于 需要长时间运行的连接的清理工作(websockets和数据流之类的)。 信号处理器需要具有如下特征:
async def on_shutdown(app): pass
139
服务器端参考
当然,用户需要弄清楚哪些web处理器还在工作以及怎么才能正确地结束它们。
我们的建议是将那些需要长时间运行的处理器添加到一个列表中并放在Application中
。
**on_cleanup**
一个在应用程序执行清理时发送的信
号。
我们可以捕获这个信号来优雅的关闭
数据库服务器之类的连接。
信号处理器需要具有如下特征:
async def on_cleanup(app): pass
140
服务器端参考
**make_handler(loop=None, \*\*kwargs)**
创建一个处理请求的HTTP协议工厂。
**参数**:
*loop - 用于处理HTTP请求的事件循环。如果该参数为None则使用`asyncio.get _event_loop()`来获取默认事件循环。2.0版本后已不再赞成使用。
*tcp_keepalive (bool) - 是否允许`TCP
*keepalive_timeout (int) -
*slow_request_timeout - 缓慢请求的超时时间。默认是0。
*logger - 自定义logger对象。默认是`aiohttp.log.server_logger`。
*access_log - 自定义logger对象。默认是`aiohttp.log.access_logger`
。
*access_log_class - access_logger的类。默认是`aiohttp.helpers.Acc
essLogger`。必须是`aiohttp.abc.AbstractAcessLogger`的子类。
* access_log_format (str) - 访问日志的字符串格式。默认是`helpers.Acce ssLogger.LOG_FORMAT`。
*debug (bool) - 选择调试组件。默认是`False`。为了有效利用`Application
.make_handler()`,自1.0版本后不再建议使用该方法中的这个参数。因为`Applic ation`的调试组件应该只有一个。
*max_line_size (int) - 最大标题行(header line)大小。默认是8190。
*max_headers (int) - 最大标题(header)大小。默认是32768。
*max_field_size (int) - 最大标题字段(header field)大小。默认是8190
。
*linegering_time (float) - 当延迟关闭开启时,服务器读取以及忽略从客户 端传来的额外信息的最大时间。传入0来禁止延迟关闭。
*linegering_timeout (float) - 延迟关闭工作时,等待客户端送达额外信息的 超时时间。
你应该把这个方法所返回的结果作为` protocol_factory`传递给`create_server()`:
loop = asyncio.get_event_loop()
app = Application()
141
服务器端参考
setup route table
app.router.add_route(...)
await loop.create_server(app.make_handler(), '0.0.0.0', 8080)
*coroutine startup()*
一个会与应用程序的请求处理器一起
调用的协程方法。
该方法的目的是调用on_startup信
号所连接的处理器。
*coroutine shutdown()*
该方法应该在服务器正在停止且在(
要)调用`cleanup()`前调用。
该方法会调用on_shutdown信号所
连接的处理器。
*coroutine cleanup()*
该方法应该在服务器正在停止且在(
要)调用`shutdown()`后调用。
该方法会调用on_cleanup信号所连
接的处理器。
###注意
Application对象拥有路由属性,但并不拥有add_route()方法。原因是:我们
想支持不同的路由部署方式(甚至基于遍历而不是基于url匹配)。
由于这个原因,我们有非常细小的AbstractRouter抽象类:这个抽象基类也只 有一个AbstractRouter.resolve()协程方法。
没有添加路由和倒推路由(由路由名来获得URL)的方法。这些已是路由部署的全 部细节了(但是说真的,用这个路由你需要在你的应用程序中自己解决这个问题)。
## Server
一个与`create_server()`兼容的协议工厂。
*class aiohttp.web.Server*
该类用于创建处理HTTP连接的HTTP协议对象。
142
服务器端参考
*Server.connections*
一个包含当前已开启的连接的列表。
*aiohttp.web.request_count*
已处理请求的总数。
新增于 1.0版本。
*coroutine Server.shutdown(timeout)*
一个用于关闭所有已开启连接的协程方法。
## Router
用于将分发URL到特定的处理器,aiohttp.web使用路由来建立联系。 路由可以是任何部署了`AbstractRouter`接口的对象。 aiohttp.web提供的部署方式为`UrlDispatcher`。
`Application`也使用`UrlDispatcher`作为`router()`的默认返回。
*class aiohttp.web.UrlDispatcher*
最直接地url匹配型路由,同时具有`collections.abc.Ma
pping`的功能,可以用于访问已命名的路由。
在运行`Application`之前,你应该首先调用`add_route ()`和`add_static()`来填写下路由表。
处理器的查找是迭代方式进行的(先进先出顺序)。首先匹配 到的路由会被调用对应处理器。
如果在创建路由时你指定了`name`参数,那么这就是一个命
名路由了。
命名路由可以调用`app.router[name]`获得,也可以用于 检测该名字是否在`app.router`中。
**add_resource(path, \*, name=None)**
添加一个资源到路由表尾部。
path可以只是字符串`'/a/b/c'`,
也可以带变量`'/a/{var}'`。
**参数**:
*path (str) - 资源路径。
*name (str) - 资源名,可选。
返回创建的资源实例(`PlainReso
urce或DynamicResource`)。
143
服务器端参考
**add_route(method, path, handler, \*, name=N one, expect_handler=None)**
添加一个处理器到路由表尾部。
path可以只是字符串`'/a/b/c'`,
也可以带变量`'/a/{var}'`。
注意: 如果处理器是一个普通函数,
aiohttp会在内部将其转换为协程函数。
**参数**:
*method (str) - 该路由的HTTP方法。应是 'GET', 'POST', 'PUT', 'DELE TE', 'PATCH', 'HEAD', 'OPTIONS' 其中的一个,或者是'\*'来表示所有方法。 该方法大小写不敏感,'get'等于'GET'。
*paht (str) - 路由路径。需要以斜线('/')开头。
*handler (callable) - 路由处理器。
*name (str) - 路由名称,可选。
*expect_handler (coroutine) - 异常处理器,可选。
返回`PlainResource或DynamicR
esource`实例对象。
**add_routes(routes_table)**
从路由表(routes_table)中注册
路由。
路由表(routes_table)需要是包 含`RouteDef`组件或`RouteTableDef`的列表。 新增于2.3版本。
**add_get(path, handler, \*, name=None, allow _head=True, \*\*kwargs)**
添加`GET`方法路由的快捷方式。等
价于调用`add_route(method='GET')`。
如果`allow_head`是True(默认
),也会添加一个HEAD方法到这个路由上。
如果有name,HEAD的路由会被添加
te')`会添加两个路由:一个是GET方法名为'route',另一个是HEAD方法名为'rout
该方法新增于1.0版本。
2.0版本新增内容: 添加allow_hea
d参数。
144
服务器端参考
**add_post(path, handler, \*\*kwargs)**
添加'POST'方法路由的快捷方式。
等价于调用`add_route(method='POST')`。
新增于1.0版本。
**add_put(path, handler, \*\*kwargs)**
添加`PUT`方法路由的快捷方式。等
价于调用`add_route(method='PUT')`。
新增于 1.0版本。
**add_patch(path, handler, \*\*kwargs)**
添加`PATCH`方法路由的快捷方式。 等价于调用`add_route(method='PATCH')`。 新增于1.0版本。
**add_delete(path, handler, \*\*kwargs)**
添加`DELETE`方法路由的快捷方式 。等价于调用`add_route(method='DELETE')`。
新增于 1.0版本。
**add_static(prefix, path, \*, name=None, exp ect_handler=None, chunk_size=256\*1024, response_factory=StreamR esponse, show_index=False, follow_symlinks=False, append_version =False)**
添加一个用于返回静态文件的路由和
处理器。
对于获取图片,js和css等文件非常
有用。
在支持它的平台上,使用这个可以让 文件发送系统(sendfile system)处理器更高效的转发这类文件。 在某些情况下,即使平台支持也要避 免使用文件发送系统。可以使用环境变量AIOHTTP_NOSENDFILE=1来设置它。 如果静态文件的内容是gzip压缩的内 容,文件的路径需要+ `.gz`,同样也会应用在响应中。
145
服务器端参考
###警告:
add_static()仅用于开发。生存环境中,静态文件功能应由wbe服务器(比如ng
inx,apache)提供。
0.18.0版本修改的内容: 在支持的 平台中,转发文件使用文件发送系统(sendfile system)。
0.19.0版本修改的内容: 可以使用
环境变量`AIOHTTP_NOSENDFILE=1`来关闭文件发送系统。
1.2.0 版本修改的内容: 发送gz
ip压缩的文件路径后会+`.gz`。
**参数**:
*prefix (str) - URL路径前缀,用于加到静态文件中。
*path - 包含静态文件的文件系统中的文件夹,可以是str或`pathlib.Path`。
*name (str) - 路由名称,可选。
*expect_handler (coroutine) - 异常处理器,可选。
*chunk_size (int) - 下载文件时单个分块的最大大小,默认是256Kb。增加chu nk_size参数的值,比如说1Mb可能会提高下载速度,但同时也会增加内存占用。新增 于 0.16版本。
*response_factory (callable) - 用于制造新响应的工厂,默认是`StreamRe sponse`,传入的对象应有兼容的API。新增于0.17版本。
*show_index (bool) - 是否允许显示目录索引的标识,默认不允许并且返回`HTT
P/403`。
* follow_symlinks (bool) - 是否允许使用符号链接的标识,默认是不允许并返
回`HTTP/404`。
* append_version (bool) - 是否给url查询字符串添加文件版本号(hash)的 标识,不过当你调用`StaticRoute.url()`和`StaticRoute.url_for()`的时候 总会使用默认值。
返回一个`StaticRoute`实例对象
。
**add_subapp(prefix, subapp)**
在给定路径前缀下注册一个嵌套子应
用。
以定前缀开始的请求会交由subapp
处理。
**参数**:
* prefix (str) - 资源的路径前缀。
146
服务器端参考
* subapp (Application) - 在给定前缀下的嵌套应用。
返回`PreFixedSubAppResource`
实例对象。
新增于 1.1版本。
*coroutine resolve(request)*
返回request的`AbstractMatchI
nfo`实例对象。
该方法不会抛出任何异常,但是返回 的`AbstractMatchInfo`的`http_exception`可能会有`HTTPException`实例 对象。
如果该请求没有与任何注册的路由相 匹配,请求处理器会抛出`HTTPNotFound`或`HTTPMethodNotAllowed`的异常。
中间件就可以捕获这些异常来显示一
个漂亮的错误页面。
内部使用,终端用户几乎不需要调用
这个方法。
###注意
该方法使用Request.raw_path来匹配已注册的路由。
**resources()**
返回一个包含所有已注册的资源的`v
iew`。
`view`是一个允许这样做的对象:
从中获取路由表大小:
len(app.router.resources())
进行迭代:
for resource in app.router.resources(): print(resource)
检测某资源是否已经在路由表中:
route in app.router.resources()
147
服务器端参考
该方法新增于 0.21.1。
**routes()**
返回一个包含所有已注册的路由的`v
iew`。
新增于 0.18版本。
**named_resources()**
返回一个包含所有已命名资源的类字
典对象types.MappingProxyType的`view`。
view会将所有已命名资源的名字变成
`BaseResource`实例对象。 支持常规类字典操作,除了可变性(它是只读的)。
len(app.router.named_resources())
for name, resource in app.router.named_resources().items(): print(name, resource)
"name" in app.router.named_resources()
app.router.named_resources()["name"]
148
服务器端参考
该方法新增于 0.21版本。
**named_routes()**
与`named_resources()`一样,这
个要早些,因为0.21版本才有`named_resources()`。
该方法新增于 0.19版本。
0.21版本修改的内容: 该方法变为
named_resources()的副本,所以请用resources代替routes。
自0.21版本后不再赞成使用: 请使
用`named_resources`代替`name_routes`。
与资源相同的路由会共用一个资源名
称。
## Resource
默认路由器`UrlDispatcher`会同<a href="https://github.com/HuberTRoy
Glossary.md#resource">资源(resource)</a>一起工作。
资源是路由表中的一个含有路径,独特的名字和至少有一条路由的组件。 web处理器会按以下方式进行检索:
1.从资源中一个个迭代。
2.如果某资源与请求的URL相匹配则迭代其包含的路由。
3.如果某路由的方法与请求的URL相匹配,该路由的处理器就会作为web处理器进行后 续处理。之后检索结束。
4.否则路由会尝试下一条路由表中的资源。
5.如果检索到最后都没有一个 资源/路由对符合请求,那么会返回一个特殊的`Abstr actMatchInfo`实例对象,该对象会附带 `AbstractMatchInfo.http_exceptio
n = HTTPException`或者说`HTTP 404 Not Found / HTTP 405 Method Not
Allowed`状态码。这时所注册的`AbstractMatchInfo.handler`会开始工作。
用户永远不要随意实例化一个资源类,只需要在用到时把它用`UrlDispatcher.add_ resource()`添加即可。
添加之后可能还需要添加路由: `Resource.add_route()`。
`UrlDispatcher.add_route()`是下列方式的简化版:
router.add_resource(path).add_route(method, handler)
149
服务器端参考
有名字的资源被叫做 已命名资源(named resource)。已命名资源存在的主要原因 是使用路由名来构建URL时可以传递它到模板引擎中使用:
url = app.router['resource_name'].url_for().with_query({'a': 1, 'b': 2})
资源类的继承等级:
*AbstractResource
*- Resource
*- - PlainResource
*- - DynamicResource
*- - StaticResource
*class aiohttp.web.AbstractResource*
所有资源的基类。
继承自`collections.abc.Sized`和`collections.ab
c.Iterable`。
`len(resource)`会返回属于该资源的路由总数,也允许进
行迭代`for route in resource`。
**name**
资源的名称,或None,该属性只读。
### xxx
*coroutine resolve(method, path)*
尝试寻找合适的web处理器(method
,path)来解析该资源。
**参数**:
*method (str) - 请求的HTTP方法。
*path (str) - 请求的路径。
返回(match_info, allowed_methods)。
allowed_methods 是一个包含资源可接受的HTTP方法的集
合。
如果请求被解析(resolve)match_info会是`UrlMappin
gMatchInfo`,如果没有找到任何路由则是None。
### xxx
**get_info()**
返回资源的描述。如`{'path': '/
150
服务器端参考
path/to'}`, `{'formatter': '/path/{to}', 'pattern': re.compile(r
**url_for(\*args, \*\*kwargs)**
构建一个该路由的URL(可附带额外
参数)。
`args`和`kwargs`依赖继承于资源
类的可接受参数列表。
返回URL实例对象。
新增于 1.1版本。
**url(\*\*kwargs)**
同上。
1.1版本后不再赞成使用,请使用`u
rl_for()`代替。
*class aiohttp.web.Resource*
新类型资源的基类,继承于`AbstractResource`。
**add_route(method, handler, \*, expect_handl er=None)**
添加一个web处理器到资源中。
**参数**:
*method (str) - 路由可接受的HTTP方法。应是'GET', 'POST', 'PUT', 'DE LETE', 'PATCH', 'HEAD', 'OPTIONS'中的一个,或传入'\*'表示任何方法。该 方法大小写不敏感,'get'=='GET'。对该资源来说,method必须是唯一的。
*handler (callable) - 路由处理器。
*expect_handler (coroutine) - 异常处理器,可选。
返回`ResourceReoute`实例对象
。
*class aiohttp.web.PlainResource*
一个资源类,继承于`Resource`。
该类等价于使用普通文本匹配的资源。如'/path/to'。
151
服务器端参考
**url_for()**
返回所属资源的URL。
新增于 1.1版本。
*class aiohttp.web.DynamicResource*
一个资源类,继承于`Resource`。
该类等价于使用<a href="https://github.com/Huber
%A3/ServerUsage.md#可变形资源">变量匹配的资源</a>。如'/path/{to}/{par
am}'。
**url_for(\*\*params)**
返回所属资源的URL。 **参数**: params - 置换动态资
源的变量。
比如 '/path/{to}/{param}',
调用的时候应该是`resource.url_for(to="val1", param='val2')`。
新增于 1.1版本。
*class aiohttp.web.StaticResource*
一个资源类,继承于`Resource`。
等价于使用<a href="https://github.com/HuberTRoy/
erverUsage.md#静态文件的处理">静态文件的资源</a>。
**url_for(filename, append_version=None)**
返回文件路径并带有资源的前缀。
**参数**:
* filename - 文件名,由静态文件处理器调用。接受str和`pathlib.Path`。 ' /prefix/dir/file.txt'由`resource.url_for(filename='dir/file.txt'
)`生成。
*append_version (bool) - 是否给url查询字符串添加文件版本号(hash)的 标识(可以加速缓存)。默认情况(False)下会由构造器产生一个值,当是Ture的时 候 `v=FILE_HASH`格式的查询字符串参数会被添加到URL中,False则不做操作。如 果文件未找到也不会有任何操作。
新增于 1.1版本。
152
服务器端参考
*class aiohttp.web.PreFixedSubAppResource*
一个用于嵌套应用的资源类。该类由`add_subapp`方法返回
而来。
新增于1.1版本。
**url_for(\*\*kwargs)**
该方法不可调用,调用会抛出`Runt
imeError`异常。
## Route
路由(Route)具有HTTP方法(通配符'\*'是可以用的),web处理器和异常处理器( 可选)。
每个路由都可属于多个资源。 路由类等级:
*AbstractRoute
*- ResourceRoute
*- SystemRoute
`ResourceRoute`是一个面向资源的路由,`SystemRoute`面向的是URL解析错误的
处理,比如`404 Not Found`和`405 Method Not Allowd`之类的。
*class aiohttp.web.AbstractRoute*
`UrlDispatcher`可使用的路由基类。
**method**
该路由要处理的HTTP方法。'GET',
'POST'之类。
**handler**
该路由的处理器。
**name**
该路由的名称,等价于资源名称中属
于该路由的名称。
**resource**
持有该路由的资源,如果该类是`Sys
temRoute`则为None。
**url_for(\*args, \*\*kwargs)**
153
服务器端参考
返回该路由所能构建的url。(只是
个基础方法,意思是功能要自己继承后实现)
同时也是`route.resource.url_
for(...)`的快捷方式。
*coroutine handle_expect_header(request)*
*class aiohttp.web.ResourceRoute*
一个用于在资源中处理不同HTTP方法的路由类。
*class aiohttp.web.SystemRoute*
一个用于处理URL错误(404/405之类的)的路由类。
**status**
HTTP状态码。
**reason**
HTTP状态码说明。
##RouteDef
路由定义,是对还没注册路由的描绘,说明。
在填写路由表时使用的是包含路由定义的列表时非常有用(Django风格)。
定义由`get()`或`post()`之类的函数创建,包含定义的列表可以用`UrlDispatch er.add_routes()`添加到路由器上:
from aiohttp import web
async def handle_get(request): ...
async def handle_post(request): ...
app.router.add_routes([web.get('/get', handle_get), web.post('/post', handle_post),
*class aiohttp.web.RouteDef*
创建一个还没被添加的路由的定义。
154
服务器端参考
**method**
HTTP方法(GET, POST之类),类
型为字符串。
**path**
资源的路径,比如/path/to,可以
是包含`{}`的<a
可变形资源">可变资源</a>,类型为字符串。
**handler**
用于处理HTTP请求的协程函数。
**kwargs**
包含额外参数的字典。
新增于2.3版本。
*aiohttp.web.get(path, handler, \*, name=None, allow_head=True,
expect_handler=None)*
返回处理GET请求的`RouteDef`。请看`UrlDispatcher.
add_get()`获取参数信息。
新增于2.3版本。
*aiohttp.web.post(path, handler, \*, name=None, expect_handler=N one)*
返回处理POST请求的 'RouteDef'。请看`UrlDispatche
r.add_post()`获取参数信息。
新增于2.3版本。
*aiohttp.web.head(path, handler, \*, name=None, expect_handler=N one)*
返回处理HEAD请求的`RouteDef`。请看`UrlDispatcher
.add_head()`获取参数信息。
新增于2.3版本。
*aiohttp.web.put(path, handler, \*, name=None, expect_handler=No ne)*
返回处理PUT请求的`RouteDef`。请看`UrlDispatcher.
155
服务器端参考
add_put()`获取参数信息。
新增于2.3版本。
*aiohttp.web.patch(path, handler, \*, name=None, expect_handler= None)*
返回处理PATCH请求的`RouteDef`。请看`UrlDispatche
r.add_patch()`获取参数信息。
新增于2.3版本。
*aiohttp.web.delete(path, handler, \*, name=None, expect_handler =None)*
返回处理DELETE请求的`RouteDef`。请看`UrlDispatch
er.add_delete()`获取参数信息。
新增于2.3版本。
### xxx
*aiohttp.web.route(method, path, handler, \*, name=None, expect_ handler=None)*
返回处理POST请求的`RouteDef`(文档中应该写错了,这 个方法处理什么由method决定)。请看`UrlDispatcher.add_route()`获取参数 信息。
新增于2.3版本。
## RouteTableDef
路由表定义(RouteTableDef)用于以装饰器模式描绘路由(Flask风格):
from aiohttp import web
routes = web.RouteTableDef()
@routes.get('/get') async def handle_get(request): ...
@routes.post('/post') async def handle_post(request): ...
app.router.add_routes(routes)
*class aiohttp.web.RouteTableDef*
包含RouteDef实例的序列(具有abc.collections.Sequ
ence的功能)。
除了所有标准列表方法,该类还提供如'get()', 'post()'
156
服务器端参考
之类用于添加新路由定义的方法。
*@get(path, \*, allow_head=True, name=None, e
xpect_handler=None)*
添加一个GET方法的路由定义。
请看`UrlDispatcher.add_get()
`查看更多信息。
*@post(path, \*, name=None, expect_handler=No ne)*
添加一个POST方法的路由定义。
请看`UrlDispatcher.add_post(
)`查看更多信息。
*@head(path, \*, name=None, expect_handler=No ne)*
添加一个HEAD方法的路由定义。
请看`UrlDispatcher.add_head(
)`查看更多信息。
*@put(path, \*, name=None, expect_handler=Non e)*
添加一个PUT方法的路由定义。
请看`UrlDispatcher.add_put()
`查看更多信息。
*@patch(path, \*, name=None, expect_handler=N one)*
添加一个PATCH方法的路由定义。
请看`UrlDispatcher.add_patch
()`查看更多信息。
*@delete(path, \*, name=None, expect_handler= None)*
添加一个DELETE方法的路由定义。
请看`UrlDispatcher.add_delet
e()`查看更多信息。
157
服务器端参考
*@route(method, path, \*, name=None, expect_h andler=None)*
添加任意HTTP方法的路由定义。
请看`UrlDispatcher.add_route
()`查看更多信息。
新增于 2.3版本。
### MatchInfo
路由匹配之后,web应用会调用任何找到的处理器。
匹配结果可以在处理器中使用`Request.match_info`属性访问。
一般情况下,匹配结果可以是任何从`AbstractMatchInfo`继承的对象(如果是默认 `UrlDispatcher`路由器则是`UrlMappingMatchInfo`)。
*class aiohttp.web.UrlMappingMatchInfo*
继承自`dict`和`AbstractMatchInfo`。字典内的项由匹
配的内容填充,其内容只是与资源相关的信息。
**expect_handler**
一个用于处理
程处理器。
**handler**
一个用于处理请求的协程处理器。
**route**
url所匹配的路由实例。
### View
*class aiohttp.web.View(request)*
继承自`AbstractView`。
该类是以类为基础的视图的基类。部署时需要继承`View`并 且要覆盖处理HTTP请求的方法(get(), post()等):
class MyView(View):
158
服务器端参考
async def get(self):
resp = await get_response(self.request) return resp
async def post(self):
resp = await post_response(self.request) return resp
app.router.add_route('', '/view', MyView) `` 如果请求的web
方法不支持的话,视图会抛出 405 Method Not allowed (HTTPMethodNotAllowed)`。
*参数:request - 创建视图处理的Request实例对象。
request
发送到视图构造器中的请求,该属性只读。
可覆盖的协程方法有: connect(), delete(), get(), head(), options(), patch(), post(), put(), trace() 。
其他工具
class aiohttp.web.FileField
该类是在Request.POST()有上传文件时所返回的类(作为并联字典的 namedtuple实例对象)。
name
字段名。
filename
上传时指定的文件名字。
file
携带上传的文件的内容的 io.IOBase 实例对象。
content_type
上传的文件的MIME类型,默认是'text/plain'。
aiohttp.web.run_app(app, \, host=None, port=None, path=None, sock=None, shutdown_timeout=60.0, ssl_context=None, print=print, backlog=128, access_log_format=None, access_log=aiohttp.log.access_logger,
159
服务器端参考
handle_signals=True, loop=None)*
用于运行应用程序的函数,会一直运行,直到键盘打断然后执行关闭操作。
适合作为基础aiohttp项目使用。生存配置可能需要更复杂些的启动器,但在项 目最初阶段,用它就够了。
该函数使用 app.loop 作为事件循环。
服务器会监听你所提供的所有主机或Unix域套接字路径。如果没有提供任何主 机或路径,或只提供了端口,则TCP服务器会监听 0.0.0.0 (表示所有主机)。
在同一应用程序中将HTTP请求分发到多个主机或路径对于在同一个事件循环中 处理请求没有执行效率上的提升。请查阅Server Deployment来了解如何进行分发 工作以提升效率。
参数:
app - 要运行的 Application 实例对象。
host (str) - 用于监听的TCP/IP 主机或一组主机序列。如果有提供端口或path没
有提供则默认是 0.0.0.0 。
port (int) - 用于监听TCP/IP 端口。对于普通HTTP默认是8080,经由SSL的 HTTP默认则是8443(需要指定ssl_context参数)。
path (str) - 作为HTTP服务器,Unix域套接字的文件系统中的路径。绑定多个 域套接字可以是一组路径序列。监听Unix域套接字并不是支持所有操作系统 的。
sock (socket) - 预先存在的套接字(socket)对象,用于接收连接。可以传递 一组套接字(socket)序列。
shuntdown_timeout (int) - 关闭服务器时,解除所有已连接的客户端套接字的
超时时间。配置有良好关闭程序的系统基本不会用到这个超时时间,配置良好 只需要几毫秒即可完成关闭。
ssl_context - HTTPS服务器所使用的ssl.SSLContext,None的话会使用HTTP
连接。
print - 请传入与 print() 相同的可调用对象。覆盖 STDOUT 输出或取缔它时 会有些用处。传入None也可以禁止输出。
backlog (int) - 无法接受的连接的总数,达到设定的值时会拒绝新连接(默认
128)。
access_log - 用于存储访问日志的 logging.Logger 实例对象。传入None来 禁用日志可以对响应速度有些许提升。
handle_signals (bool) - 是否覆盖信号TERM处理来正常关闭应用程序。 loop - 用于运行应用程序的事件循环(默认是None)。如果loop并未显式指 定,函数关闭时(调用close()后)不会对非默认循环做任何事。
160
服务器端参考
常量
class aiohttp.web.ContentCoding
可以使用的内容代码(Content Codings)的枚举( enum.Enum )类。
deflate
表示 DEFLATE 压缩。
gzip
表示 GZIP 压缩。
aiohttp.web.identity
表示无压缩。
中间件
标准化路径的中间件
aiohttp.web.normalize_path_middleware(\, append_slash=True,
merge_slashes=True)*
标准化请求中路径的中间件。标准化的意思是:
在路径尾添加一个斜线('/')。
只要改路径符合规范,则立即返回。如果两个参数所指代的功能都是允许的 话,执行顺序是这样的:
1.merge_slashes
2.append_slash
3.执行merge_slashes和append_slash
如果路径至少符合其中一个条件,则重定向到一个新路径上。
在需要添加斜线时 append_slash 也需要是Ture才会执行。是True的话当一个 资源定义时尾部有斜线,但请求没有斜线时,将会自动给请求加上斜线。
如果merge_slashes是True,将会把路径中连续的斜线变成一个斜线。
161
服务器端参考
162
日志
日志
aiohttp使用标准库logging追踪库活动。
aiohttp中有以下日志记录器(以名字排序):
'aiohttp.access'
'aiohttp.client'
'aiohttp.internal'
'aiohttp.server'
'aiohttp.web'
'aiohttp.websocket'
你可以追踪这些记录器来查看日志信息。此文档中没有关于配置日志追踪的说明, logging.config.dictConfig()对于将记录器配置到应用程序中来说已经很好用了。
访问日志
默认情况下访问日志是开启的使用的是'aiohttp.access'记录器。 可以调用aiohttp.web.Applicaiton.make_handler()来控制日志。
将logging.Logger实例通过access_log参数传递即可覆盖默认记录器。
注意
可以使用 web.run_app(app, access_log=None)来禁用记录访问日志。
其他参数如access_log_format可以指定日志格式(详情见下文)
指定格式
aiohttp提供自定义的微语言来指定请求和响应信息:
163
日志
选项 |
含义 |
|
|
%% |
百分号 |
|
|
%a |
远程IP地址(如果使用了反向代理则为代理IP地址) |
|
|
%t |
请求被处理时的时间 |
|
|
%P |
处理请求的ID。 |
|
|
%r |
请求的首行。 |
|
|
%s |
响应状态码。 |
|
|
%b |
响应字节码的大小,包括HTTP头。 |
|
|
%T |
处理请求的时间,单位为秒。 |
|
|
%Tf |
处理请求的时间,秒的格式为 %.06f。 |
|
|
%D |
处理请求的时间,单位为毫秒。 |
|
|
%{FOO}i |
request.headers['FOO'] |
|
|
%{FOO}o |
response.headers['FOO'] |
|
|
访问日志的默认格式为:
'%a %l %u %t "%r" %s %b "%{Referrer}i"
aiohttp.helper.AccessLogger替换例子:
class AccessLogger(AbstractAccessLogger):
def log(self, request, response, time): self.logger.info(f'{request.remote} '
f'"{request.method} {request.path} ' f'done in {time}s: {response.status}')
注意
如果使用了Gunicorn来部署,默认的访问日式格式会自动被替换为aiohttp的默认访 问日志格式。
如果要指定Gunicorn的访问日志格式,需要使用aiohttp的格式标识。
164
日志
错误日志
aiohttpw.web使用 aiohttp.server 来存储处理web请求时的错误日志。 默认情况下是开启的。
使用不同的记录器名字请在调用 aiohttp.web.Application.make_handler() 时 指定 logge 参数的值(需要 logging.Logger 实例对象)。
165
测试
测试
aiohttp web服务器测试
aiohttp有一个pytest插件可以轻松构建web服务器测试程序,同时该插件还有一个 用于测试其他框架(单元测试等)的测试框架包。
在写测试之前,我想你可能会想读一读如何写一个可测试的服务器程序感兴趣,因 为它们之间的作用的相互的。
在使用之前,我们还需要安装下才行:
$ pip install
如果你不想安装它,你可以在conftest.py中插入一行
pytest_plugins='aiohttp.pytest_plugin' 来代替这个包。
临时状态说明
该模块是临时的。
对于已经废弃的API,基于向后不兼容政策,aiohttp允许仍可以继续使用一年半的 时间。
不过这对aiohttp.test_tools则不适用。
同时,如有一些必要的原因,我们也会不管向后兼容期而做出更改。
客户端与服务器端测试程序
aiohttp中的test_utils有一个基于aiohttp的web服务器测试模板。
其中包含两个部分: 一个是启动测试服务器,然后是向这个服务器发起HTTP请求。 TestServer使用以aiohttp.web.Application为基础的服务器。RawTestServer则使用
166
测试
基于aiohttp.web.WebServer的低级服务器。 发起HTTP请求到服务器你可以创建一 个TestClient实例对象。 测试客户端实例使用aiohttp.ClientSession来对常规操作如 ws_connect,get,post等进行支持。
Pytest
简易程序如下:
from aiohttp import web
async def hello(request):
return web.Response(text='Hello, world')
async def test_hello(test_client, loop): app = web.Application() app.router.add_get('/', hello) client = await test_client(app) resp = await client.get('/') assert resp.status == 200
text = await resp.text() assert 'Hello, world' in text
同样,它也提供访问app实例的方法,允许测试组件查看app的状态。使用fixture可 以创建非常便捷的app测试客户端:
167
测试
import pytest
from aiohttp import web
async def previous(request):
if request.method == 'POST':
request.app['value'] = (await request.post())['value'] return web.Response(body=b'thanks for the data')
return web.Response(
body='value: {}'.format(request.app['value']).encode('ut
@pytest.fixture
def cli(loop, test_client): app = web.Application() app.router.add_get('/', previous) app.router.add_post('/', previous)
return loop.run_until_complete(test_client(app))
async def test_set_value(cli):
resp = await cli.post('/', data={'value': 'foo'}) assert resp.status == 200
assert await resp.text() == 'thanks for the data' assert cli.server.app['value'] == 'foo'
async def test_get_value(cli): cli.server.app['value'] = 'bar' resp = await cli.get('/') assert resp.status == 200
assert await resp.text() == 'value: bar'
Pytest工具箱里有以下fixture: aiohttp.test_utils.test_server(app, \*kwargs*) 一 个创建TestServer的fixture。
168
测试
async def test_f(test_server):
app = web.Application()
#这里填写路由表
server = await test_server(app)
服务器会在测试功能结束后销毁。
app是aiohttp.web.Application组件,用于启动服务器。
kwargs是其他需要传递的参数。
aiohttp.test_utils.test_client(app, \*kwargs) aiohttp.test_utils.test_client(server, **kwargs) aiohttp.test_utils.test_client(raw_server, **kwargs*) 一个用户创建 访问测试服务的TestClient fixture。
async def test_f(test_client):
app = web.Application()
#这里填写路由表。
client = await test_client(app) resp = await client.get('/')
客户端和响应在测试功能完成后会自动清除。
这个fixture可以接收aiohttp.webApplication, aiohttp.test_utils.TestServer或
aiohttp.test_utils.RawTestServer实例对象。
kwargs用于接收传递给aiohttp.test_utils.TestClient的参数。
aiohttp.test_utils.raw_test_server(handler, \*kwargs*) 一个从给定web处理器 实例创建RawTestServer的fixture。
处理器应是一个可以接受请求并且返回响应的协同程序:
169
测试
async def test_f(raw_test_server, test_client):
async def handler(request): return web.Response(text="OK")
raw_server = await raw_test_server(handler) client = await test_client(raw_server) resp = await client.get('/')
单元测试
使用标准库里的单元测试/基础单元测试的功能来测试应用程序,提供 AioHTTPTestCase类:
170
测试
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loo p
from aiohttp import web
class MyAppTestCase(AioHTTPTestCase):
async def get_application(self):
"""
Override the get_app method to return your application.
"""
return web.Application()
#the unittest_run_loop decorator can be used in tandem with
#the AioHTTPTestCase to simplify running
#tests that are asynchronous
@unittest_run_loop
async def test_example(self):
request = await self.client.request("GET", "/") assert request.status == 200
text = await request.text() assert "Hello, world" in text
#a vanilla example def test_example(self):
async def test_get_route(): url = root + "/"
resp = await self.client.request("GET", url, loop=lo
op)
assert resp.status == 200 text = await resp.text() assert "Hello, world" in text
self.loop.run_until_complete(test_get_route())
class aiohttp.test_utils.AioHTTPTestCase
一个允许使用aiohttp对web应用程序进行单元测试的基础类。
该类派生于unittest.TestCase.
提供下列功能:
171
测试
client
aiohttp测试客户端(TestClient实例)。
server
aiohttp测试服务器(TestServer实例)。 新增于2.3.0版本。
loop
应用程序和服务器运行的事件循环。
app
应用程序(aiohttp.web.Application实例),由get_app()返回。
coroutine get_client()
该方法可以被覆盖。返回测试中的TestClient对象。
返回TestClient实例对象。 新增于2.3.0版本。
coroutine get_server()
该方法可以备覆盖。返回测试中的TestServer对象。
返回TestServer实例对象。 新增于2.3.0版本。
coroutine get_application()
该方法可以被覆盖。返回用于测试的aiohttp.web.Application对象。
返回aiohttp.web.Application实例对象。
coroutine setUpAsync()
默认该方法什么也不做,不过可以被覆盖用于在TestCase的setUp阶段执行 异步代码。 新增于2.3.0版本。
coroutine tearDownAsync()
默认该方法什么也不做,不过可以被覆盖用于在TestCase的tearDown阶段 执行异步代码。 新增于2.3.0版本。
setUp()
标准测试初始化方法。
tearDown()
标准测试析构方法。
注意
TestClient的方法都是异步方法,你必须使用异步方法来执行它的函数。 使用unittest_run_loop()装饰器可以包装任何一个基础类中的测试方法。
172
测试
class TestA(AioHTTPTestCase):
@unittest_run_loop async def test_f(self):
resp = await self.client.get('/')
unittest_run_loop
专门用在AioHTTPTestCase的异步方法上的装饰器。
使用AioHTTPTestCase中的AioHTTPTestCase.loop来执行异步函数。
虚假请求对象
aiohttp提供创建虚假aiohttp.web.Request对象的测试工具:
,在一些简单的单元测试中特别
好用,比如处理器测试,或者很难在真实服务器上重现的错误之类的。
from aiohttp import web
from aiohttp.test_utils import make_mocked_request
def handler(request):
assert request.headers.get('token') == 'x' return web.Response(body=b'data')
def test_handler():
req = make_mocked_request('GET', '/', headers={'token': 'x'}
)
resp = handler(req)
assert resp.body == b'data'
警告
我们不建议在任何地方都用make_mocked_request()来测试,最好使用真实的操作。 make_mocked_request()的存在只是为了测试那些很难或根本不能通过简便方法测试 的复杂案例(比如模仿网络错误)。
173
测试
aiohttp.test_utils.make_mocked_request(method, path, headers=None, \,
version=HttpVersion(1, 1), closing=False, app=None, match_info=sentinel,
reader=sentinel, writer=sentinel, transport=sentinel, payload=sentinel,
sslcontext=None, loop=...)
创建一个用于测试的仿真web.Request。
对于那些在特殊环境难以触发的错误在单元测试中非常有用。
*参数:
method (str) - str, 代表HTTP方法,如GET, POST。 path(str) - str, 带有URL的路径信息但没有主机名的字符串。
headers(dict, multidict.CIMultiDict, 成对列表) - 一个包含头信息的映射对象。 可传入任何能被multidict.CIMultiDict接受的对象。
match_info(dict) - 一个包含url参数信息的映射对象。 version(aiohttp.protocol.HttpVersion) - 带有HTTP版本的namedtuple。 closing(bool) - 一个用于决定是否在响应后保持连接的标识。 app(aiohttp.web.Application) - 带有虚假请求的aiohttp.web.application。
writer - 管理如何输出数据的对象。
transport (asyncio.transports.Transport) - asyncio transport 实例。
payload (aiohttp.streams.FlowControlStreamReader) - 原始载体读取器对象。 sslcontext(ssl.SSLContext) - ssl.SSLContext对象,用于HTTPS连接。
loop (asyncio.AbstractEventLoop) - 事件循环对象,默认是仿真(mocked)循
环。 返回
返回aiohttp.web.Request对象。
2.3版本新增: match_info参数。
未知框架工具箱
创建高等级测试:
174
测试
from aiohttp.test_utils import TestClient, loop_context from aiohttp import request
#loop_context is provided as a utility. You can use any
#asyncio.BaseEventLoop class in it's place.
with loop_context() as loop: app = _create_example_app()
with TestClient(app, loop=loop) as client:
async def test_get_route(): nonlocal client
resp = await client.get("/") assert resp.status == 200 text = await resp.text() assert "Hello, world" in text
loop.run_until_complete(test_get_route())
如果需要更细粒度的创建/拆除,可以直接用TestClient对象:
from aiohttp.test_utils import TestClient
with loop_context() as loop: app = _create_example_app()
client = TestClient(app, loop=loop)
loop.run_until_complete(client.start_server()) root = "http://127.0.0.1:{}".format(port)
async def test_get_route(): resp = await client.get("/") assert resp.status == 200 text = await resp.text() assert "Hello, world" in text
loop.run_until_complete(test_get_route()) loop.run_until_complete(client.close())
你可以在api参考中找到所有工具包清单。
175
测试
编写可测试服务
一些如motor, aioes等依赖asyncio循环来执行代码的库,当它们运行正常程序时, 都会选一个主事件循环给asyncio.get_event_loop。问题在于,当处在测试环境中 时,我们没有主事件循环,因为每个测试都有一个独立的循环。 这样当其他库尝试 找这个主事件循环时就会发生出错。不过幸运的是,这问题很好解决,我们可以显 式的传入循环。我们来看aioes客户端中的代码:
def __init__(self, endpoints, *, loop=None, **kwargs)
如你所见,有一个可选的loop参数。当然,我们并不打算直接测试aioes客户端只是 我们的服务建立在它之上。所以如果我们想让我们的AioESService容易测试,我们 可以这样写:
import asyncio
from aioes import Elasticsearch
class AioESService:
def __init__(self, loop=None):
self.es = Elasticsearch(["127.0.0.1:9200"], loop=loop)
async def get_info(self): cluster_info = await self.es.info() print(cluster_info)
if __name__ == "__main__":
client = AioESService()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.get_info())
注意它接受的loop参数。正常情况下没有什么影响因为我们不用显示地传递loop就 能让服务有一个主事件循环。问题出在我们测试时:
176
测试
import pytest
from main import AioESService
class TestAioESService:
async def test_get_info(self):
cluster_info = await AioESService().get_info() assert isinstance(cluster_info, dict)
如果尝试运行测试,一般会失败并给出类似下面的信息:
...
RuntimeError: There is no current event loop in thread 'MainThre ad'.
因为aioes在主线程中找不到当前的事件循环,所以就报错咯。显式地传递事件循环 可以解决这个问题。 如果你的代码依靠隐式循环工作,你可以需要点小技巧。请看 FAQ。
测试API参考
测试服务器
在随机TCP端口上运行给定的aiohttp.web.Application。
创建完成后服务器并没开始,请用start_server()确保服务器开启和使用close()来确 保关闭。
测试服务器通常与aiohttp.test_utils.TestClient连用,后者可以提供便利的客户端方 法来访问服务器。
class aiohttp.test_utils.BaseTestServer(\, scheme='http', host='127.0.0.1')
测试服务器的基础类。
*参数:
scheme(str) - HTTP协议,默认是无保护的“http”。
177
测试
host(str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。
scheme
被测试应用使用的协议,'http'是无保护的,'https'是有TLS加密的。
host
用于启动测试服务器的主机名。
port
用于启动测试服务器的端口(随机的)。
handler
用于处理HTTP请求的aiohttp.web.WebServer对象。
server
用于管理已接受连接的asyncio.AbstractServer对象。 coroutine start_server(loop=None, \*kwargs)
参数: loop(asyncio.AbstractEventLoop) - 用于开启测试服务器的事件
循环。
coroutine close()
停止和结束开启的测试服务器。
make_url(path)*
返回给定path的绝对URL。
class aiohttp.test_utils.RawTestServer(handler, \, scheme="http", host="127.0.0.1")
低级测试服务器(派生于BaseTestServer)
*参数:
handler - 用于处理web请求的协同程序。处理器需要接受 aiohttp.web.BaseRequest实例并且返回响应实例(StreamResponse或 Response之类的)。对于非200的HTTP响应,处理器可以抛出 HTTPException异常。
scheme(str) - HTTP协议,默认是无保护的“http”。
host(str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。
class aiohttp.test_utils.TestServer(app, \, scheme="http", host="127.0.0.1")
用于启动应用程序的测试服务器(派生于BaseTestServer)。
*参数:
app - 要启动的aiohttp.web.Application实例对象。 scheme(str) - HTTP协议,默认是无保护的“http”。
host(str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。 app
要启动的aiohttp.web.Application实例对象。
178
测试
测试客户端。
class aiohttp.test_utils.TestClient(app_or_server, \, loop=None, scheme='http',
host='127.0.0.1')
一个用于制造请求来测试服务器的测试客户端。
*参数:
app_or_server - BaseTestServer实例对象,用于向其发起请求。如果是 aiohttp.web.Application对象,会为应用程序自动创建一个TestServer。 cookie_jar - 可选的aiohttp.CookieJar实例对象,搭配CookieJar(unsafe=True)
更佳。
scheme (str) - HTTP协议,默认是无保护的“http”。
loop (asyncio.AbstractEventLoop) - 需要使用的事件循环。
host (str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。
scheme
被测试应用的使用的协议,'http'是无保护的,'https'是有TLS加密的。
host
用于启动测试服务器的主机名。
port
用于启动测试服务器的端口(随机的)。
server
BaseTestServer测试服务器实例,一般与客户端连用。
session
内部aiohttp.ClientSession对象.
不同于TestClient中的那样,客户端会话的请求不会自动将url查询放入 主机名中,需要传入一个绝对路径。
coroutine start_server(**kwargs)
开启测试服务器。
coroutine close()
关闭在运行的服务器。
make_url(path)
返回给定path的绝对URL。
coroutine request(method, path, *args, **kwargs)
将请求发送给测试服务器。
除了loop参数被测试服务器所使用的循环覆盖外,该接口与 asyncio.ClientSession.request()相同。
179
测试
coroutine get(path, *args, **kwargs)
执行HTTP GET请求。
coroutine post(path, *args, **kwargs)
执行HTTP POST请求。
coroutine options(path, *args, **kwargs)
执行HTTP OPTIONS请求。
coroutine head(path, *args, **kwargs)
执行HTTP HEAD请求。
coroutine put(path, *args, **kwargs)
执行HTTP PUT请求。
coroutine patch(path, *args, **kwargs)
执行HTTP PATCH请求。
coroutine delete(path, *args, **kwargs)
执行HTTP DELETE请求。
coroutine ws_connect(path, *args, **kwargs)
初始化websocket连接。
该api与aiohttp.ClientSession.ws_connect()相同。
其他工具包
aiohttp.test_utils.make_mocked_coro(return_value)
创建一个协程mock。
其表现形式像一个协程一般,作用是返回要返回的值(return_value)。而同时 又是一个mock对象,你可以使用一般的Mock来测试它:
mocked = make_mocked_coro(1)
assert 1 == await mocked(1, 2)
mocked.assert_called_with(1, 2)
参数: return_value - 当mock对象被调用时返回的值。
像协程一样返回return_value的值。
aiohttp.test_utils.unused_port()
返回一个可以用在IPv4 TCP协议上的还没有被使用的端口。 返回一个可以 使用端口值(类型为整数int)。
180
测试
aiohttp.test_utils.loop_context(loop_factory=)
一个上下文管理器,可以创建一个用于测试目的事件循环。
用于进行测试循环的创建和清理工作。
aiohttp.test_utils.setup_test_loop(loop_factory=)
创建并返回asyncio.AbstractEventLoop实例对象。
如果要调用它,需要在结束循环时调用下teardown_test_loop.
aiohttp.test_utils.teardown_test_loop(loop)
销毁并清除setup_test_loop所创建的event_loop。
参数: loop(asyncio.AbstractEventLoop) - 需要拆除的循环。
181
服务器部署
服务器部署
关于aiohttp服务器部署,这里有以下几种选择:
1.独立的服务器。
2.使用nginx, HAProxy等反向代理服务器,之后是后端服务器。
3.在反向代理之后在部署一层gunicorn,然后才是后端服务器。
独立服务器
只需要调用 aiohttp.web.run_app() ,并传递 aiohttp.web.Application 实例 即可。
该方法最简单,也是在比较小的程序中最好的解决方法。但该方法并不能完全利用 CPU。
如果要运行多个aiohttp服务器实例请用反向代理。
Nginx + supervisord
将aiohttp服务器组运行在nginx之后有好多好处。
首先,nginx是个很好的前端服务器。它可以预防很多攻击如格式错误的http协议的 攻击。
第二,部署nginx后可以同时运行多个aiohttp实例,这样可以有效利用CPU。 最后,nginx提供的静态文件服务器要比aiohttp内置的静态文件支持快很多。
但部署它也意味着更复杂的配置。
配置Nginx
下面是一份简短的配置Nginx参考,并没涉及到所有的Nginx选项。
好啦,首先我们要配置HTTP服务器本身:
182
服务器部署
http { server {
listen 80;
client_max_body_size 4G;
server_name example.com;
location / {
proxy_set_header Host $http_host;
proxy_set_header
r;
proxy_redirect off; proxy_buffering off; proxy_pass http://aiohttp;
}
location /static {
#path for static files
root /path/to/app/static;
}
}
}
这样配置之后会监听80端口,服务器名为 example.com ,所有的请求都会被重定 向到aiohttp后端处理组。
静态文件也同样适用,/path/to/app/static路径的静态文件访问时就变成了 example.com/static。
接下来我们配置aiohttp上游组件:
183
服务器部署
http {
upstream aiohttp {
#fail_timeout=0 means we always retry an upstream even if i t failed
#to return a good HTTP response
#Unix domain servers
server unix:/tmp/example_1.sock fail_timeout=0; server unix:/tmp/example_2.sock fail_timeout=0; server unix:/tmp/example_3.sock fail_timeout=0; server unix:/tmp/example_4.sock fail_timeout=0;
#Unix domain sockets are used in this example due to their high performance,
#but TCP/IP sockets could be used instead:
#server 127.0.0.1:8081 fail_timeout=0;
#server 127.0.0.1:8082 fail_timeout=0;
#server 127.0.0.1:8083 fail_timeout=0;
#server 127.0.0.1:8084 fail_timeout=0;
}
}
所有来自 http://example.com 的HTTP请求(除
了http://example.com/static )都将重定向到example1.sock,
example2.sock, example3.sock 或 example4.sock后端服务器。默认情况下,
注意
Nginx 并不是反向代理的唯一选择,不过却是最流行的。你也可以选择HAProxy。
Supervisord
配置完Nginx,我们要开始配置aiohttp后端服务器了。使用些工具可以在系统重启 或后端出现错误时更快地自动启动。
我们有很多工具可以选择: Supervisord, Upstart, Systemd, Gaffer, Runit等等。
184
服务器部署
我们用Supervisord当做例子:
[program:aiohttp] numprocs = 4 numprocs_start = 1
process_name = example_%(process_num)s
;Unix socket paths are specified by command line. command=/path/to/aiohttp_example.py
;We can just as easily pass TCP port numbers:
;command=/path/to/aiohttp_example.py
user=nobody
autostart=true
autorestart=true
aiohtto服务器
最后我们要让aiohttp服务器在supervisord上工作。
假设我们已经正确配置 aiohttp.web.Application ,端口也被正确指定,这些工 作挺烦的:
185
服务器部署
#aiohttp_example.py import argparse
from aiohttp import web
parser = argparse.ArgumentParser(description="aiohttp server exa mple")
if __name__ == '__main__': app = web.Application()
# configure app
args = parser.parse_args()
web.run_app(app, path=args.path, port=args.port)
当然在真实环境中我们还要做些其他事情,比如配置日志等等,但这些事情不在本 章讨论范围内。
Nginx + Gunicorn
与部署Ngnix相反,使用Gunicorn不需要我们手动启动aiohttp进程,也不需要使用 如supervisord之类的工具进行监控。但这并不是没有代价:在Gunicorn下运行 aiohttp应用会有些许缓慢。
准备环境
在做以上操作之前,我们首先要做的就是配置我们的部署环境。本章例子基于 Ubuntu 14.04。
首先为应用程序创建个目录:
186
服务器部署
>>mkdir myapp
>>cd myapp
在Ubuntu中使用pyenv有一个bug,所以我们需要做些额外的操作才能配置虚拟环 境:
>>
>>source venv/bin/activate
>>curl
>>deactivate
>>source venv/bin/activate
好啦,虚拟环境我们已经配置完了,之后我们要安装aiohttp和gunicorn:
>>pip install gunicorn
>>pip install
应用程序
我们写一个简单的应用程序,将其命名为 my_app_module.py:
from aiohttp import web
def index(request):
return web.Response(text="Welcome home!")
my_web_app = web.Application()
my_web_app.router.add_get('/', index)
启动Gunicorn
187
服务器部署
启动Gunicorn时我们要将模块名字(如my_app_module)和应用程序的名字(如 my_web_app)传入,可以一起在配置Gunicorn其他选项时写入也可以写在配置文 件中。
本章例子所使用到的选项:
>>gunicorn my_app_module:my_web_app
.0
现在,Gunicorn已成功运行,随时可以将请求交由应用程序的worker处理。
注意
如果你使用的是另一个asyncio事件循环uvloop, 你需要
用aiohttp.GunicornUVLoopWebWorker worker类。
其他内容
Gunicorn 文档建议将Gunicorn部署在Nginx代理服务器之后。可看下官方文档的建 议。
配置日志
188
服务器部署
aiohttp 和 Gunicorn 使用不同的日志格式。 默认aiohttp使用自己的日志格 式:
'%a %l %u %t "%r" %s %b "%{Referrer}i"
查看访问日志的格式来获取更多信息。
189
抽象基类参考
抽象基类
抽象路由类
aiohttp 使用抽象类来管理web接口。
aiohttp.web 中大部分类都不打算是可以继承的,只有几个是可以继承的。 aiohttp.web 建立在这几个概念之上: 应用(application),路由(router),请
求(request)和响应(response)。
路由(router)是一个可插拔的部分: 用户可以从头开始创建一个新的路由库,不过 其他的部分必须与这个新路由无缝契合才行。 AbstractRouter只有一个必须(覆盖)的方法: AbstractRouter.resolve() 协 程方法。同时必须返回 AbstractMatchInfo 实例对象。
如果所请求的URL的处理器发现 AbstractMatchInfo.handler() 所返回的是web 处理器,那 AbstractMatchInfo.http_exception 则为None。
否则的话 AbstractMatchInfo.http_exceptio n会是一个 HTTPException 实 例,如 404: NotFound 或 405: Method Not Allowed 。如果调
用AbstractMatchInfo.handler() 则会抛出 http_exception 。
class aiohttp.abc.AbstractRouter
此类是一个抽象路由类,可以指定 aiohttp.web.Application 中的router参
数来传入此类的实例,使用aiohttp.web.Application.router来查看返回.
coroutine resolve(request)
URL处理方法。该方法是一个抽象方法,需要被继承的类所覆盖。
参数:
request - 用于处理请求的 aiohttp.web.Request 实例。在处理
时aiohttp.web.Request.match_info 会被设置为None。
返回:
返回 AbstractMathcInfo 实例对象。
class aiohttp.abc.AbstractMatchInfo
抽象的匹配信息,由 AbstractRouter.resolve() 返回。
http_exception
如果没有匹配到信息则是 aiohttp.web.HTTPException 否则是None。
coroutine handler(request)
执行web处理器程序的抽象方法。
190
抽象基类参考
参数:
request - 用于处理请求的 aiohttp.web.Request 实例。在处理
时aiohttp.web.Request.match_info 会被设置为None。
返回:
返回 aiohttp.web.StreamResponse 或其派生类。
可能会抛出的异常:
所抛出的异常类型是 aiohttp.web.HTTPException 。
coroutine expect_handler(request)
抽象类基础视图
aiohttp 提供抽象类 AbstractView 来对基于类的视图进行支持,而且还是一
个awaitable 对象(可以应用在 await Cls() 或 yield from Cls() 中,同时
还有一个 request 属性。)
class aiohttp.AbstarctView
一个用于部署所有基于类的视图的基础类。
__iter__和__await__方法需要被覆盖。
request
用于处理请求的 aiohttp.web.Request 实例。
抽象Cookies Jar类
class aiohttp.abc.AbstractCookieJar
此类所生成的cookie jar实例对象可以作为 ClientSession.cookie_jar 所需
要的对象。
jar中包含用于存储内部cookie数据的Morsel组件。
提供一个统计所存储的cookies的API:
len(session.cookie_jar)
jar也可以被迭代:
191
抽象基类参考
for cookie in session.cookie_jar:
print(cookie.key)
print(cookie["domain"])
此类用于存储cookies,同时提供collection.abc.Iterable和 collections.abc.Sized的功能。
update_cookies(cookies, response_url=None)
参数:
cookies - 接受 collection.abc.Mapping 对象(如dict, SimpleCookie
等)或由服务器响应返回的可迭代cookies键值对。
response_url(str) - 响应该cookies的URL,如果设置为None则该cookies 为共享cookies。标准的cookies应该带有服务器URL,只在请求此服务器时发送, 共享的话则会向全部的客户端请求都发送。
filter_cookies(request_url)
返回jar中可以被此URL接受的cookies和可发送给给定URL的客户端请求的 Cookies 头。
参数:
request_url (str) - 想要查询cookies的请求URL。
返回:
返回包含过滤后的cookies的 http.cookies.SimpleCookie 对象。
抽象访问日志
class aiohttp.abc.AbstractAccessLogger
所有 RequestHandler 中实现 access_logger 的基础类。
log方法需要被覆盖。
log(request, response, time)
参数:
request - aiohttp.web.Request 对象。
reponse - aiohttp.web.Response 对象。
time (float) - 响应该请求的时间戳。
192
使用Mulitipart
使用Multipart
aiohttp 支持功能完备的Multipart读取器和写入器。这俩都使用流式设计,以避 免不必要的占用,尤其是处理的载体较大时,但这也意味着大多数I/O操作只能被执 行一次。
读取Multipart响应
假设你发起了一次请求,然后想读取Multipart响应数据:
async with aiohttp.request(...) as resp:
pass
首先,你需要使用来处理下响应内容。这 样可以让数据从响应和连接中分离出来并保持 MultipartReader 状态,使其更便 捷的使用:
reader = aiohttp.MultipartReader.from_response(resp)
假设我们需要接受JSON数据和Multipart文件,但并不需要所有数据,只是其中的 一个。
那我们首先需要进入一段循环中,在里面处理Multipart:
metadata = None
filedata = None
while True:
part = await reader.next()
所返回的类型取决于下一次循环时的值: 如果是一个正常响应内容那会得
到BodyPartReader 实例对象,否则将会是一个嵌套Multipart的MultipartReader
实例对象。记住,Multipart的格式就是递归并且支持嵌套多层。如果接下来没有内 容可以获取了,则返回 None - 然后就可以跳出这个循环了:
193
使用Mulitipart
if part is None: break
BodyPartReader 和 MultipartReader 都可访问内容的 headers : 这样就可以
使用他们的属性来进行过滤:
if part.headers[aiohttp.hdrs.CONTENT_TYPE] == 'application/json'
:
metadata = await part.json()
continue
不明确说明的话,不管是 BodyPartReader 还是 MultipartReader 都不会读取 出全部的内容。 BodyPartReader 提供一些易用的方法来帮助获取比较常见的内 容类型:
BodyPartReader.text() 普通文本内容。
BodyPartReader.json() JSON内容。
BodyPartReader.form()
果传输内容使用了gzip和deflate进行过编码则会自动识别,或者如果是base64
用BodyPartReader.read() 和 BodyPartReader.read_chunk() 协程方法
都可以读取原始数据,只不过一个是一次性读取全部一个是分块读取。 BodyPartReader.filename 属性对于处理Multipart文件时可能会有些用处:
if part.filename != 'secret.txt':
continue
当前的内容不符合你的期待然后要跳过的话只需要使用 continue 来继续这个 循环。在获取下一个内容之前使用 await reader.next() 确保之前那个已经 完全被读取出来了。如果没有的话,所有的内容将会被抛弃然后来获取下一个 内容。所以你不用关心如何清理这些无用的数据。 一旦发现你搜寻的那个文 件,直接读就行。我们可以先不使用解码读取:
filedata = await part.read(decode=False)
之后如果要解码的话也很简单:
194
使用Mulitipart
filedata = part.decode(filedata)
一旦完成了关于Multipart的处理,只需要跳出循环就好了:
break
发送Multipart请求
MultipartWriter 提供将Python数据转换到Multipart载体(以二进制流的形式) 的接口。因为Multipart格式是递归的而且支持深层嵌套,所以你可以使用 with 语 句设计Multipart数据的关闭流程:
with aiohttp.MultipartWriter('mixed') as mpwriter:
...
with aiohttp.MultipartWriter('related') as subwriter:
...
mpwriter.append(subwriter)
with aiohttp.MultipartWriter('related') as subwriter:
...
with aiohttp.MultipartWriter('related') as subsubwriter:
...
subwriter.append(subsubwriter)
mpwriter.append(subwriter)
with aiohttp.MultipartWriter('related') as subwriter:
...
mpwriter.append(subwriter)
MultipartWriter.append() 用于将新的内容压入同一个流中。它可以接受各种 输入,并且决定给这些输入用什么 headers 。 对于文本数据默认的 Content- Type 是 text/plain;
mpwriter.append('hello')
195
使用Mulitipart
二进制则是
mpwriter.append(b'aiohttp')
你也可以使用第二参数来覆盖默认值:
mpwriter.append(io.BytesIO(b'GIF89a...'),
对于文件对象
part = root.append(open(__file__, 'rb'))
如果你想给文件设置个其他的名字,只需要操作 BodyPartWriter 实例即可,使
用BodyPartWriter.set_content_disposiition() 后 MultipartWriter.appe nd() 方法总会显式的返回和设置
part.set_content_disposition('attachment', filename='secret.txt'
)
此外,你还可以设置些其他的头信息:
part.headers[aiohttp.hdrs.CONTENT_ID] =
如果你设置了
part.headers[aiohttp.hdrs.CONTENT_ENCODING] = 'gzip'
常用的方法还
有MultipartWriter.append_json() 和 MultipartWriter.append_form() 对
JSON和表单数据非常好用,这样你就不需要每次都手动编码成需要的格式:
196
使用Mulitipart
mpwriter.append_json({'test': 'passed'})
mpwriter.append_form([('key', 'value')])
最后,只需要将根 MultipartWriter 实例通
过aiohttp.client.request() 的 data 参数传递出去即可:
await aiohttp.post('http://example.com', data=mpwriter)
后台的 MultipartWriter.serialize() 对每个部分都生成一个块,如果拥
有
到流数据上。
注意,在被MultipartWriter.serialize()处理时,所有的文件对象都会被读至末尾,不 将文件指针重置到开始时是不能重复读取的。
Multipart使用技巧
互联网上充满陷阱,有时你可能会发现一个支持Multipart的服务器出现些奇怪的情 况。 比如,如果服务器使用了 cgi.FieldStorage ,你就必须确认是否包
含
for part in mpwriter: part.headers.pop(aiohttp.hdrs.CONTENT_LENGTH, None)
另一方面,有些服务器可能需要你为所有的 Multipart 请求指定 Content- Length 头信息。但 aiohttp 并不会指定因为默认是用块传输来发送Multipart的。 要实现的话你必须连接 MultipartWriter 来计算大小:
body = b''.join(mpwriter.serialize()) await aiohttp.post('http://example.com',
data=body, headers=mpwriter.headers)
197
使用Mulitipart
有时服务器的响应并没有一个很好的格式: 可能不包含嵌套部分。比如,我们请求 的资源返回JSON和文件的混合体。如果响应中有任何附加信息,他们应该使用嵌 套Multipart的形式。如果没有则是普通形式:
198
使用Mulitipart
{"_id": "foo"}
{"_id": "bar"}
bar! bar! bar!
{"_id": "boo"}
{"_id": "baz"}
baz! baz! baz!
在单个流内读取这样的数据是可以的,不过并不清晰:
199
使用Mulitipart
result = [] while True:
part = await reader.next()
if part is None: break
if isinstance(part, aiohttp.MultipartReader):
#Fetching files while True:
filepart = await part.next() if filepart is None:
break
else:
#Fetching document result.append([(await part.json())])
我们换一种方式来处理,让普通文档和与文件相关的读取器成对附到每个迭代器上:
200
使用Mulitipart
class PairsMultipartReader(aiohttp.MultipartReader):
#keep reference on the original reader multipart_reader_cls = aiohttp.MultipartReader
async def next(self):
"""Emits a tuple of document object (:class:`dict`) and multipart
reader of the followed attachments (if any).
:rtype: tuple
"""
reader = await super().next()
if self._at_eof: return None, None
if isinstance(reader, self.multipart_reader_cls): part = await reader.next()
doc = await part.json() else:
doc = await reader.json()
return doc, reader
这样我们就可以更轻快的解决:
201
使用Mulitipart
reader = PairsMultipartReader.from_response(resp) result = []
while True:
doc, files_reader = await reader.next()
if doc is None: break
files = [] while True:
filepart = await files_reader.next() if file.part is None:
break
files.append((await filepart.read()))
result.append((doc, files))
扩展
Multipart API in Helpers API section.
202
流式API参考
流API
aiohttp.web.Request.content 和 aiohttp.ClientResponse.content 都使
用流来接受数据,同时也是流形式的API属性。
class aiohttp.StreamReader
流内容读取器
除了已经存在
的aiohttp.web.Request.content 和 aiohttp.ClientResponse.content 用 于读取原始内容的 StreamReader 实例,用户不要手动创建 StreamReader 实例
对象。
读取方法
coroutine
如果接受的是EOF并且内部缓存器没有内容,则返回空字节对象。
参数:
n(int) -
返回所读字节。
coroutine StreamReader.readany()
用于读取下一部分流内容。
如果内部缓存器有数据则立即返回。
返回所读字节。
coroutine StreamReader.readexactly(n)
返回n字节数据。
如果超过了读取上限则会抛出 asyncio.IncompleteReadError 异
常, asyncio.IncompleteReadError.partial 属性包含了已经读取到的那部分 字节。
参数: n (int) - 要读的字节数。
返回所读字节。
coroutine StreamReader.readline()
读取其中的一行数据,行是以"\n"所区分开的数据。
203
流式API参考
如果发现EOF而不是\n则会返回所读到的字节数据。
如果发现EOF但内部缓存器是空的,则返回空字节对象。
返回所读行。
coroutine StreamReader.readchunk()
读取从服务器接受到的块数据。
返回包含信息的元组((data, end_of_HTTP_chunk))。
如果使用了分块传输编码, end_of_HTTP_chunk 是一个指示末尾数据是否对 应HTTP块末尾的布尔值,其他情况下都是 False 。
返回元组[bytes, bool]:
bytes指代数据块,bool指代是否对应了HTTP块的最后一部分。
异步迭代
流读取器支持异步迭代。 默认是以行读取的:
async for line in response.content:
print(line)
当然我们还提供几个如可以指定最大限度的数据块迭代和任何可读内容的迭代器 等。
指定了最大可读字节的数据块迭代器:
async for data in response.content.iter_chunked(1024):
print(data)
按顺序读取在流中的数据块:
async for data in response.content.iter_any():
print(data)
204
流式API参考
读取从服务器接收到的数据块:
async for data, _ in response.content.iter_chunks():
print(data)
如果使用了分块传输编码,使用返回的元组中的第二个元素即可检索原始http块的 格式信息。
buffer = b""
async for data, end_of_http_chunk in response.content.iter_chunk s():
buffer += data
if end_of_http_chunk:
print(buffer) buffer = b""
其他帮助信息
StreamReader.exception()
返回数据读取时发生的异常。
aiohttp.is_eof()
如果检索到EOF则返回 Ture 。
这个函数检索到EOF的场合内部缓存器可能不是空的。
aiohttp.at_eof()
如果检索到EOF同时缓存器是空的则返回 True 。
StreamReader.read_nowait(n=None)
返回内部缓存器的任何数据,没有则返回空对象。
如果其他协同程序正在等待这个流则抛出 RuntimeError 异常。
参数: n (int) -
返回所读字节。
205
流式API参考
StreamReader.unread_data(data)
将所读的一些内容回滚到数据流中,数据会插入到缓存器的头部。
参数: data (bytes) - 需要压入流中的数据。
警告
该方法不会唤醒正在等待的协同程序。 如对read()方法就不会奏效。
coroutine aiohttp.wait_eof()
等待直到发现EOF。所给出的数据可以被接下来的读方法获取到。
206
其他帮助类
WebSocket 工具类
class aiohttp.WSCloseCode
一个保留关闭消息码的整数枚举类。
OK
正常结束,表示目标连接已经成功建立。
GOING_AWAY
表示服务器正在关闭或浏览器已离开页面。
PROTOCOL_ERROR
表示由于协议错误引起的终止连接。
UNSUPPORTED_DATA
表示因接收到不能接受到的数据类型引起的终止连接。(比如只能接受文本 的端口却接受了二进制消息)
INVALIED_TEXT
表示因接受到的数据中含有不能理解的消息类型引起的终止连接。(如在文
POLICY_VIOLATION
表示因接收到的信息违反规定引起的终止连接。如果没有合适的状态码会返 回通用状态码(比如 unsupported_data 或 message_too_big )。
MESSAGE_TOO_BIG
表示因接受的消息(数量)太大引起的终止连接。
MANDATORY_EXTENSION
表示因客户端期望与服务器协商更多的扩展类型但服务器没用在响应消息中 回复此类内容引起的终止连接。扩展列表需要在Close帧中的/reason/部分显示。注 意该状态码不会被服务器端使用,因为此状态码已代表WebSocket握手失败。
INTERNAL_ERROR
表示服务器端因遇到一个期望之外的错误无法完成请求而引起的终止连接。
207
其他帮助类
SERVICE_RESTART
TRY_AGAIN_LATER
服务过载。客户端需要连接到不同的IP地址(如果有的话)或尝试重新连 接。
class aiohttp.WSMsgType
描述 WSMessage 类型的 整数枚举(IntEnum) 类。
CONTINUATION
用于连接帧的标记,用户不会收到此消息类型。
TEXT
文本消息,值为字符串。
BINARY
二进制类型,值为字节码。
PING
代表Ping帧(由客户端发送)。
PONG
代表Pong帧,用于回复ping。由服务器发送。
CLOSE
代表Close帧。
CLOSED FRAME
不是一个帧,只是一个代表websocket已被关闭的标志。
ERROR
不是一个帧,只是一个代表websocket接受到一个错误的标志。
class aiohttp.WSMessage
WebSocket信息,由 .receive() 调用得到。
type(类型)
消息类型,是一个 WSMsgType 实例对象。
data(数据)
消息载体。
1. WSMsgType.TEXT 消息的类型为 str 。
208
其他帮助类
2. |
WSMsgType.BINARY |
消息的类型为 |
bytes |
。 |
||||||||||
3. |
|
|
|
消息的类型为 |
|
|
|
。 |
||||||
WSMsgType.CLOSE |
WSCloseCode |
|||||||||||||
4. |
|
|
消息的类型为 |
|
|
。 |
||||||||
|
WSMsgType.PING |
|
bytes |
|||||||||||
5. |
|
|
消息的类型为 |
|
。 |
|||||||||
|
WSMsgType.PONG |
bytes |
extra
额外信息,类型为字符串。
只对 WSMsgType.CLOSE 消息有效,内容包含可选的消息描述。
json(*, loads=json.loads)
返回已解析的JSON数据。
新增于0.22版本。
参数: loads - 自定义JSON解码函数。
tp
不赞成使用的type别名函数。
1.0版本后不再建议使用。
信号
信号是一个包含注册过的异步回调函数的列表。
信号的生命周期有两个阶段: 在信号的内容被标准列表操作所填充之后: sig.append() 之类的。
第二个是 sig.freeze() 调用之后,在这之后信号会被冻结: 添加,删除和丢弃回 调函数都是被禁止的。
唯一可做的就是调用之前已经注册过的回调函数: await sig.send(data) 。
更多实用例子请看 aiohttp.web 中的信号章节。
class aiohttp.Signal
信号组件,具有 collections.abc.MutableSequence 接口。
coroutine send(\args, **kwargs)*
从列表头部开始逐个调用已注册的回调函数。
frozen
如果 freeze() 被调用过则为 True 。该属性只读。
209
其他帮助类
freeze()
冻结列表。在这之后所有内容均不允许改动。
210
常见问题汇总
常见问题汇总
有没有提供像Flask一样的 @app.route 装饰器的计划? aiohttp有没有Flask中的蓝图或Django中的App的概念呢? 如何创建一个可以缓存url的给定前缀的路由?
我要把数据库连接放在哪才可以让处理器访问到它? 为什么最低版本只支持到Python 3.4.2?
如何以编程的方式在服务器端关闭websocket? 如何从特定IP地址上发起请求?
如果是隐式循环要怎么用aiohttp的测试功能呢? API创建和废弃政策是什么?
有没有提供像Flask一样的 @app.route 装饰 器的计划?
这种方法有几项问题:
最大的问题是“导入时会有副作用”。
路由匹配是有序的,这样做的话在导入时很难保证顺序。
在大部分大型应用中,都表示在某文件中写路由表比这样好很多。 所以,基于 以上原因,我们就没有提供这个功能。不过如果你真的很想用这个功能,继承
下web.Application 然后自己写一个吧~。
aiohttp有没有Flask中的蓝图或Django中的
App的概念呢?
如果你计划写一个大型应用程序,你可以考虑使用嵌套应用。它的功能就像Flask的 蓝图和Django的应用一样。 使用嵌套应用你可以为主应用程序添加子应用。
211
常见问题汇总
请看: 嵌套应用
如何创建一个可以缓存url的给定前缀的路由?
尝试下这样做:
app.router.add_route('*', '/path/to/{tail:.+}', sink_handler)
第一个参数星号(*)表示可以是任意方法(GET, POST, OPTIONS等等),第二 个参数则是指定的前缀,第三个就是处理器了。
我要把数据库连接放在哪才可以让处理器访问 到它?
aiohttp.web.Application对象支持字典接口(dict),在这里面你可以存储数据库连
接或其他任何你想在不同处理器间共享的资源。请看例子:
async def go(request):
db = request.app['db'] cursor = await db.cursor() await cursor.execute('SELECT 42')
# ...
return web.Response(status=200, text='ok')
async def init_app(loop):
app = Application(loop=loop)
db = await create_connection(user='user', password='123') app['db'] = db
app.router.add_get('/', go) return app
为什么最低版本只支持到Python 3.4.2?
212
常见问题汇总
在aiohttp 还是v0.18.0时,我们是支持Python 3.3 - 3.4.1的。
最主要的原因是 object.__del__() 方法了,自Python3.4.1时它才可以完整地工
当前适用于Python3.3, 3.4.0版本的是v0.17.4。
当然,这应该对于大多数适用aiohttp的用户来说不是个问题(例子中的Ubuntu
14.04.3LTS的Python版本是3.4.3呢!),不过依赖于aiohttp的包应该考虑下这个 问题了,是只使用低版本aiohttp呢还是一起抛弃Python3.3呢。 在aiohttp v1.0.0时 我们就抛弃了Python 3.4.1转而要求3.4.2 +。原因是: loop.is_closed自3.4.2才出 现。 最后,在如今的2016年夏这更不应该是个问题了,主流都已经是Python 3.5 啦。
如何让中间件可以存储数据以便让web- handler使用?
aiohttp.web.Request 与 aiohttp.web.Application 一样都支持字典接口 (dict)。 只需要将数据放到里面即可:
async def handler(request):
request['unique_key'] = data
请看
码, aiohttp_session.get_session(request) 方法使用 SESSION_KEY 来保存 请求的特定会话信息。
如何并行地接收来自不同源的事件?
比如我们现在有两个事件:
1.某一终端用户的WebSocket事件。
2.Redis PubSub从应用的其他地方接受信息并要通过websocket发送给其他用户 的事件。 并行地调用 aiohttp.web.WebSocketResponse.receive() 是不行 的,同一时间只有一个任务可以执行websocket读操作。 不过其他任务可以使 用相同的websocket对象发送数据:
213
常见问题汇总
async def handler(request):
ws = web.WebSocketResponse() await ws.prepare(request)
task = request.app.loop.create_task( read_subscription(ws,
request.app['redis']))
try:
async for msg in ws:
#handle incoming messages
#use ws.send_str() to send data back
...
finally:
task.cancel()
async def read_subscription(ws, redis): channel, = await redis.subscribe('channel:1')
try:
async for msg in channel.iter(): answer = process message(msg) ws.send_str(answer)
finally:
await redis.unsubscribe('channel:1')
如何以编程的方式在服务器端关闭 websocket?
比如我们现在有两个终端:
1./echo 一个用于以某种方式验证用户真实性的回显websocket。
2./logout 用于关闭某一用户的打开的所有websocket连接。 一种简单的解决 方法是在 aiohttp.web.Application 实例中为某一用户持续存储websocket 响应,并在 /logout_user 处理器中执
行 aiohttp.web.WebSocketResponse.close() 。
214
常见问题汇总
async def echo_handler(request):
ws = web.WebSocketResponse()
user_id = authenticate_user(request) await ws.prepare(request) request.app['websockets'][user_id].add(ws) try:
async for msg in ws: ws.send_str(msg.data)
finally: request.app['websockets'][user_id].remove(ws)
return ws
async def logout_handler(request):
user_id = authenticate_user(request)
ws_closers = [ws.close() for ws in request.app['websockets'] [user_id] if not ws.closed]
#Watch out, this will keep us from returing the response un til all are closed
ws_closers and await asyncio.gather(*ws_closers)
return web.Response(text='OK')
def main():
loop = asyncio.get_event_loop() app = web.Application(loop=loop)
app.router.add_route('GET', '/echo', echo_handler) app.router.add_route('POST', '/logout', logout_handler) app['websockets'] = defaultdict(set) web.run_app(app, host='localhost', port=8080)
如何从特定IP地址上发起请求?
215
常见问题汇总
如果你的系统上有多个IP接口,你可以选择其中一个来绑定到本地socket:
conn = aiohttp.TCPConnector(local_addr=('127.0.0.1', 0), loop=lo op)
async with aiohttp.ClientSession(connector=conn) as session:
...
扩展
请看 aiohttp.TCPConnector 及其 local_addr 参数。
如果是隐式循环要怎么用aiohttp的测试功能 呢?
传递显式的loop是推荐方式。但有时如果你有一个嵌套多层而且写的不好的服务 时,这几乎是不可能的任务。
这里推荐一种基于猴子补丁的技术方式,要依赖于 aioes ,具体方式是注射一个 loop进去。这样你只需要让 AioESService 在其自己的循环中就行(##非常不确 定此句的正确性##)。例子如下:
216
常见问题汇总
import pytest
from unittest.mock import patch, MagicMock
from main import AioESService, create_app
class TestAcceptance:
async def test_get(self, test_client, loop): with patch("main.AioESService", MagicMock(
side_effect=lambda *args, **kwargs: AioESService
(*args,
**kwargs,
loop=loop))):
client = await test_client(create_app) resp = await client.get("/")
assert resp.status == 200
注意我们为 AioESService 打了补丁,但是要额外传入一个显式 loop (你需要 自己加载loop fixture)。 最后需要测试的代码(你需要一个本地elasticsearch实例 来运行):
import asyncio
from aioes import Elasticsearch from aiohttp import web
class AioESService:
def __init__(self, loop=None):
self.es = Elasticsearch(["127.0.0.1:9200"], loop=loop)
async def get_info(self): return await self.es.info()
217
常见问题汇总
class MyService:
def __init__(self): self.aioes_service = AioESService()
async def get_es_info(self):
return await self.aioes_service.get_info()
async def hello_aioes(request): my_service = MyService()
cluster_info = await my_service.get_es_info() return web.Response(text="{}".format(cluster_info))
def create_app(loop=None):
app = web.Application(loop=loop) app.router.add_route('GET', '/', hello_aioes) return app
if __name__ == "__main__": web.run_app(create_app())
全部测试文件:
218
常见问题汇总
from unittest.mock import patch, MagicMock
from main import AioESService, create_app
class TestAioESService:
async def test_get_info(self, loop):
cluster_info = await AioESService("random_arg", loop=loo p).get_info()
assert isinstance(cluster_info, dict)
class TestAcceptance:
async def test_get(self, test_client, loop): with patch("main.AioESService", MagicMock(
side_effect=lambda *args, **kwargs: AioESService
(*args,
**kwargs,
loop=loop))):
client = await test_client(create_app) resp = await client.get("/")
assert resp.status == 200
注意我们要怎么用sideeffect功能来注射一个loop到`AioESService._init`中的。 *args和**kwargs是必须的,是为了将调用时传入的参数都传递出去。
API创建和废弃政策是什么?
aiohttp尽量不会破坏现存的用户代码。
过时的属性和方法会在文档中被标记为已废弃,并且在使用时也会抛
出DeprecationWarning 警告。
废弃周期通常为一年半。
过了废弃周期后废弃的代码会被删除。
219
常见问题汇总
如果有新功能或bug修复强制我们打破规则我们也会打破的(比如合适地客户端 cookies支持让我们打破了向后兼容政策两次!)。
所有向后不兼容的改变都会在CHANGES章节明确指出。
如何让整个应用程序都使用gzip压缩?
这是不可能的。选择在哪不用压缩和用哪种压缩方式是一个非常棘手的问题。
在web服务器中如何管理ClientSession?
aiohttp.ClientSession在服务器的生命周期中只应该被创建一次,因为可以更好地利 用连接池。
Session在内部保存cookies。如果你不需要cookies使用aiohttp.DummyCookieJar 就行。如果你需要在不同的http调用中使用不同的cookies,但在逻辑链的处理时使 用的是同一个aiohttp.TCPConnector,那把own_connector设置为False。
220
相关文献
221
相关文献
222
相关名词释义
相关名词释义
aiodns
异步DNS解决方案
https://pypi.python.org/pypi/aiodns
asyncio
写单线程多并发代码的协程库,通过套接字和其他资源进行多路复用的I/O访 问,运行网络客户端和服务器 ,还有其他相关内容。
相关参考信息请看PEP 3156
https://pypi.python.org/pypi/asyncio/
callable
任何可调用的对象。可以使用callable()进行检测是不是一个callable对象。
cchardet
cChardet 是一个高速的通用编码发现引擎。
https://pypi.python.org/pypi/cchardet/
chardet
通用编码发现引擎。
https://pypi.python.org/pypi/chardet/
gunicorn
Gunicorn “绿色独角兽”是一个UNIX下的Python WSGI HTTP 服务器。
223
相关名词释义
IDNA
应用程序中的国际化域名(IDNA)是为了编码互联网域名的一个行业标准,基 于特定的语言模板和字母,如阿拉伯语,汉语,西里尔文,泰米尔文,希伯来文或 以拉丁字母为基础的变音字符还有像法语一样的连字字符。这些写作系统经由电脑 编码成多字节Unicode字符。国际化的域名以ASCII的形式保存到域名系统中,使用
Punycode编码互转。
一个可以让HTTP客户端和服务器间保持通讯的技术。一旦进行连接并发送完一 次响应后并不关闭连接,而是保持打开状态以使用同一个套接字发送下一次请求。
此技术可以让通讯变得飞快,因为无需为每次请求都建立一遍连接。
nginx
Nginx [engine x] 是一个HTTP和反向代理服务器,邮件代理服务器和通用
TCP/UDP代理服务器。
是一个在URL中编码信息的机制,当URL中有不合适的字符时一般会进行编 码。
requoting
resource
224
相关名词释义
一个映射HTTP路径的概念,每个资源(resource)相当于一个URI。
可以有一个独立的名称。
基于不同的HTTP方法,会包含不同的路由。
route
资源(resource)的一部分,资源(resource)的路径和HTTP方法组成路由 (route)。
用于返回HTTP响应的终端。
websocket
是一个在同一个TCP连接上进行全双工通讯的协议。WebSocket协议由IETF标
准化写入RFC 6455。
yarl
一个操作URL对象的库。
225
外部资源包
有用的外部资源包汇集
226
贡献须知
贡献须知
贡献者说明
首先需要clone GitHub上的仓库: 打开链接,并位于右上角的点击“Fork”按钮。 :) 我 想应该所有人都会使用git和github吧~。
之后要做的步骤很清晰:
2.进行修改。
3.确保所有代码测试通过。
4.在CHANGES文件夹中添加一个说明文件(用于更新Changelog)。
5.提交到自己的aiohttp仓库。
6.发起一个关于你的修改的PR。
注意
如果你的PR有很长历史记录或者很多提交,请在创建PR之前重新从主仓库创建一次,确 保它没有这些记录。
运行aiohttp测试组件的准备
我们建议你使用pyhton虚拟环境来运行我们的测试。 关于创建虚拟环境有以下几种 方法:
如果你喜欢使用virtualenv,可以这样用:
$ cd aiohttp
$virtualenv
$. venv/bin/activate
或者使用python 标准库中的venv:
227
贡献须知
$ cd aiohttp
$python3
$. venv/bin/activate
还可以使用virtualenvwarapper:
$ cd aiohttp
$ mkvirtualenv
还有其他可用的工具比如 pyvenv ,不过我们只是要让你知道: 需要创建一个 Python3虚拟环境并使用它。
弄完之后我们要安装开发所需的包:
$ pip install
注意
如果你计划在测试组件中使用pdb或ipdb,执行:
```
$ py.test tests
```
使用命令禁止输出捕获。
恭喜,到这一步我们就准备好我们的测试组件了。
运行aiohttp测试组件
全部准备完之后,是时候运行了:
$ make test
228
贡献须知
第一次使用命令运行时会运行 flask8 工具(抱歉,我们不接受关于pep8和 pyflakes相关错误的PR)。 flake8 成功运行后,测试也会随之运行。 请稍微注 意下输出的语句。 任何额外的文本信息(打印的条款等)都会被删除。
测是覆盖
我们正尽可能地有一个好的测试覆盖率,请不要把它变得更糟。 运行下这个命令:
$ make cov
来加载测试组件并收集覆盖信息。一旦命令执行完成,会在最后一行显示如下输
出: open file:///.../aiohttp/coverage/index.html 请看一下这个链接并确
保你的修改已被覆盖到。 aiohttp使用 codecov.io 来存储覆盖结果。 你可以去这 个页面查看:
文档
我们鼓励完善文档。 发起一个关于修改文档的PR时请先运行下这个命令:
$ make doc
完成后会输出一个索引页面:
file:///.../aiohttp/docs/_build/html/index.html 请看一下,确保样式
没问题。
拼写检测
我们使用 pyenchant 和
$ make
不幸的是,在MacOS X上会有些问题。 在Linux上运行拼写检测前要先安装下:
229
贡献须知
$ sudo
$ pip install
更新 修改日志
CHANGES.rst文件使用towncrier工具进行管理,所有重要的修改都要有一个条目。 要为新文件增加一个条目,首要需要创建一个关于你想怎么做的issue。一个PR本 身的功能也和这个一样,但有一个正经关于此修改的issue更好(举个例子,万一因 为代码质量问题驳回了PR呢...)。
一旦你有了一个issue或PR,你会得到一个号码并在CHANGES/目录内以此issue的 号码和其扩展内容如 .removal, .feature, .bugfix, .doc 创建一个文件。比 如你的issue或PR号码是1234,是关于修复bug的,这样就会创建一
个CHANGES/1234.bugfixs 的文件。PR们可以创建多个类别的说明文件(比如, 你添加了一个新功能,并且要删除或不在赞成使用某个旧功能,那就创
建CHANGES/NNNN.feature 和 CHANGES/NNNN.removal )。同样地,如果一个
PR涉及到多个issues/PR你可以为它们每个都创建相同的内容,Towncrier会删除重 复的部分。
文件的内容使用 reStruredText 格式化内容,格式化后的内容会作为新文件条目 来使用。你不需要为issue或PR添加关联,towncrier会自动为所有涉及到的issues 添加关联。
最后
做完这些之后,请在GitHub上发起PR,谢谢~。
230