目錄

 

 

 

 

 

Introduction

1.1

 

 

 

 

 

 

 

 

前言

1.2

 

 

 

 

 

 

 

 

简介及快速开始

1.3

 

 

 

 

 

 

 

 

客户端部分

1.4

 

 

 

 

 

 

 

 

基本及进阶使用

1.4.1

 

 

 

 

 

 

 

 

 

发起请求

1.4.1.1

 

 

 

 

 

 

 

 

JSON请求

1.4.1.2

 

 

 

 

 

 

 

 

传递URL中的参数

1.4.1.3

 

 

 

 

 

 

 

 

获取响应内容

1.4.1.4

 

 

 

 

 

 

 

 

获取二进制响应内容

1.4.1.5

 

 

 

 

 

 

 

 

获取JSON响应内容

1.4.1.6

 

 

 

 

 

 

 

 

获取流式响应内容

1.4.1.7

 

 

 

 

 

 

 

 

获取请求信息

1.4.1.8

 

 

 

 

 

 

 

 

自定义Headers

1.4.1.9

 

 

 

 

 

 

 

 

自定义Cookies

1.4.1.10

 

 

 

 

 

 

 

发起更复杂的POST请求

1.4.1.11

 

 

 

 

 

 

 

发送多部分编码文件(Multipart-Encoded)

1.4.1.12

 

 

 

1.4.1.13

 

 

流式上传

 

 

 

1.4.1.14

 

 

上传预压缩过的数据

 

 

 

 

 

 

持久连接(keep-alive), 连接池和cookies共享

1.4.1.15

 

 

 

 

 

 

安全cookies

1.4.1.16

 

 

 

 

 

 

使用虚假Cookies Jar

1.4.1.17

 

 

 

1.4.1.18

 

 

使用连接器

 

 

 

1.4.1.19

 

 

限制连接池的容量

 

 

 

 

 

 

TCP-sockets添加SSL控制

1.4.1.20

 

 

 

 

 

 

Unix域套接字相关

1.4.1.21

 

 

 

1.4.1.22

 

 

代理支持

 

 

 

 

 

 

 

 

1

 

 

查看响应状态码

 

1.4.1.23

 

 

获取响应头信息

1.4.1.24

 

 

 

 

 

 

 

 

 

 

 

获取响应cookies

1.4.1.25

 

 

 

 

 

 

 

 

 

 

 

获取响应历史

1.4.1.26

 

 

 

 

 

 

 

 

 

 

 

使用WebSockets

1.4.1.27

 

 

 

 

 

 

 

 

 

 

 

设置超时

1.4.1.28

 

 

 

 

 

 

 

 

 

 

 

愉快地结束

1.4.1.29

 

 

 

 

 

 

 

 

 

 

客户端部分参考

1.4.2

 

 

 

 

 

 

 

 

服务端部分

1.5

 

 

 

 

 

 

 

 

服务端快速习得指南

1.5.1

 

 

 

 

 

 

 

 

 

aiohttp搭建一个投票系统

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

 

 

 

 

 

 

 

启动一个简单地Web服务器

1.5.2.1

 

 

 

 

 

 

 

使用命令行接口(CLI)

1.5.2.2

 

 

 

 

 

 

 

使用处理器

1.5.2.3

 

 

 

 

 

 

 

使用资源和路由

1.5.2.4

 

 

 

 

 

 

 

使用可变形(动态)资源

1.5.2.5

 

 

 

 

 

 

 

使用命名资源反向引用URL

1.5.2.6

 

 

 

 

 

 

 

将处理器放到类中使用

1.5.2.7

 

 

 

 

 

 

 

 

 

2

基础视图类

 

1.5.2.8

资源视图

1.5.2.9

 

 

 

 

其他注册路由的方法

1.5.2.10

 

 

 

Web处理器中的取消操作

1.5.2.11

 

 

 

自定义路由准则

1.5.2.12

 

1.5.2.13

静态文件的处理

 

1.5.2.14

模板

 

 

返回JSON响应

1.5.2.15

 

1.5.2.16

处理用户会话

 

 

处理HTTP表单

1.5.2.17

 

1.5.2.18

文件上传

 

 

使用WebSockets

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

流控制

 

 

使用Except

1.5.2.28

 

1.5.2.29

部署自定义资源

 

1.5.2.30

优雅地关闭

 

1.5.2.31

后台任务

 

1.5.2.32

处理错误页面

 

1.5.2.33

基于代理部署服务器

 

 

Swagger支持

1.5.2.34

 

 

CORS支持

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

 

 

抽象类基础视图

 

 

 

 

 

 

抽象Cookies Jar

1.6.1.3

 

 

 

1.6.1.4

 

 

抽象访问日志

 

 

 

 

 

使用Mulitipart

1.6.2

 

 

 

 

 

 

 

读取Multipart响应

1.6.2.1

 

 

 

 

 

 

发送Multipart请求

1.6.2.2

 

 

 

 

 

 

Multipart使用技巧

1.6.2.3

 

 

 

 

 

流式API参考

1.6.3

 

 

 

 

 

 

 

读取方法

1.6.3.1

 

 

 

1.6.3.2

 

 

异步迭代

 

 

 

1.6.3.3

 

 

其他帮助信息

 

 

 

1.6.4

 

其他帮助类

 

 

 

 

 

 

 

WebSocket 工具类

1.6.4.1

 

 

 

1.6.4.2

 

 

信号

 

 

1.7

常见问题汇总

 

 

 

 

 

有没有提供像flask一样的approute装饰器的计划

1.7.1

 

 

 

 

 

aiohttp有没有Flask中的蓝图或Django中的App的概念呢?

1.7.2

 

 

 

 

 

如何创建一个可以缓存url的给定前缀的路由?

1.7.3

 

 

 

 

 

我要把数据库连接放在哪才可以让处理器访问到它?

1.7.4

 

 

 

 

 

为什么最低版本只支持到Python 3.4.2

1.7.5

 

 

 

 

 

如何让中间件可以存储数据以便让web-handler使用?

1.7.6

 

 

 

 

 

如何并行地接收来自不同源的事件?

1.7.7

 

 

 

 

 

 

4

 

如何以编程的方式在服务器端关闭websocket

 

1.7.8

 

如何从特定IP地址上发起请求?

1.7.9

 

 

 

 

 

 

 

如果是隐式循环要怎么用aiohttp的测试功能呢?

1.7.10

 

 

 

 

 

 

API创建和废弃政策是什么?

1.7.11

 

 

 

 

 

 

如何让整个应用程序都使用gzip压缩?

1.7.12

 

 

 

 

 

web服务器汇总如何管理ClientSession

1.7.13

 

 

1.8

各种杂项

 

 

 

 

 

相关文献

1.8.1

 

 

 

 

 

相关名词释义

1.8.2

 

 

 

 

 

外部资源包

1.8.3

 

 

 

 

贡献须知

1.9

 

 

 

 

 

 

5

Introduction

aiohttp 中文文档

来源:HuberTRoy/aiohttp-chinese-documentation

自己试着翻译,如有不当的地方请随时反馈~:wink:

如果你是在GitHub上浏览,可以移步至GitBookLove2.io中获得更好的阅读体 验。

进度:

基本翻译完成~,可能有很多不合理的地方,如有看到方便的话请随时反馈交流~, 谢谢~

快速浏览目录:

简介及快速上手 客户端使用手册 客户端参考手册 服务端指南

服务端使用手册 服务端参考手册 底层服务器搭建 抽象基类参考 使用Multipart 流式API参考

其他帮助API参考 日志

测试(待完善...) 服务器部署 常见问题汇总 外部资源包

参考文献(待完善...) 贡献须知

更新日志(待更新...)

6

Introduction

相关名词释义

7

前言

前言

这是我在学习过程中翻译出的第一本"",带给和我一样英语方面有些欠缺又想要 学习查阅一些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()

服务端指南:

Polls tutorial

源码:

该项目托管在Github.

如果你发现了一个bug或有一些改善的建议请随时提交

该库使用Travis进行持续集成。

程序依赖:

Python 3.4.2+

chardet

multidict

async_timeout

yarl

可选更快的cchardet代替chardet

11

简介及快速开始

可通过下面命令的安装:

$ pip install cchardet

可选aiodns进行DNS快速解析。极力推荐。 $ pip install aiodns

交流渠道:

aio-libs 谷歌交流群: https://groups.google.com/forum/#!forum/aio-libs

随时在这里交流你的问题和想法。

gitter 聊天 https://gitter.im/aio-libs/Lobby

我们还支持Stack Overflow. 在你的问题上添加aiohttp标签即可。

贡献

请在写一个PR前阅读下贡献须知

作者和授权

aiohttp 大部分由 Nikolay Kim Andrew Svetlov编写.

使用 Apache 2 授权并可随意使用。

随时在GitHub上提交PR来改善此项目。

对后续不再兼容的更改所采用的策略

一般的更改aiohttp 保持向后兼容.

在废弃某些公开API(方法,类,函数参数等.)后仍保证可以使用这些被废弃的API至 少一年半的时间直到某新版本完全弃用。

所有废弃的东西都会反映在文档中并给出已废弃提示。

有时我们会因一些必须要做的理由而打破某些我们定的规则。大多数原因是有只能 通过修改主要API解决的BUG出现,但我们会尽可能不让这种事情发生。

12

简介及快速开始

目录:

打开此链接看完整的目录。

13

基本及进阶使用

客户端使用

发起请求

让我们从导入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

基本及进阶使用

注意:

不要为每个请求都创建一个会话。大多数情况下每个应用程序只需要一个会话 就可以执行所有的请求。 每个会话对象都包含一个连接池,可复用的连接和持 久连接状态(keep-alives,这两个是默认的)可提升总体的执行效率。

发起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=value1key2=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())

这样会打印出类似于下面的信息:

'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{..

.

aiohttp 将会自动解码内容。你可以为text()方法指定编码(使用encoding参数):

await resp.text(encoding='windows-1251')

获取二进制响应内容

你也可以以字节形式获取响应,这样得到的就不是文本了:

print(await resp.read())

b'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{.

..

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(请求信息),主要是urlheaders信息。 raise_for_status结构体上的信息会被复制给ClientResponseError

例。

自定义Headers

18

基本及进阶使用

如果你需要给某个请求添加HTTP,可以使用headers参数,传递一个dict对象即 可。 比如,如果你想给之前的例子指定 content-type可以这样:

import json

url = 'https://api.github.com/some/endpoint' payload = {'some': 'data'}

headers = {'content-type': 'application/json'}

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:

...

发送多部分编码文件(Multipart-Encoded)

上传多部分编码文件:

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', content_type='application/vnd.ms-excel')

await session.post(url, data=data)

如果你把一个文件对象传递给data参数,aiohttp会自动将其以流的形式上传。查 看StreamReader以获取支持的格式信息。

参见:

使用Multipart.

流式上传

aiohttp 支持多种形式的流式上传,允许你直接发送大文件而不必读到内存。

下面是个简单的例子,提供类文件对象即可:

with open('massive-body', 'rb') as f:

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 实例,所以你可以将getpost 请求连在一起用:

r = await session.get('http://python.org')

await session.post('http://httpbin.org/post',

data=r.content)

上传预压缩过的数据

上传一个已经压缩过的数据,需要为Headers中的 Content-Encoding 指定算法名 (通常是deflate或者是zlib).

async def my_coroutine(session, headers, my_data):

data = zlib.compress(my_data)

headers = {'Content-Encoding': 'deflate'}

async with session.post('http://httpbin.org/post',

data=data,

headers=headers)

pass

持久连接(keep-alive), 连接池和cookies共享

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(

cafile='/path/to/ca-bundle.crt')

r = await session.get('https://example.com', ssl_context=sslcont

ext)

如果你要验证自签名的证书,你也可以用之前的例子做同样的事,但是用的是

load_cert_chain():

sslcontext = ssl.create_default_context( cafile='/path/to/ca-bundle.crt')

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 -in crt.pem -inform PEM -outform DER > crt.der

注解:

提示: 16进制数字转换成二进制字节码,你可以用binascii.unhexlify().

TCPConnector中设置的verify_ssl, fingerprintssl_context都会被当做默认的 verify_ssl, fingerprintssl_contextClientSession或其他同类组件中的设置会覆

盖默认值。

警告:

verify_ssl ssl_context是互斥的。 MD5SHA1指纹虽不赞成使用但是是支

持的 - 这俩是非常不安全的哈希函数。

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_PROXYHTTPS_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 {'ACCESS-CONTROL-ALLOW-ORIGIN': '*', 'CONTENT-TYPE': 'application/json', 'DATE': 'Tue, 15 Jul 2014 16:49:51 GMT', 'SERVER': 'gunicorn/18.0', 'CONTENT-LENGTH': '331', 'CONNECTION': 'keep-alive'}

这是一个特别的字典,它只为HTTP头信息而生。根据 RFC 7230HTTP头信息中 的名字是不分区大小写的。同时也支持多个不同的值对应同一个键。

所以我们可以通过任意形式访问它:

>>>resp.headers['Content-Type'] 'application/json'

>>>resp.headers.get('content-type') 'application/json'

所有的header信息都是由二进制数据转换而来,使用带有 surrogateescape 选项 UTF-8编码方式(surrogateescape是一种错误处理方式,详情看))。大部分时候都 可以很好的工作,但如果服务器使用的不是标准编码就不能正常解码了。从 RFC 7230的角度来看这样的headers并不是合理的格式,你可以

ClientReponse.resp.raw_headers来查看原形:

30

基本及进阶使用

>>>resp.raw_headers ((b'SERVER', b'nginx'),

(b'DATE', b'Sat, 09 Jan 2016 20:28:40 GMT'), (b'CONTENT-TYPE', b'text/html; charset=utf-8'), (b'CONTENT-LENGTH', b'12150'), (b'CONNECTION', b'keep-alive'))

获取响应cookies:

如果某响应包含一些Cookies,你可以很容易地访问他们:

url = 'http://example.com/some/cookie/setting/url'

async with session.get(url) as resp:

print(resp.cookies['example_cookie_name'])

注意:

响应中的cookies只包含重定向链中最后一个请求中的 Set-Cookies 头信息设 置的值。如果每一次重定向请求都收集一次cookies请使用 aiohttp.ClientSession对象.

获取响应历史

如果一个请求被重定向了,你可以用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()

注意:

超时时间是累计的,包含如发送情况,重定向,响应解析,处理响应等所有操

作在内...

愉快地结束:

当一个包含 ClientSessionasync with 代码块的末尾行结束时(或直接调用

.close() ),因为asyncio内部的一些原因底层的连接其实没有关闭。在实际使 用中,底层连接需要有一个缓冲时间来关闭。然而,如果事件循环在底层连接关闭 之前就结束了,那么会抛出一个 资源警告: 存在未关闭的传输(通道)

( ResourceWarning: unclosed transport ),如果警告可用的话。 为了避免这种 情况,在关闭事件循环前加入一小段延迟让底层连接得到关闭的缓冲时间。 对于非

SSLClientSession , 使用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())

#Zero-sleep to allow underlying connections to close loop.run_until_complete(asyncio.sleep(0)) loop.close()

33

基本及进阶使用

对于使用了SSLClientSession , 需要设置一小段合适的时间:

...

#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 href="https://docs.python.org/3/library/asyncio-eventl

oop.html#asyncio-event-loop">事件循环(event loop)</a>用于处理HT

TP请求。

如果loopNone,则从connector中获取(如果也有的话)。 一般用asyncio.get_event_loop()来获取事件循环。

2.0版本后已被弃用。(2.0版本发布日是2017/3/20,之前的文档内写明 遭到弃用的还是会保留一年半。)

cookies (dict) – 发送请求时所携带的cookies(可选)

headers –

所有请求发送时携带的HTTP(可选)

可以是任何可迭代的键值对(key-value)对象或<a href="https://

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.

如果没有显式传递,那么aiohttp会自动生成诸如User-AgentConte nt-Type。使用该参数可以指定跳过,但注意Content-Length是不能跳过的。

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分钟)。传递None0来禁 用超时检测。

conn_timeout (浮点数) –

建立连接的超时时间(可选)0None则禁用超时检测。

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 async-with request(method, url, \, params=None, data=None, json=None, headers=None, skip_auto_headers=None, auth=None, allow_redirects=True, max_redirects=10, compress=None, chunked=None, expect100=False, read_until_eof=True, proxy=None, proxy_auth=None, timeout=5*60, verify_ssl=None, fingerprint=None, ssl_context=None, proxy_headers=None)*     以异步方式执行HTTP请求,返回响应对象。

   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信息。

aiohttp会自动生成诸如User-AgentContent-Type的信 (当然如果没有指定的话)。使用skip_auto_headers参数来跳过。

接受stristr(该参数可选)

auth(aiohttp.BasicAuth) – 接受HTTP基本认证对象。(该参数可选)

allow_redirects(布尔类型) – 如果设为False,则不允许重定向。默认是True

(该参数可选)

compress (布尔类型) – 如果请求内容要进行deflate编码压缩可以设为True如果设置了Content-EncodingContent-Length则不要使用这个参数。默认是

39

客户端部分参考

None(该参数可选)

chunked (整数) – 允许使用分块编码传输。开发者想使用分块数据流时,用它 就没错啦。如果是允许的,aiohttp将会设置为"Transfer-encoding:chunked" 这时不要在设置Transfer-encodingcontent-length这两个headers了。默认该 参数为None(该参数可选)

expect100(布尔类型) 服务器返回100时将等待响应(返回100表示服务器正在处 理数据)。默认是False(该参数可选)

read_until_eof (布尔类型) 如果响应不含Content-Length头信息将会一直读取 响应内容直到无内容可读。默认是True(该参数可选)

proxy – 代理URL,接受字符串或URL对象(该参数可选)

proxy_auth(aiohttp.BaicAuth) – 传入表示HTTP代理基础认证的对象。(该参数

可选)

timeout(整数) – 覆盖会话的超时时间。

verify_ssl(布尔类型) – HTTPS请求验证SSL证书(默认是验证的)。如果某些

网站证书无效的话也可禁用。

新增于2.3版本。

fringerprint (字节) – 传递想要验证的证书(使用DER编码的证书)SHA256值来

验证服务器是否可以成功匹配。对证书固定非常有用。

警告: 不赞成使用不安全的MD5SHA1哈希值。

新增于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 async-with get(url, \, allow_redirects=True, **kwargs)

   该方法会进行GET请求。

   kwargs用于指定些request的参数。

   *Parameters:

url - 请求的URL, 字符串或URL对象。

allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True

   返回ClientResponse:

      返回一个客户端响应(client response)对象。

   1.1版本修改的内容: URLs可以是字符串和URL对象。

coroutine async-with post(url, \, data=None, **kwargs)

   该方法会执行POST请求。

   kwargs用于指定些request的参数。

   *Parameters:

url - 请求的URL, 字符串或URL对象。

data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)

   返回ClientResponse:

      返回一个客户端响应(client response)对象。

      1.1版本修改的内容: URLs可以是字符串和URL对象。

coroutine async-with put(url, \, data=None, **kwargs)

   该方法会执行PUT请求。

   kwargs用于指定些request的参数。

   *Parameters:

url - 请求的URL, 字符串或URL对象。

data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)

   返回ClientResponse:

      返回一个客户端响应(client response)对象。

      1.1版本修改的内容: URLs可以是字符串和URL对象。

41

客户端部分参考

coroutine async-with delete(url, \*kwargs)

   该方法会执行DELETE请求。

   kwargs用于指定些request的参数。

   *Parameters:

url - 请求的URL, 字符串或URL对象。

   返回ClientResponse:

      返回一个客户端响应(client response)对象。

      1.1版本修改的内容: URLs可以是字符串和URL对象。

coroutine async-with head(url, \, allow_redirects=False, **kwargs)

   该方法执行HEAD请求。

   kwargs用于指定些request的参数。

   *Parameters:

url - 请求的URL, 字符串或URL对象。

allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True

   返回ClientResponse:

      返回一个客户端响应(client response)对象。

      1.1版本修改的内容: URLs可以是字符串和URL对象。

coroutine async-with options(url, \, allow_redirects=True, **kwargs)

   该方法执行options请求。

   kwargs用于指定些request的参数。

   *Parameters:

url - 请求的URL, 字符串或URL对象。

allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True

   返回ClientResponse:

      返回一个客户端响应(client response)对象。

      1.1版本修改的内容: URLs可以是字符串和URL对象。

coroutine async-with patch(url, \, data=None, **kwargs)

   该方法执行patch请求。

   kwargs用于指定些request的参数。

   *Parameters:

url - 请求的URL, 字符串或URL对象。

42

客户端部分参考

data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)

   返回ClientResponse:

      返回一个客户端响应(client response)对象。

      1.1版本修改的内容: URLs可以是字符串和URL对象。

coroutine async-with ws_connect(url, \, protocols=(), timeout=10.0, receive_timeout=None, auth=None, autoclose=True, autoping=True, heartbeat=None, origin=None, proxy=None, proxy_auth=None, verify_ssl=None, fingerprint=None, ssl_context=None, proxy_headers=None, compress=0)*

   创建一个websocket连接。返回ClientWebSocketResponse对象。

   Parameters:

url - Websocket服务器url, strURL对象。 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值来

验证服务器是否可以成功匹配。对证书固定非常有用。

警告: 不赞成使用不安全的MD5SHA1哈希值。 新增于2.3版本。

ssl_context (ssl.SLLContext) –

43

客户端部分参考

ssl上下文(管理器)用于处理HTTPS请求。(该参数可选)

ssl_context 用于配置证书授权通道,支持SSL选项等作用。

新增于2.3版本。

proxy_headers(abc.Mapping) – 如果该参数有提供内容,则会将其做为HTTP headers发送给代理。

新增于2.3版本。

compress (整数) -

`!此处有疑问!`

原文:

```

Enable Per-Message Compress Extension supp

ort.

0 for disable, 9 to 15 for window bit supp ort. Default value is 0.

```

允许支持(Per-Message Compress Extension).

0表示不使用,9-15表示支持的窗口位数。默认是0.

```

由于没用过websocket这里不是很懂,从源码来看是一个head

ers中的信息。可自行查看https://tools.ietf.org/html/rfc7692#secti

on-4

```

新增于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, encoding='utf-8', version=HttpVersion(major=1, minor=1), compress=None, chunked=None, expect100=False, connector=None, loop=None, read_until_eof=True)*

   异步的执行HTTP请求。返回一个响应对象(ClientResponse或其衍生对象)

   (参数)Parameters:

method (字符串) - HTTP方法,接受字符串。 url - 请求URL, 接受strURL对象。

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如果设置了Content-EncodingContent-Length则不要使用这个参数。默认是 None(该参数可选)

chunked (整数) – 允许使用分块编码传输。开发者想使用分块数据流时,用它 就没错啦。如果是允许的,aiohttp将会设置为"Transfer-encoding:chunked" 这时不要在设置Transfer-encodingcontent-length这两个headers了。默认该 参数为None(该参数可选)

45

客户端部分参考

expect100(布尔类型) 服务器返回100时将等待响应(返回100表示服务器正在处 理数据)。默认是False(该参数可选)

connector(aiohttp.connector.BaseConnector) - 接受BaseConnector的子类实

例用于支持连接池。

read_until_eof (布尔类型) 如果响应不含Content-Length头信息将会一直读取 响应内容直到无内容可读。默认是True(该参数可选)

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连接(包括HTTPHTTPS连接)

2.UnixConnector 用于使用UNIX连接(大多数情况下都用于测试的目的)。 所有的连接器都应是BaseConnector的子类。

默认情况下所有连接器都支持持久连接(keep-alive)(该行为由force_close参数 控制)

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连接。如果设置为Trueaiohttp

在两秒后额外执行一次停止。此功能默认不开启。

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处理HTTPHTTPS的连接器。

   如果你不知道该用什么连接器传输数据,那就用它吧。

   TCPConnector继承于BaseConnector.

   接受BaseConnector所需的所有参数和几个额外的TCP需要的参数。

   (参数)Parameters:

verify_ssl (布尔类型) –

HTTPS请求验证SSL证书(默认是验证的)。如果某些网站证书无效的话也可

禁用。(该参数可选)

2.3版本后不赞成通过ClientSession.get()方法传递该参数。

fingerprint (字节码) -

48

客户端部分参考

传递所期望的SHA256(使用DER编码)来验证服务器是否可以成功匹配。对证 书固定非常有用。

警告: 不赞成使用不安全的MD5SHA1哈希值。 新增于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套接字成员,默认有IPv4IPv6.

IPv4使用的是socket.AF_INET, IPv6使用的是socket.AF_INET6. 0.18版本修改的内容: 默认是0,代表可接受IPv4IPv6。可以传入socke t.AF_INETsocket.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连接。如果设置为Trueaiohttp会在两秒后额外执行一次停止。此功能默 认不开启。

   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,SHA1SHA256哈希值 ,如果没有的话会

返回None.

      该属性只读。

      新增于0.16版本。

   clear_dns_cache(self, host=None, port=None)

      清除内部DNS缓存。

      如果hostport指定了信息会删除指定的这个,否则清除所有的。

      新增于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 cookies,(Set-Cookie HTTP头信息设置, 属于SimpleCoo

kie)

headers

返回响应的HTTP头信息,是一个大小写不敏感的并联字典(CIMultiDictProxy)

raw_headers

返回原始HTTP头信息,未经编码,格式是键值对形式。

content_type

返回Content-Type头信息的内容。该属性只读。

54

客户端部分参考

注意

根据RFC2616,如果没有Content-Type包含其中则它的值为'application/oc tet-stream'.可以用使用'CONTENT-TYPE' not in resp.headers(raw_h

eaders)来弄清楚服务器的响应是否包含Content-type

charset

返回请求的主体的编码。

该值来自于Content-Type HTTP头信息。

返回的值是类似于'utf-8'之类的字符串,如果HTTP头中不含Content-Type 其中没有charset信息则是None

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)

      读取响应内容并返回解码后的信息。

      如果encodingNone则会从Content-Type中获取,如果Content-Type中也

没有会用chardet获取。

      如果有cchardet会优先使用cchardet

      如果读取数据时得到一个错误将会关闭底层连接,否则将会释放连接。

      Parameters: encoding(字符串) - 指定以该编码方式解码内容,None则自动 获取编码方式(默认为None)

      Return 字符串: 解码后的内容。

注意

如果Content-Type中不含charset信息则会使用cchardet/chardet获取 编码。

这两种方法都会拖慢执行效率。如果知道页面所使用的编码直接指定是比较好 的做法:

```

await resp.text('ISO-8859-1')

```

coroutine json(*, encoding=None, loads=json.loads,

content_type='application/json')

   JSON格式读取响应内容,解码和解析器可由参数指定。如果数据不能read则 会直接结束。

   如果encodingNone,会使用cchardetchardet获取编码。

   如果响应中的Content-type不能与参数中的content_type的值相匹配则会抛出 aiohttp.ContentTypeError错误。可传入None跳过此检查。

   Parameters:

encoding (字符串) - 传入用于解码内容的编码名,或None自动获取。 loads (可调用对象) - 用于加载JSON数据,默认是json.loads. content_type (字符串) - 传入字符串以查看响应中的content-type是否符合预 期,如果不符合则抛出aiohttp.ContentTypeError错误。传入None可跳过该检 测,默认是application/json

   Returns:

      返回使用loads进行JSON编码后的数据,如果内容为空或内容只含空白

56

客户端部分参考

则返回None

   request_info

      存放有headers和请求URLnametuple(一种方便存放数据的扩展类,存在 于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 – 发送PING时携带的消息,类型是由UTF-8编码的

字符串或字节码。(可选)

   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字符串的可调用对

象。默认是json.dumps()

      Raises:

RuntimeError 如果连接没有启动或已关闭会抛出这个错误。 ValueError 如果数据不是可序列化的对象会抛出这个错误。 TypeError 如果由dumps调用后返回的不是字符串会抛出这个错误。

   coroutine close(*, code=1000, message=b'')

      用于向服务器发起挥手(CLOSE)信息,请求关闭连接。它会等待服务器响 应。这是一个异步方法,所以如果要添加超时时间可以用asyncio.wait()asyncio.wait_for()包裹。

      Parameters:

code (整数) – 关闭状态码。

message – pong消息携带的信息,类型是由UTF-8编码的字符串或字节码。

(可选)

   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)

   从服务器返回的Set-Cookie信息中更新cookies

   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 cookieshttp.cookies.SimpleCookie

   save(file_path)

      pickle形式将cookies信息写入指定路径。

      Parameters: file_path – 要写入的路径。字符串或pathlib.Path实例对象。

   load(file_path)

      从给定路径读取被picklecookies信息。

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标准异常如ValueErrorTypeError

   读取响应内容可能会抛出ClientPayloadError异常。该异常表示载体编码时有些 问题。比如有无效的压缩信息,不符合分块编码要求的分块内容或内容与content- length指定的大小不一致。

   所有的异常都可当做aiohttp模块使用。

   exception aiohttp.ClientError

      所有客户端异常的基类。

      继承于Exception

   class aiohttp.ClientPayloadError

      该异常只会因读取响应内容时存在下列错误而抛出:

       1. 存在无效压缩信息。

       2. 错误的分块编码。

       3. Conent-Length不匹配的内容。

      继承于ClientError.

   exception aiohttp.InvalidURL

      不合理的URL会抛出此异常。比如不含域名部分的URL

      继承于ClientErrorValueError

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属同类异常。

   继承于ClientConnectionErrorOSError.

class aiohttp.ClientConnectorError

   连接器相关异常。

   继承于ClientOSError

class aiohttp.ClientProxyConnectionError

   继承于ClientConnectonError(由源码知继承于ClientConnectorError原文写错

了。)

class aiohttp.ServerConnectionError

   继承于ClientConnectonError(由源码知继承于ClientConnectionError原文写错

了。)

class aiohttp.ClientSSLError

   继承于ClientConnectonError(由源码知继承于ClientConnectorError原文写错

了。)

class aiohttp.ClientConnectorSSLError

   用于响应ssl错误。

   继承于ClientSSLErrorssl.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 -V Python 3.5.0

我们需要python 3.5.0及以上版本。

假设你已经安装好aiohttp库了。你可以用以下命令来查询当前aiohttp库的版本。

$python3 -c 'import aiohttp; print(aiohttp.__version__)' 2.0.5

项目结构与其他以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 -X GET

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 -X GET localhost:8080 Hello Aiohttp!

啊哈!成功了!我们现在应该有了一个如下所示的目录结构:

.

├── .. └── polls

├── aiohttpdemo_polls

├── main.py

├── routes.py

└── views.py

使用配置文件

aiohttp不需要任何配置文件,也没有内置支持任何配置架构。 但考虑到这些事实:

1.99%的服务器都有配置文件。

2.其他的同类程序(除了以Python为基础的像DjangoFlask)都不会将配置文件

71

服务端快速习得指南

作为源码的一部分。

比如Nginx将配置文件保存在 /etc/nginx文件夹里。

mongo则保存在 /etc/mongodb.conf里。

3.使用配置文件是公认的好方法,在部署产品时可以预防一些小错误。 所以我们建议用以下途径(进行配置文件):

1.将配置信息写在yaml文件中。(jsonini都可以,但yaml最好用。)

2.在一个预先设定好的目录中加载yaml

3.拥有能通过命令行来设置配置文件的功能。如: ./run_app -- config=/opt/config/app_cfg.yaml

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 来写数据库架构。我们只要创建两个简单的模块

——questionchoice :

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站点都有一些静态文件: 图片啦,JavaScriptCSS文件啦等等。 在生产环 境中处理这些静态文件最好的方法是使用NGINXCDN服务做反向代理。 但在开 发环境中使用aiohttp服务器处理静态文件是很方便的。

只需要简单的调用一个信号即可:

app.router.add_static('/static/',

path=str(project_root / 'static'),

name='static')

project_root表示根目录。

使用中间件

中间件是每个web处理器必不可少的组件。它的作用是在处理器处理请求前预处理 请求以及在得到响应后发送出去。

我们下面来实现一个用于显示漂亮的404500页面的简单中间件。

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

这些 overrideshandle_404handle_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 -m aiohttp.web -H localhost -P 8080 package.module:init _func

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+}')

注意:

正则表达式匹配的是非百分号编码的URLrequest.raw_path)字符。比如 空格编 码后是%20

RFC 3986规定可在路径中的字符是:

82

服务端使用

allowed

= unreserved / pct-encoded / sub-delims

/":" / "@" / "/"

pct-encoded = "%" HEXDIG HEXDIG

 

unreserved

= ALPHA / DIGIT / "-" / "." / "_" / "~"

 

 

 

 

 

sub-delims

= "!" / "$" / "&" / "'" / "(" / ")"

 

 

 

 

 

 

 

/ "*" / "+" / "," / ";" / "="

 

 

使用命名资源进行反向引用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', name='user-info')

这时传入那部分即可:

>>>request.app.router['user-info'].url_for(user='john_doe')\

....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架构 如FlaskDjango在这点上有所不同。

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 所产生的错误并不能被捕获,这样会导致有很多asyncioFuture异常的日志消息并且是不可恢复的,因为Task被销毁而不是挂起了。

此外,在按照优雅地关闭(Graceful shutdown)中的进行关闭操作

时, aiohttp 并不会等待这些任务完成,所以你还要找个机会来释放这些重要的 数据。

88

服务端使用

另一方面, aiojobs 提供一个生成新任务并且能等待结果(等此类操作)

APIaiojobs 会将所有待完成的操作存入内部数据结构中,并且可以优雅地终 止这些操作:

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文件等)最好的方法是使用反向代理,像是 nginxCDN服务。

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

client_max_size

服务端使用

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 url-safe base64-encoded bytes fernet_key = fernet.Fernet.generate_key() secret_key = base64.urlsafe_b64decode(fernet_key) setup(app, EncryptedCookieStorage(secret_key)) app.router.add_route('GET', '/', handler)

return app

web.run_app(make_app())

处理HTTP表单

aiohttp 直接提供HTTP表单支持。

如果表单的方法是 “GET”

),则要使用 Request.query 获取数据。 如果是“POST”

Request.post()Request.multipart() Request.post() 接受标明

'application/x-www-form-urlencoded''multipart/form-data' 的数

据()。它会将数据存进一个临时字典中。如果指定了,超出

93

服务端使用

了的话会抛出 ValueError 异常,这时使用 Request.multipart() 是更好的选 择,尤其是在上传大文件时。

小例子: 由以下表单发送的数据:

<form action="/login" method="post" accept-charset="utf-8" enctype="application/x-www-form-urlencoded">

<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 元素并且设置为了 "multipart/form-data" 。 我们写一个可 以上传MP3文件的表单(作为例子):

94

服务端使用

<form action="/store/mp3" method="post" accept-charset="utf-8" enctype="multipart/form-data">

<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(

{'CONTENT-DISPOSITION': mp3_file}))

注意例子中的警告。通常 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

#如果是分块传输的,别用Content-Length做判断。

size = 0

with open(os.path.join('/spool/yarrr-media/mp3/', filename), 'wb') as f:

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不过如果我们有维护一个基于 aiohttpSockJS包,用于部署兼容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规定来确定的: 100-300不是由错误引起的; 400 之后是客户端错误,500之后是服务器端错误。

异常等级图:

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.Applicationaiohttp.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': message}).encode('utf-8'), content_type='application/json')

@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实例对象和请求处理器最后返回一个新的处理器。

注意:

外部中间件工厂和内部中间件处理器会处理每一个请求。 中间件工厂要返回一个与 请求处理器有同样功能的新处理器——接受Request实例对象并返回响应或抛出异 常。

信号

信号组件新增于0.18版本。

尽管中间件可以自定义之前和之后的处理行为,但并不能自定义响应中的行为。所 以信号量由此而生。

比如,中间件只能改变没有预定义HTTP头的响应的HTTP 头(看 prepare() ), 但有时我们需要一个可以改变流式响应和WebSockets HTTP头的钩子。所以我们 可以用 on_response_prepare 信号来充当这个钩子:

async def on_prepare(request, response): response.headers['My-Header'] = 'value'

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_startupon_shutdownon_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.CORKtcp_cork设置为True)。这个选项不会发送一部分TCP/IP帧。当这个 选项被(再次)清除时会发送所有已经在队列中的片段帧。因为会把各种小帧 聚合起来发送,所以这种方式对发送大量片段数据非常理想。 如果操作系统不 支持CORK模式(不管是socket.TCP_CORK还是socket.TCP_NOPUSH)那 该模式与Nagle模式一样。一般来说是windows系统不支持此模式。

2.NODELAYtcp_nodelay设置为True)。这个选项会禁用Nagle算法。选用这 个那么无论数据多小都会尽快发出去,即使是很小的数据。该模式对发送少量 数据非常理想。

3.Nagle算法(tcp_corktcp_nodelay都为False)。该模式会先缓存数据,直到 达到预定的数据大小后再一起发送。如果要发送HTTP数据应该避免使用这个 模式除非你确定要使用它。

默认情况下,流数据( StreamResponse,标准响应( Responsehttp异常及 其派生类)和websocketsWebSocketResponse )使用NODELAY模式,静态文 件处理器使用CORK模式。

可以使用 set_tcp_cork( )方法和 set_tcp_nodelay() 方法手动切换。

使用Expect

aiohttp.web支持使用Expect头。默认是HTTP/1.1 100 Continue,如果Expect头不

"100-continue" 则抛出 HTTPExpectationFailed 异常。你可以自定 义 Expect处理器。如果 Expect存在的话则会调用 Expect处理

Expect处理器 会先于中间件和路由处理器被调用。 Expect处理器 可以返

None ,返回 None 则会继续执行(调用中间件和路由处理器)。如果返回的 是 StreamResponse 实例对象,之后请求处理器则使用该返回对象作为响应内 容。也可以抛出 HTTPException 的子类对象。抛出错误的时候之后的处理将不会 进行,客户端将会接受一个适当的http响应。

注意:

108

服务端使用

如果服务器不能理解或不支持的Expect域中的任何期望值,则服务器必须返回417 或其他适当的错误码(4xx状态码)。

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20

如果所有的检查通过,在返回前自定义处理器需要会将 HTTP/1.1 100 Continue 写入。

下面是一个自定义 Expect处理器 的例子:

async def check_auth(request):

if request.version != aiohttp.HttpVersion11: return

if request.headers.get('EXPECT') != '100-continue':

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()

后台任务

有些时候我们需要执行些异步操作。 甚至需要在请求处理器处理问题时进行一些后 台操作。比如监听消息队列或者其他网络消息/事件资源(ZeroMQRedis Pub/SubAMQP等)然后接受消息并作出反应。 例如创建一个后台任务,用于在 zmq.SUB套接字上监听ZeroMQ,然后通

WebSocketapp['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 ch.iter(encoding='utf-8'):

#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 NotFound500 Internal Error之类的错误页面可以看Middlewares章节了解详

情,这些都可以通过自定义中间件完成。

基于代理部署服务器

Server Deployment中讨论了基于反向代理服务(像nginx)部署 aiohttp 来用于 生产使用。 如果使用这种方法,就不要在使用scheme, hostremote了。 将正确 的值配置在代理服务器中,之后不管是使用 Forwarded 还是使用旧式的 X- Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto HTTP头都是可以 的。 aiohttp 默认不会获取 forwarded 值,因为可能会引起一些安全问题: HTTP客户端也可以自己添加这个值,非常不值得信任。 这就是为什

aiohttp 应该在使用反向代理的自定义中间件中设置forwarded头的原因。 在 中间件中改变schemehostremote可以用 clone()

待更新: 添加一个可以很好的配置中间件的第三方项目。

Swagger 支持

aiohttp-swagge r是一个允许在 aiohttp.web 项目中使用Swagger-UI的库。

CORS 支持

aiohttp.web 本身不支持跨域资源共享( Cross-Origin Resource Sharing ),但可以用 aiohttp_cors

调试工具箱

开发 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)

愉快的调试起来吧~

开发工具

aiohttp-devtools 提供几个简化开发的小工具。

可以使用pip安装:

$pip install aiohttp-devtools

* ``runserver`` 提供自动重载,实时重载,静态文件服务和aiohttp_debugto

olbar_integration

* ``start`` 是一个帮助做繁杂且必须的创建'aiohttp.web'应用的命令。

创建和运行本地应用的文档和指南请看 aiohttp-devtools

114

底层服务器搭建

底层服务器

这一节介绍了aiohttp.web的基础底层服务器。

抽象基础

有时候用户不需要更高级的封装,像是 applicationrouterssignals。 只是需要 一个支持异步调用并且是接受请求返回响应对象的东西。 在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 发起/处理Web-Socket连接。

运行基础底层服务器

请看下列代码:

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, Web-Socket 都可以,无论哪一个路径的访问也都同样由其处理。 不过也同样很基础: 无论如何处理器都只返回 200 OK 。实际生活中所产生的状态 要复杂的多。

116

服务器端参考

服务器端参考

请求(Request)和基础请求(BaseRequest)

Request 对象中包含所有的HTTP请求信息。

BaseRequest 用在底层服务器中(底层服务器没有应用,路由,信号和中间件)。 Request对象拥有Request.appRequest.match_info属性。 BaseRequestReuqest都是类字典对象,以便在中间件信号处理器中共享数

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 X-Forwarded-Proto不在被使用。

       调用 .clone(scheme=new_scheme) 来设置一个新的值。

117

服务器端参考

扩展

基于代理部署服务器

   secure

       设置 request.url.scheme == 'https' 的快捷方法。

       该属性只读,True或者False

   forwarded

       包含了所有已编译过的Forwarded头信息的元组。

       尽可能做到符合RFC 7239规定:

       1. 为每个Forwarded域值添加一个不可变的字典。字典内的元素等同于 Forwarded域值中的数据,该数据是客户端首次遇到的代理时所添加的值。随后的 项是客户端后来遇到的代理所添加的值。

       2. 检查每个值是否符合RFC 7239#section-4中的语法规定:令牌(token 或已编译字符串(quoted-string)。        3. 对已编译对(quoted-pairs)进行 un-escape解码。        4. 根据RFC 7239#section-6规定,将不验证‘by’‘for’ 内容。        5. 不验证host内容(Host ABNF)。        6. 对于有效的URI协议 名不验证其协议内容。

       返回包含一个或多个MappingProxy对象的元组。

   host

       请求中的主机(Host)名,以此顺序解析:

clone() 方法所设置的值。 HTTP头信息中的HOST

socket.gtfqdn()

       该属性只读,类型为str

       2.3版本时更改内容: Forwarded X-Forwarded-Proto不在被使用。

       调用 .clone(host=new_host) 来设置一个新的值。

扩展

基于代理部署服务器

   remote

       初始化HTTP请求的IP地址。

       按以下顺序解析:

118

服务器端参考

clone() 方法所设置的值。 已打开的socketPeer的值。

       该属性只读,类型为str

       调用 .clone(remote=new_remote) 来设置一个新的值。

       新增于 2.3版本。

扩展

基于代理部署服务器

   path_qs

       包含路径信息和查询字符串的URL/app/blog?id=10)。

       该属性只读,类型为str

   path

       包含路径信息但不含有主机(host)或协议(scheme)的

URL/app/blog)。路径是URL解码(URL-unquoted)后的。获取原始路径信息 请看 raw_path

       该属性只读,类型为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客户端进行 keep-alive 连接并且协议版本也支持的话则 为True,否则是False

       该属性只读,类型为Bool

   transport

       用于处理请求的传输端口(transport),该属性只读。

       该属性可以被用在获取客户端 peerIP地址时。

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

       返回Content-Type头信息的内容,该属性只读。

       类型是str,如'text/html'

注意

如果无Content-Type头信息,根据RFC 2616的规定,返回的值为

'application/octet-stream'

   charset

       请求主体(BODY)使用的编码,该属性只读。

       该值由Content-Type头信息解析而来。

       返回值为字符串,如'utf-8'如果没有则为None

   content_length

       请求主体(BODY)的长度,该属性只读。

       该属性由Content-Length解析而来。

       返回值为整数,如果没有Content-Length信息则为None

   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 f.read(rng.stop-rng.start)

2.切片方式:

121

服务器端参考

return buffer[request.http_range]

       新增于 1.2版本。

   if_modified_since

       返回由If-Modified-Since头信息指定的日期值。

       类型为 datetime.datetime,如果没有If-Modified-Since值或是一个无效的 HTTP日期则为None

   clone(*, method=..., rel_url=..., headers=...)

       克隆自己,并将相应的值替换。

       创建并返回一个新Request实例对象。如果没有传递任何参数,将会复制一 份一模一样的。如果没有传递某一个参数,那个值与原值一样。

       参数:

method (str) - http方法。

rel_url - 使用的urlstrURL对象。

headers - CIMuliDictProxy对象或其他兼容头信息的容器。

       返回:           返回克隆的Request对象。

   coroutine read()

       读取请求主体,返回带有主体内容的bytes对象。

注意

该方法在内部存储已读信息,之后的调用会返回同样的值。

   coroutine text()

       读取请求主体,使用charset所返回的编码解码如果MIME-type并未指定编码 信息则使用UTF-8解码。

       返回带有主体内容的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)保 持一致。

警告

该方法并不在内部存储已读信息。也就是说你读完一次之后不能在用它读第二次了。

扩展

使用Multipart

123

服务器端参考

   coroutine post()

       一个从请求主体中读取POST参数的协程方法。

       返回带有已解析后的数据的MultiDictProxy实例对象。

       如果HTTP方法不是 POST, PUT, PATCH, TRACE DELETE,或者 content_type非空,或者存在application/x-www-form-urlencodedmultipart/form-

data,将会返回空的并联字典(multidict)。

注意

该方法在内部存储已读信息,之后的调用会返回同样的值。

   coroutine release()

       用于释放请求。

       未读的HTTP主体将会被清空。

注意

用户代码中不应存在调用release()aiohttp.web会在内部自行处理。

class aiohttp.web.Request     web处理器中接受请求信息的Request类。

   所有的处理器的第一个参数都要接受Request类的实例对象。

   该类派生于BaseRequest,支持父类中所有的方法和属性。还有几个额外的:

   match_info

       返回AbstractMatchInfo实例对象,内容是路由解析的结果,该属性只读。

注意

属性的具体类型由路由决定,如果app.routerUrlDispatcher,则该属性包含的是 UrlMappingMatchInfo实例对象。

   app

       返回一个用于调用请求处理器的应用(Application)实例对象。

124

服务器端参考

注意

用户不要手动创建Request实例对象,aiohttp.web会自动完成这些操作。但你可以 使用clone()方法来进行一些修改,比如路径和方法之类的修改。

响应类

目前为止,aiohttp.web提供三个不同的响应类: StreamResponse , ResponseFileResponse

通常你要使用的是第二个也就是 ResponseStreamResponse 用于流数

据, Response 会将HTTP主体保存在一个属性中,以正确的Content-Length头信 息发送自己的内容。

为了设计考虑, Response 的父类是 StreamResponse

如果请求支持keep-alive的话,响应也是同样支持的,无需其他操作。 当然,你可以使用 force_close() 来禁用keep-alive

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()

       禁止keep-alive连接。不过禁止后没有方法再次开启了。

   compression

       如果允许压缩则返回True,否则返回False。该属性只读。

       默认是False

126

服务器端参考

   enable_compression(force=None)

       开启压缩。

       force如果没有设置则使用的压缩编码从请求的 Accept-Encoding 头信息 中选择。

       如果force设置了则不会管 Accept-Encoding 的内容。

   chunked

       指代是否使用了分块编码,该属性只读。

       可以通过调用 enable_chunked_encoding() 开启分块编码。

   enable_chunked_encoding()

       允许响应使用分块编码。 开启之后没有方法关闭它。允许之后,每 次 write() 都会编码进分块中。

警告

分块编码只能在HTTP/1.1中使用。

content_length和分块编码是相互冲突的。

   headers

       携带HTTP头信息的CIMultiDict实例对象。

   cookies

       携带cookies信息的http.cookies.SimpleCookie实例对象。

警告

直接使用Set-Cookie头信息设置cookie信息会被显式设置cookie操作所覆盖。 我们推荐使用cookiesset_cookie()del_cookie()进行cookie相关操作。

   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已不再赞成使用,请使用更简单的 max-age来设置, IE6,7,8并不支持max-age

   del_cookie(name, *, path='/'. domain=None)

       删除某cookie

       参数:

name (str) - cookie名称。

domain (str) - cookie主域(可选)。

path (str) - cookie的路径(可选,默认是'/')。     1.0版本修改内容: 修复

IE11以下版本的过期时间支持。

   content_length

       响应中的Content-Length头信息。

   content_type**

       响应中Content-Type中设置的内容部分。

128

服务器端参考

   charset

       响应中Content-Type中设置的编码部分。

       该值会在调用时被转成小写字符。

   last_modified

       响应中的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(FreeBSDMacOSX)

       使用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)

       发送byte-ish数据,该数据作为响应主体的一部分。        需要在此之前调

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 ,否则是 application/octet-stream

charset (str) - 响应的charset。如果有传入text参数则为 utf-8 ,否则是 None

   body

       存储响应内容或者叫响应主体,该属性可读可写,类型为bytes

       设置body会重新计算content_length的值。

       设置bodyNone会将content_length也一并设置为None,也就是不写入 Content-Length HTTP头信息。

   text

       存储响应内容(响应主体),该属性可读可写,类型为str

       设置text会重新计算content_lengthbody的值。

       设置textNone会将content_length也一并设置为None,也就是不写入 Content-Length HTTP头信息。

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版本。

   为了支持缓慢的websockets所带来的背压(back-pressure,服务器达到可连接 的上限,而这时又有新的客户端请求连接就会出现背压。具体可看知乎中的解释) 现象,相关方法都被设计成协程方法。默认写缓存的大小为64K    *参数:

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,或者也 应该是intFalseTrue对应01))     新增于 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. okTrueWebSocketReady.protocolwebsocket的子协议,由客户端传递被 服务端所接受(一个由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

消息载体,str(会被转换成utf-8编码的bytes)或者bytes        如果连接未开启或已关闭则抛出`Run

timeError`异常。

    **pong(message=b'')**

       发送迷之PONG消息到peer

133

服务器端参考

       **参数**message - 可选pong

消息载体,str(会被转换成utf-8编码的bytes)或者bytes        如果连接未开启或已关闭则抛出`Run

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 - 可选的pong消息载体,str(会被转换为utf-8编码的bytes)bytes

       **可能抛出的异常**

* RuntimeError - 如果连接未开启则抛出。

    *coroutine receive(timeout=None)*

       等待peer即将发来的数据消息并返回

它的协程方法。

       该方法在PINGPONGCLOSE处理

时都有调用,但并不返回。

       该方法在内部会执行ping-pong游戏

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 href="https://github.com/HuberTRoy/aiohttp-chinese-document/b

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

s://github.com/HuberTRoy/aiohttp-chinese-document/blob/master/ai

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=

"https://docs.python.org/3/library/asyncio-eventloop.html#asynci

o-event-loop">事件循环</a>

    **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 href="https://github.com/HuberTRoy/aiohttp-chinese-document/b

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 Keep-Alive`。默认为`True`

*keepalive_timeout (int) - 关闭`Keep-Alive`连接前所持续的秒数。默认75NGINX的默认值)。

*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

urceDynamicResource`)。

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) - 异常处理器,可选。

       返回`PlainResourceDynamicR

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方法到这个路由上。

       如果有nameHEAD的路由会被添加

一个'-head'后缀。举个栗子:`router.add_get(path, handler, name='rou

te')`会添加两个路由:一个是GET方法名为'route',另一个是HEAD方法名为'rout e-head'

       该方法新增于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)**

       添加一个用于返回静态文件的路由和

处理器。

       对于获取图片,jscss等文件非常

有用。

       在支持它的平台上,使用这个可以让 文件发送系统(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

/aiohttp-chinese-document/blob/master/aiohttp%E6%96%87%E6%A1%A3/

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方法的集

合。

    如果请求被解析(resolvematch_info会是`UrlMappin

gMatchInfo`,如果没有找到任何路由则是None

### xxx

    **get_info()**

       返回资源的描述。如`{'path': '/

150

服务器端参考

path/to'}`, `{'formatter': '/path/{to}', 'pattern': re.compile(r

'^/path/(?P<to>[a-zA-Z][_a-zA-Z0-9]+)$)`

    **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 TRoy/aiohttp-chinese-document/blob/master/aiohttp%E6%96%87%E6%A1

%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/ aiohttp-chinese-document/blob/master/aiohttp%E6%96%87%E6%A1%A3/S

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)*

       `100-continue`的处理器。

*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 href="https://github.com/HuberTRoy/aiohttp-chinese

-document/blob/master/aiohttp%E6%96%87%E6%A1%A3/ServerUsage.md#

可变形资源">可变资源</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**

       一个用于处理 100-continue的协

程处理器。

    **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,经由SSLHTTP默认则是8443(需要指定ssl_context参数)。

path (str) - 作为HTTP服务器,Unix域套接字的文件系统中的路径。绑定多个 域套接字可以是一组路径序列。监听Unix域套接字并不是支持所有操作系统 的。

sock (socket) - 预先存在的套接字(socket)对象,用于接收连接。可以传递 一组套接字(socket)序列。

shuntdown_timeout (int) - 关闭服务器时,解除所有已连接的客户端套接字的

超时时间。配置有良好关闭程序的系统基本不会用到这个超时时间,配置良好 只需要几毫秒即可完成关闭。

ssl_context - HTTPS服务器所使用的ssl.SSLContextNone的话会使用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_slashesappend_slash

   如果路径至少符合其中一个条件,则重定向到一个新路径上。

   在需要添加斜线时 append_slash 也需要是Ture才会执行。是True的话当一个 资源定义时尾部有斜线,但请求没有斜线时,将会自动给请求加上斜线。

   如果merge_slashesTrue,将会把路径中连续的斜线变成一个斜线。

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" "%{User-Agent}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 pytest-aiohttp

如果你不想安装它,你可以在conftest.py中插入一行

pytest_plugins='aiohttp.pytest_plugin' 来代替这个包。

临时状态说明

该模块是临时的。

对于已经废弃的API,基于向后不兼容政策,aiohttp允许仍可以继续使用一年半的 时间。

不过这对aiohttp.test_tools则不适用。

同时,如有一些必要的原因,我们也会不管向后兼容期而做出更改。

客户端与服务器端测试程序

aiohttp中的test_utils有一个基于aiohttpweb服务器测试模板。

其中包含两个部分: 一个是启动测试服务器,然后是向这个服务器发起HTTP请求。 TestServer使用以aiohttp.web.Application为基础的服务器。RawTestServer则使用

166

测试

基于aiohttp.web.WebServer的低级服务器。 发起HTTP请求到服务器你可以创建一 个TestClient实例对象。 测试客户端实例使用aiohttp.ClientSession来对常规操作如 ws_connect,get,post等进行支持。

Pytest

pytest-aiohttp插件允许你创建客户端并向你的应用程序发起请求来进行测试。

简易程序如下:

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

f-8'))

@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*)     一 个创建TestServerfixture

168

测试

async def test_f(test_server):

app = web.Application()

#这里填写路由表

server = await test_server(app)

   服务器会在测试功能结束后销毁。

   appaiohttp.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处理器 实例创建RawTestServerfixture

   处理器应是一个可以接受请求并且返回响应的协同程序:

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

   一个允许使用aiohttpweb应用程序进行单元测试的基础类。

   该类派生于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()

       默认该方法什么也不做,不过可以被覆盖用于在TestCasesetUp阶段执行 异步代码。 新增于2.3.0版本。

   coroutine tearDownAsync()

       默认该方法什么也不做,不过可以被覆盖用于在TestCasetearDown阶段 执行异步代码。 新增于2.3.0版本。

   setUp()

       标准测试初始化方法。

   tearDown()

       标准测试析构方法。

注意

TestClient的方法都是异步方法,你必须使用异步方法来执行它的函数。 使用unittest_run_loop()装饰器可以包装任何一个基础类中的测试方法。

172

aiohttp.test_utils.make_mocked_request()

测试

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, POSTpath(str) - str, 带有URL的路径信息但没有主机名的字符串。

headers(dict, multidict.CIMultiDict, 成对列表) - 一个包含头信息的映射对象。 可传入任何能被multidict.CIMultiDict接受的对象。

match_info(dict) - 一个包含url参数信息的映射对象。 version(aiohttp.protocol.HttpVersion) - 带有HTTP版本的namedtupleclosing(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实例并且返回响应实例(StreamResponseResponse之类的)。对于非200HTTP响应,处理器可以抛出 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对象,会为应用程序自动创建一个TestServercookie_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连接。

       apiaiohttp.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选项。

你可以阅读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 X-Forwarded-For $proxy_add_x_forwarded_fo

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.comHTTP请求(除

http://example.com/static )都将重定向到example1.sock,

example2.sock, example3.sock example4.sock后端服务器。默认情况下,

Nginx使用轮询调度算法(round-robin)来选择后端服务器。

注意

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 --path=/tmp/example_%(proces s_num)s.sock

;We can just as easily pass TCP port numbers:

;command=/path/to/aiohttp_example.py --port=808%(process_num)s

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")

parser.add_argument('--path') parser.add_argument('--port')

if __name__ == '__main__': app = web.Application()

# configure app

args = parser.parse_args()

web.run_app(app, path=args.path, port=args.port)

当然在真实环境中我们还要做些其他事情,比如配置日志等等,但这些事情不在本 章讨论范围内。

Nginx + Gunicorn

我们还可以使用Gunicorn来部署aiohttpGunicorn基于pre-fork worker模式。 Gunicorn将你的app当做worker进程来处理即将到来的请求。

与部署Ngnix相反,使用Gunicorn不需要我们手动启动aiohttp进程,也不需要使用 如supervisord之类的工具进行监控。但这并不是没有代价:在Gunicorn下运行 aiohttp应用会有些许缓慢。

准备环境

在做以上操作之前,我们首先要做的就是配置我们的部署环境。本章例子基于 Ubuntu 14.04

首先为应用程序创建个目录:

186

服务器部署

>>mkdir myapp

>>cd myapp

Ubuntu中使用pyenv有一个bug,所以我们需要做些额外的操作才能配置虚拟环 境:

>>pyvenv-3.4 --without-pip venv

>>source venv/bin/activate

>>curl https://bootstrap.pypa.io/get-pip.py | python

>>deactivate

>>source venv/bin/activate

好啦,虚拟环境我们已经配置完了,之后我们要安装aiohttpgunicorn:

>>pip install gunicorn

>>pip install -e git+https://github.com/aio-libs/aiohttp.git#eg g=aiohttp

应用程序

我们写一个简单的应用程序,将其命名为 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其他选项时写入也可以写在配置文 件中。

本章例子所使用到的选项:

-bind 用于设置服务器套接字地址。

-worker-class 表示使用我们自定义的worker代替Gunicorn的默认worker。 你可能还想用 -workers Gunicorn知道应该用多少个worker来处理请求。 (建议的worker数量请看 设置多少个worker合适?

>>gunicorn my_app_module:my_web_app --bind localhost:8080 --wor ker-class aiohttp.GunicornWebWorker

[2015-03-11 18:27:21 +0000] [1249] [INFO] Starting gunicorn 19.3

.0

[2015-03-11 18:27:21 +0000] [1249] [INFO] Listening at: http://1 27.0.0.1:8080 (1249)

[2015-03-11 18:27:21 +0000] [1249] [INFO] Using worker: aiohttp. worker.GunicornWebWorker

[2015-03-11 18:27:21 +0000] [1253] [INFO] Booting worker with pi d: 1253

现在,Gunicorn已成功运行,随时可以将请求交由应用程序的worker处理。

注意

如果你使用的是另一个asyncio事件循环uvloop, 你需要

aiohttp.GunicornUVLoopWebWorker worker类。

其他内容

Gunicorn 文档建议将Gunicorn部署在Nginx代理服务器之后。可看下官方文档的建 议

配置日志

188

服务器部署

aiohttpGunicorn 使用不同的日志格式。 默认aiohttp使用自己的日志格 式:

'%a %l %u %t "%r" %s %b "%{Referrer}i" "%{User-Agent}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)

      用户处理100-continue的抽象方法。

抽象类基础视图

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组件。

   提供一个统计所存储的cookiesAPI:

len(session.cookie_jar)

   jar也可以被迭代:

191

抽象基类参考

for cookie in session.cookie_jar:

print(cookie.key)

print(cookie["domain"])

   此类用于存储cookies,同时提供collection.abc.Iterablecollections.abc.Sized的功能。

   update_cookies(cookies, response_url=None)

      更新由服务器返回的Set-Cookie头所设置的cookies

      参数:

         cookies - 接受 collection.abc.Mapping 对象(如dict, SimpleCookie

等)或由服务器响应返回的可迭代cookies键值对。

         response_url(str) - 响应该cookiesURL,如果设置为None则该cookies 为共享cookies。标准的cookies应该带有服务器URL,只在请求此服务器时发送, 共享的话则会向全部的客户端请求都发送。

   filter_cookies(request_url)

      返回jar中可以被此URL接受的cookies和可发送给给定URL的客户端请求的 Cookies 头。

      参数:

         request_url (str) - 想要查询cookies的请求URL

      返回:

         返回包含过滤后的cookieshttp.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

MultipartReader.from_response()

使用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 实例对象,否则将会是一个嵌套MultipartMultipartReader

实例对象。记住,Multipart的格式就是递归并且支持嵌套多层。如果接下来没有内 容可以获取了,则返回 None - 然后就可以跳出这个循环了:

193

使用Mulitipart

if part is None: break

BodyPartReaderMultipartReader 都可访问内容的 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() application/www-urlform-encode 内容。 如

果传输内容使用了gzipdeflate进行过编码则会自动识别,或者如果是base64 quoted-printable这种情况也会自动解码。不过如果你需要读取原始数据,使

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- Typetext/plain; charset=utf-8 :

mpwriter.append('hello')

195

使用Mulitipart

二进制则是 application/octet-stream :

mpwriter.append(b'aiohttp')

你也可以使用第二参数来覆盖默认值:

mpwriter.append(io.BytesIO(b'GIF89a...'),

{'CONTENT-TYPE': 'image/gif'})

对于文件对象 Content-Type 会使用Pythonmimetypes 模块来做判断,此 外, Content-Disposition 头会把文件的基本名包含进去。

part = root.append(open(__file__, 'rb'))

如果你想给文件设置个其他的名字,只需要操作 BodyPartWriter 实例即可,使

BodyPartWriter.set_content_disposiition()MultipartWriter.appe nd() 方法总会显式的返回和设置 Content-Disposition :

part.set_content_disposition('attachment', filename='secret.txt'

)

此外,你还可以设置些其他的头信息:

part.headers[aiohttp.hdrs.CONTENT_ID] = 'X-12345'

如果你设置了 Content-Encoding ,后续的数据都会自动编码:

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() 对每个部分都生成一个块,如果拥

Content-Encoding 或者 Content-Transfer-Encoding 头信息会被自动应用

到流数据上。

注意,在被MultipartWriter.serialize()处理时,所有的文件对象都会被读至末尾,不 将文件指针重置到开始时是不能重复读取的。

Multipart使用技巧

互联网上充满陷阱,有时你可能会发现一个支持Multipart的服务器出现些奇怪的情 况。 比如,如果服务器使用了 cgi.FieldStorage ,你就必须确认是否包

Content-Length 头信息:

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

CONTENT-TYPE: multipart/mixed; boundary=--:

--:

CONTENT-TYPE: application/json

{"_id": "foo"} --:

CONTENT-TYPE: multipart/related; boundary=----:

----:

CONTENT-TYPE: application/json

{"_id": "bar"}

----:

CONTENT-TYPE: text/plain

CONTENT-DISPOSITION: attachment; filename=bar.txt

bar! bar! bar!

----:--

--:

CONTENT-TYPE: application/json

{"_id": "boo"} --:

CONTENT-TYPE: multipart/related; boundary=----:

----:

CONTENT-TYPE: application/json

{"_id": "baz"}

----:

CONTENT-TYPE: text/plain

CONTENT-DISPOSITION: attachment; filename=baz.txt

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

result[-1].append((await filepart.read()))

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.contentaiohttp.ClientResponse.content 都使

用流来接受数据,同时也是流形式的API属性。

class aiohttp.StreamReader

   流内容读取器

   除了已经存在

aiohttp.web.Request.contentaiohttp.ClientResponse.content 用 于读取原始内容的 StreamReader 实例,用户不要手动创建 StreamReader 实例

对象。

读取方法

coroutine StreamReader.read(n=-1)

   读取n字节内容,如果n没有提供或为-1,会一直读到EOF(无更多内容可读) 并返回全部所读字节。

   如果接受的是EOF并且内部缓存器没有内容,则返回空字节对象。

   参数:

      n(int) - 指定读取的字节数,-1则为全部读取。

   返回所读字节。

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 StreamReader.iter_chunked(n)

   指定了最大可读字节的数据块迭代器:

async for data in response.content.iter_chunked(1024):

print(data)

async-for StreamReader.iter_any()

   按顺序读取在流中的数据块:

async for data in response.content.iter_any():

print(data)

204

流式API参考

async-for StreamReader.iter_chunks()

   读取从服务器接收到的数据块:

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) - 要读的字节数。-1则是缓存器中的所有数据。

   返回所读字节。

205

流式API参考

StreamReader.unread_data(data)

   将所读的一些内容回滚到数据流中,数据会插入到缓存器的头部。

   参数: data (bytes) - 需要压入流中的数据。

警告

该方法不会唤醒正在等待的协同程序。 如对read()方法就不会奏效。

coroutine aiohttp.wait_eof()

   等待直到发现EOF。所给出的数据可以被接下来的读方法获取到。

206

其他帮助类

该文档介绍所有子模块—— 客户端 multipart协议 工具类 中要被加载 到aiohttp命名空间的名称信息。

WebSocket 工具类

class aiohttp.WSCloseCode

   一个保留关闭消息码的整数枚举类。

   OK

       正常结束,表示目标连接已经成功建立。

   GOING_AWAY

       表示服务器正在关闭或浏览器已离开页面。

   PROTOCOL_ERROR

       表示由于协议错误引起的终止连接。

   UNSUPPORTED_DATA

       表示因接收到不能接受到的数据类型引起的终止连接。(比如只能接受文本 的端口却接受了二进制消息)

   INVALIED_TEXT

       表示因接受到的数据中含有不能理解的消息类型引起的终止连接。(如在文 本消息中出现非UTF-8编码的内容)

   POLICY_VIOLATION

       表示因接收到的信息违反规定引起的终止连接。如果没有合适的状态码会返 回通用状态码(比如 unsupported_datamessage_too_big )。

   MESSAGE_TOO_BIG

       表示因接受的消息(数量)太大引起的终止连接。

   MANDATORY_EXTENSION

       表示因客户端期望与服务器协商更多的扩展类型但服务器没用在响应消息中 回复此类内容引起的终止连接。扩展列表需要在Close帧中的/reason/部分显示。注 意该状态码不会被服务器端使用,因为此状态码已代表WebSocket握手失败。

   INTERNAL_ERROR

       表示服务器端因遇到一个期望之外的错误无法完成请求而引起的终止连接。

207

其他帮助类

   SERVICE_RESTART

       服务重启。客户端需要重新连接,如果确定重连需要等5-30S不等的时间。

   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

如何让中间件可以存储数据以便让web-handler使用? 如何并行地接收来自不同源的事件?

如何以编程的方式在服务器端关闭websocket? 如何从特定IP地址上发起请求?

如果是隐式循环要怎么用aiohttp的测试功能呢? API创建和废弃政策是什么?

如何让整个应用程序都使用gzip压缩?

web服务器汇总如何管理ClientSession

有没有提供像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.3LTSPython版本是3.4.3呢!),不过依赖于aiohttp的包应该考虑下这个 问题了,是只使用低版本aiohttp呢还是一起抛弃Python3.3呢。 在aiohttp v1.0.0时 我们就抛弃了Python 3.4.1转而要求3.4.2 +。原因是: loop.is_closed3.4.2才出 现。 最后,在如今的2016年夏这更不应该是个问题了,主流都已经是Python 3.5 啦。

如何让中间件可以存储数据以便让web- handler使用?

aiohttp.web.Requestaiohttp.web.Application 一样都支持字典接口 (dict)。 只需要将数据放到里面即可:

async def handler(request):

request['unique_key'] = data

请看 https://github.com/aio-libs/aiohttp_session 的代

码, 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压缩?

这是不可能的。选择在哪不用压缩和用哪种压缩方式是一个非常棘手的问题。

如果你需要全局压缩——写一个自定义中间件吧。或在NGINX中开启(:smirk: 你正 在部署反向代理是不是)。

web服务器中如何管理ClientSession

aiohttp.ClientSession在服务器的生命周期中只应该被创建一次,因为可以更好地利 用连接池。

Session在内部保存cookies。如果你不需要cookies使用aiohttp.DummyCookieJar 就行。如果你需要在不同的http调用中使用不同的cookies,但在逻辑链的处理时使 用的是同一个aiohttp.TCPConnector,那把own_connector设置为False

220

相关文献

0.21版本时的路由重构

合理性方面

部署方面

向后兼容方面 1.1中的新内容

YARL URL 编码

API

URL编码

子应用

Url倒推

应用冻结

走向2.x

客户端方面

分块

压缩

客户端连接器

ClientResponse.release

221

相关文献

客户端异常

客户端payload

其他杂项

服务器方面

ServerHttpProtocal和底层服务器的一些详情

应用方面

WebRequest WebResponse

RequestPayloadError

WSGI

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 服务器。

   http://gunicorn.org/

223

相关名词释义

IDNA

   应用程序中的国际化域名(IDNA)是为了编码互联网域名的一个行业标准,基 于特定的语言模板和字母,如阿拉伯语,汉语,西里尔文,泰米尔文,希伯来文或 以拉丁字母为基础的变音字符还有像法语一样的连字字符。这些写作系统经由电脑 编码成多字节Unicode字符。国际化的域名以ASCII的形式保存到域名系统中,使用

Punycode编码互转。

keep-alive

   一个可以让HTTP客户端和服务器间保持通讯的技术。一旦进行连接并发送完一 次响应后并不关闭连接,而是保持打开状态以使用同一个套接字发送下一次请求。

   此技术可以让通讯变得飞快,因为无需为每次请求都建立一遍连接。

nginx

   Nginx [engine x] 是一个HTTP和反向代理服务器,邮件代理服务器和通用

TCP/UDP代理服务器。

   https://nginx.org/en/

percent-encoding

   是一个在URL中编码信息的机制,当URL中有不合适的字符时一般会进行编 码。

requoting

   对不合理的字符进行百分号编码(percent-encoding)或编码后的字符进行解 码。

resource

224

相关名词释义

   一个映射HTTP路径的概念,每个资源(resource)相当于一个URI

   可以有一个独立的名称。

   基于不同的HTTP方法,会包含不同的路由。

route

   资源(resource)的一部分,资源(resource)的路径和HTTP方法组成路由 (route)。

web-handler

   用于返回HTTP响应的终端。

websocket

   是一个在同一个TCP连接上进行全双工通讯的协议。WebSocket协议由IETF

准化写入RFC 6455

yarl

   一个操作URL对象的库。

   https://pypi.python.org/pypi/yarl

225

外部资源包

有用的外部资源包汇集

第三方扩展包

官方支持的包

aiohttp 扩展

数据库相关

其他工具

很赞的第三方包

数据库相关

其他工具

使用aiohttp创建的其他类型的包。 基于aiohttp的网站。

226

贡献须知

贡献须知

贡献者说明

首先需要clone GitHub上的仓库: 打开链接,并位于右上角的点击“Fork”按钮。 :) 我 想应该所有人都会使用gitgithub~

之后要做的步骤很清晰:

1.clone这个GitHub仓库

2.进行修改。

3.确保所有代码测试通过。

4.CHANGES文件夹中添加一个说明文件(用于更新Changelog)。

5.提交到自己的aiohttp仓库。

6.发起一个关于你的修改的PR

注意

如果你的PR有很长历史记录或者很多提交,请在创建PR之前重新从主仓库创建一次,确 保它没有这些记录。

运行aiohttp测试组件的准备

我们建议你使用pyhton虚拟环境来运行我们的测试。 关于创建虚拟环境有以下几种 方法:

如果你喜欢使用virtualenv,可以这样用:

$ cd aiohttp

$virtualenv --python=`which python3` venv

$. venv/bin/activate

或者使用python 标准库中的venv:

227

贡献须知

$ cd aiohttp

$python3 -m venv venv

$. venv/bin/activate

还可以使用virtualenvwarapper:

$ cd aiohttp

$ mkvirtualenv --python=`which python3` aiohttp

还有其他可用的工具比如 pyvenv ,不过我们只是要让你知道: 需要创建一个 Python3虚拟环境并使用它。

弄完之后我们要安装开发所需的包:

$ pip install -r requirements/dev.txt

注意

如果你计划在测试组件中使用pdbipdb,执行:

```

$ py.test tests -s

```

使用命令禁止输出捕获。

恭喜,到这一步我们就准备好我们的测试组件了。

运行aiohttp测试组件

全部准备完之后,是时候运行了:

$ make test

228

贡献须知

第一次使用命令运行时会运行 flask8 工具(抱歉,我们不接受关于pep8pyflakes相关错误的PR)。 flake8 成功运行后,测试也会随之运行。 请稍微注 意下输出的语句。 任何额外的文本信息(打印的条款等)都会被删除。

测是覆盖

我们正尽可能地有一个好的测试覆盖率,请不要把它变得更糟。 运行下这个命令:

$ make cov

来加载测试组件并收集覆盖信息。一旦命令执行完成,会在最后一行显示如下输

: open file:///.../aiohttp/coverage/index.html 请看一下这个链接并确

保你的修改已被覆盖到。 aiohttp使用 codecov.io 来存储覆盖结果。 你可以去这 个页面查看: https://codecov.io/gh/aio-libs/aiohttp 其他详细信息。 强烈推荐浏览器 扩展 https://docs.codecov.io/docs/browser-extension ,仅在GitHub Pull Request上的"已修改文件"标签上就能做分析。

文档

我们鼓励完善文档。 发起一个关于修改文档的PR时请先运行下这个命令:

$ make doc

完成后会输出一个索引页面:

file:///.../aiohttp/docs/_build/html/index.html 请看一下,确保样式

没问题。

拼写检测

我们使用 pyenchantsphinxcontrib-spelling 检测文档中的拼写:

$ make doc-spelling

不幸的是,在MacOS X上会有些问题。 在Linux上运行拼写检测前要先安装下:

229

贡献须知

$ sudo apt-get install enchant

$ pip install sphinxcontrib-spelling

更新 修改日志

CHANGES.rst文件使用towncrier工具进行管理,所有重要的修改都要有一个条目。 要为新文件增加一个条目,首要需要创建一个关于你想怎么做的issue。一个PR本 身的功能也和这个一样,但有一个正经关于此修改的issue更好(举个例子,万一因 为代码质量问题驳回了PR...)。

一旦你有了一个issuePR,你会得到一个号码并在CHANGES/目录内以此issue的 号码和其扩展内容如 .removal, .feature, .bugfix, .doc 创建一个文件。比 如你的issuePR号码是1234,是关于修复bug的,这样就会创建一

CHANGES/1234.bugfixs 的文件。PR们可以创建多个类别的说明文件(比如, 你添加了一个新功能,并且要删除或不在赞成使用某个旧功能,那就创

CHANGES/NNNN.featureCHANGES/NNNN.removal )。同样地,如果一个

PR涉及到多个issues/PR你可以为它们每个都创建相同的内容,Towncrier会删除重 复的部分。

文件的内容使用 reStruredText 格式化内容,格式化后的内容会作为新文件条目 来使用。你不需要为issuePR添加关联,towncrier会自动为所有涉及到的issues 添加关联。

最后

做完这些之后,请在GitHub上发起PR,谢谢~

230