├── README.md ├── SREADME.md ├── SUMMARY.md └── aiohttp文档 ├── AbstractBaseClasses.md ├── ClientReference.md ├── ClientUsage.md ├── Contributing.md ├── Essays.md ├── Essays ├── MigrationTo2.0.md ├── RouterRefactoring.md └── What'sNewIn1.1.md ├── ExternalSources.md ├── ExternalSources └── Third-PartyLibraries.md ├── FrequentlyAskedQuestions.md ├── Glossary.md ├── Helper.md ├── Introduce.md ├── Logging.md ├── LowLevelServer.md ├── ServerDeployment.md ├── ServerReference.md ├── ServerTutorial.md ├── ServerUsage.md ├── StreamingAPI.md ├── Testing.md └── WorkWithMultipart.md /README.md: -------------------------------------------------------------------------------- 1 | # aiohttp 中文文档 2 | 3 | 自己试着翻译,如有不当的地方请随时反馈~。:wink: 4 | 5 | 如果你是在GitHub上浏览,可以移步至[GitBook](https://hubertroy.gitbooks.io/aiohttp-chinese-documentation/content/aiohttp%E6%96%87%E6%A1%A3/Introduce.html)或[Love2.io](https://love2.io/@hubertroy/doc/aiohttp-chinese-documentation/SREADME.md)中获得更好的阅读体验。 6 | 7 | ### 进度: 8 | 基本翻译完成~,可能有很多不合理的地方,如有看到方便的话请随时反馈交流~,谢谢~。 9 | 10 | ## 快速浏览目录: 11 | * 简介及快速上手 12 | * 客户端使用手册 13 | * 客户端参考手册 14 | * 服务端指南 15 | * 服务端使用手册 16 | * 服务端参考手册 17 | * 底层服务器搭建 18 | * 抽象基类参考 19 | * 使用Multipart 20 | * 流式API参考 21 | * 其他帮助API参考 22 | * 日志 23 | * 测试(待完善...) 24 | * 服务器部署 25 | * 常见问题汇总 26 | * 外部资源包 27 | * 参考文献(待完善...) 28 | * 贡献须知 29 | * 更新日志(待更新...) 30 | * 相关名词释义 31 | -------------------------------------------------------------------------------- /SREADME.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 这是我在学习过程中翻译出的第一本"书",带给和我一样英语方面有些欠缺又想要学习查阅一些aiohttp相关方面知识的小伙伴。 3 | 4 | 在阅读中如果发现任何错误/不合适的翻译/奇怪的语句/错误的代码都可以可以在[GitHub](https://github.com/HuberTRoy/aiohttp-chinese-documentation/issues)发起Issue,我会尽快更正~。 5 | 6 | 翻译时官方文档还是旧版,现在变成了新版,内容上95%都是一样的,新版的目录索引做的更好,我会尽快更新到最新版。 7 | 8 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | * [前言](SREADME.md) 3 | * [简介及快速开始](aiohttp文档/Introduce.md) 4 | * [客户端部分](Client.md) 5 | * [基本及进阶使用](aiohttp文档/ClientUsage.md) 6 | * [发起请求](aiohttp文档/ClientUsage.md#发起请求) 7 | * [JSON请求](aiohttp文档/ClientUsage.md#发起JSON请求) 8 | * [传递URL中的参数](aiohttp文档/ClientUsage.md#传递url中的参数) 9 | * [获取响应内容](aiohttp文档/ClientUsage.md#获取响应内容) 10 | * [获取二进制响应内容](aiohttp文档/ClientUsage.md#获取二进制响应内容) 11 | * [获取JSON响应内容](aiohttp文档/ClientUsage.md#获取JSON响应内容) 12 | * [获取流式响应内容](aiohttp文档/ClientUsage.md#获取流式响应内容) 13 | * [获取请求信息](aiohttp文档/ClientUsage.md#获取请求信息) 14 | * [自定义Headers](aiohttp文档/ClientUsage.md#自定义Headers) 15 | * [自定义Cookies](aiohttp文档/ClientUsage.md#自定义Cookies) 16 | * [发起更复杂的POST请求](aiohttp文档/ClientUsage.md#发起更复杂的POST请求) 17 | * [发送多部分编码文件(Multipart-Encoded)](aiohttp文档/ClientUsage.md#发送多部分编码文件Multipart-Encoded) 18 | * [流式上传](aiohttp文档/ClientUsage.md#流式上传) 19 | * [上传预压缩过的数据](aiohttp文档/ClientUsage.md#上传预压缩过的数据) 20 | * [持久连接(keep-alive), 连接池和cookies共享](aiohttp文档/ClientUsage.md#持久连接keep-alive-连接池和cookies共享) 21 | * [安全cookies](aiohttp文档/ClientUsage.md#安全cookies) 22 | * [使用虚假Cookies Jar](aiohttp文档/ClientUsage.md#使用虚假cookies-jar) 23 | * [使用连接器](aiohttp文档/ClientUsage.md#使用连接器) 24 | * [限制连接池的容量](aiohttp文档/ClientUsage.md#限制连接池的容量) 25 | * [为TCP-sockets添加SSL控制](aiohttp文档/ClientUsage.md#为tcp-sockets添加SSL控制) 26 | * [Unix域套接字相关](aiohttp文档/ClientUsage.md#Unix域套接字) 27 | * [代理支持](aiohttp文档/ClientUsage.md#代理支持) 28 | * [查看响应状态码](aiohttp文档/ClientUsage.md#查看响应状态码) 29 | * [获取响应头信息](aiohttp文档/ClientUsage.md#获取响应头信息) 30 | * [获取响应cookies](aiohttp文档/ClientUsage.md#获取响应cookies) 31 | * [获取响应历史](aiohttp文档/ClientUsage.md#获取响应历史) 32 | * [使用WebSockets](aiohttp文档/ClientUsage.md#使用WebSockets) 33 | * [设置超时](aiohttp文档/ClientUsage.md#设置超时) 34 | * [愉快地结束](aiohttp文档/ClientUsage.md#愉快地结束) 35 | * [客户端部分参考](aiohttp文档/ClientReference.md) 36 | * [服务端部分](Server.md) 37 | * [服务端快速习得指南](aiohttp文档/ServerTutorial.md) 38 | * [用aiohttp搭建一个投票系统](aiohttp文档/ServerTutorial.md#开始用aiohttp构建我们的第一个应用程序) 39 | * [创建应用程序](aiohttp文档/ServerTutorial.md#创建应用程序) 40 | * [创建视图](aiohttp文档/ServerTutorial.md#创建视图) 41 | * [使用配置文件](aiohttp文档/ServerTutorial.md#使用配置文件) 42 | * [构建数据库](aiohttp文档/ServerTutorial.md#构建数据库) 43 | * [准备工作](aiohttp文档/ServerTutorial.md#准备工作) 44 | * [数据库架构](aiohttp文档/ServerTutorial.md#数据库架构) 45 | * [创建连接引擎](aiohttp文档/ServerTutorial.md#创建连接引擎) 46 | * [关闭数据库](aiohttp文档/ServerTutorial.md#关闭数据库) 47 | * [使用模板](aiohttp文档/ServerTutorial.md#使用模板) 48 | * [静态文件](aiohttp文档/ServerTutorial.md#静态文件) 49 | * [使用中间件](aiohttp文档/ServerTutorial.md#使用中间件) 50 | * [服务端使用](aiohttp文档/ServerUsage.md) 51 | * [启动一个简单地Web服务器](aiohttp文档/ServerUsage.md#启动一个简单地web服务器) 52 | * [使用命令行接口(CLI)](aiohttp文档/ServerUsage.md#使用命令行接口cli) 53 | * [使用处理器](aiohttp文档/ServerUsage.md#使用处理器) 54 | * [使用资源和路由](aiohttp文档/ServerUsage.md#使用资源和路由) 55 | * [使用可变形(动态)资源](aiohttp文档/ServerUsage.md#使用可变形资源) 56 | * [使用命名资源反向引用URL](aiohttp文档/ServerUsage.md#使用命名资源反向引用URL) 57 | * [将处理器放到类中使用](aiohttp文档/ServerUsage.md#将处理器放到类中使用) 58 | * [基础视图类](aiohttp文档/ServerUsage.md#基础视图类) 59 | * [资源视图](aiohttp文档/ServerUsage.md#资源视图) 60 | * [其他注册路由的方法](aiohttp文档/ServerUsage.md#其他注册路由的方式) 61 | * [Web处理器中的取消操作](aiohttp文档/ServerUsage.md#web处理器中的取消操作) 62 | * [自定义路由准则](aiohttp文档/ServerUsage.md#自定义路由准则) 63 | * [静态文件的处理](aiohttp文档/ServerUsage.md#静态文件的处理) 64 | * [模板](aiohttp文档/ServerUsage.md#模板) 65 | * [返回JSON响应](aiohttp文档/ServerUsage.md#返回JSON响应) 66 | * [处理用户会话](aiohttp文档/ServerUsage.md#处理用户会话) 67 | * [处理HTTP表单](aiohttp文档/ServerUsage.md#处理HTTP表单) 68 | * [文件上传](aiohttp文档/ServerUsage.md#文件上传) 69 | * [使用WebSockets](aiohttp文档/ServerUsage.md#使用websockets) 70 | * [异常](aiohttp文档/ServerUsage.md#异常) 71 | * [数据共享](aiohttp文档/ServerUsage.md#数据共享) 72 | * [中间件](aiohttp文档/ServerUsage.md#中间件) 73 | * [例子](aiohttp文档/ServerUsage.md#例子) 74 | * [旧式中间件](aiohttp文档/ServerUsage.md#旧式中间件) 75 | * [信号](aiohttp文档/ServerUsage.md#信号) 76 | * [嵌套应用](aiohttp文档/ServerUsage.md#嵌套应用) 77 | * [流控制](aiohttp文档/ServerUsage.md#流控制) 78 | * [使用Except头](aiohttp文档/ServerUsage.md#使用Except头) 79 | * [部署自定义资源](aiohttp文档/ServerUsage.md#部署自定义资源) 80 | * [优雅地关闭](aiohttp文档/ServerUsage.md#优雅地关闭) 81 | * [后台任务](aiohttp文档/ServerUsage.md#后台任务) 82 | * [处理错误页面](aiohttp文档/ServerUsage.md#处理错误页面) 83 | * [基于代理部署服务器](aiohttp文档/ServerUsage.md#基于代理部署服务器) 84 | * [Swagger支持](aiohttp文档/ServerUsage.md#Swagger支持) 85 | * [CORS支持](aiohttp文档/ServerUsage.md#CORS支持) 86 | * [调试工具箱](aiohttp文档/ServerUsage.md#调试工具箱) 87 | * [开发工具箱](aiohttp文档/ServerUsage.md#开发工具箱) 88 | * [底层服务器搭建](aiohttp文档/LowLevelServer.md) 89 | * [服务器端参考](aiohttp文档/ServerReference.md) 90 | * [日志](aiohttp文档/Logging.md) 91 | * [测试](aiohttp文档/Testing.md) 92 | * [服务器部署](aiohttp文档/ServerDeployment.md) 93 | * [其他工具包](Utilities.md) 94 | * [抽象基类参考](aiohttp文档/AbstractBaseClasses.md) 95 | * [抽象路由类](aiohttp文档/AbstractBaseClasses.md#抽象路由类) 96 | * [抽象类基础视图](aiohttp文档/AbstractBaseClasses.md#抽象类基础视图) 97 | * [抽象Cookies Jar类](aiohttp文档/AbstractBaseClasses.md#抽象cookies-jar类) 98 | * [抽象访问日志](aiohttp文档/AbstractBaseClasses.md#抽象访问日志) 99 | * [使用Mulitipart](aiohttp文档/WorkWithMultipart.md) 100 | * [读取Multipart响应](aiohttp文档/WorkWithMultipart.md#读取multipart响应) 101 | * [发送Multipart请求](aiohttp文档/WorkWithMultipart.md#发送multipart请求) 102 | * [Multipart使用技巧](aiohttp文档/WorkWithMultipart.md#mulitipart使用技巧) 103 | * [流式API参考](aiohttp文档/StreamingAPI.md#) 104 | * [读取方法](aiohttp文档/StreamingAPI.md#读取方法) 105 | * [异步迭代](aiohttp文档/StreamingAPI.md#异步迭代) 106 | * [其他帮助信息](aiohttp文档/StreamingAPI.md#其他帮助信息) 107 | * [其他帮助类](aiohttp文档/Helper.md) 108 | * [WebSocket 工具类](aiohttp文档/Helper.md#websocket-工具类) 109 | * [信号](aiohttp文档/Helper.md#信号) 110 | * [常见问题汇总](aiohttp文档/FrequentlyAskedQuestions.md) 111 | * [有没有提供像flask一样的approute装饰器的计划](aiohttp文档/FrequentlyAskedQuestions.md#有没有提供像flask一样的approute装饰器的计划) 112 | * [aiohttp有没有Flask中的蓝图或Django中的App的概念呢?](aiohttp文档/FrequentlyAskedQuestions.md#aiohttp有没有flask中的蓝图或django中的app的概念呢) 113 | * [如何创建一个可以缓存url的给定前缀的路由?](aiohttp文档/FrequentlyAskedQuestions.md#如何创建一个可以缓存url的给定前缀的路由) 114 | * [我要把数据库连接放在哪才可以让处理器访问到它?](aiohttp文档/FrequentlyAskedQuestions.md#我要把数据库连接放在哪才可以让处理器访问到它) 115 | * [为什么最低版本只支持到Python 3.4.2?](aiohttp文档/FrequentlyAskedQuestions.md#为什么最低版本只支持到python-342) 116 | * [如何让中间件可以存储数据以便让web-handler使用?](aiohttp文档/FrequentlyAskedQuestions.md#如何让中间件可以存储数据以便让web-handler使用) 117 | * [如何并行地接收来自不同源的事件?](aiohttp文档/FrequentlyAskedQuestions.md#如何并行地接收来自不同源的事件) 118 | * [如何以编程的方式在服务器端关闭websocket?](aiohttp文档/FrequentlyAskedQuestions.md#如何以编程的方式在服务器端关闭websocket) 119 | * [如何从特定IP地址上发起请求?](aiohttp文档/FrequentlyAskedQuestions.md#如何从特定ip地址上发起请求) 120 | * [如果是隐式循环要怎么用aiohttp的测试功能呢?](aiohttp文档/FrequentlyAskedQuestions.md#如果是隐式循环要怎么用aiohttp的测试功能呢) 121 | * [API创建和废弃政策是什么?](aiohttp文档/FrequentlyAskedQuestions.md#api创建和废弃政策是什么) 122 | * [如何让整个应用程序都使用gzip压缩?](aiohttp文档/FrequentlyAskedQuestions.md#如何让整个应用程序都使用gzip压缩) 123 | * [在web服务器汇总如何管理ClientSession?](aiohttp文档/FrequentlyAskedQuestions.md#在web服务器中如何管理clientsession) 124 | * [各种杂项](Miscellaneous.md) 125 | * [相关文献](aiohttp文档/Essays.md) 126 | * [相关名词释义](aiohttp文档/Glossary.md) 127 | * [外部资源包](aiohttp文档/ExternalSources.md) 128 | * [贡献须知](aiohttp文档/Contributing.md) -------------------------------------------------------------------------------- /aiohttp文档/AbstractBaseClasses.md: -------------------------------------------------------------------------------- 1 | # 抽象基类 2 | 3 | ## 抽象路由类 4 | 5 | `aiohttp`使用抽象类来管理web接口。 6 | `aiohttp.web`中大部分类都不打算是可以继承的,只有几个是可以继承的。 7 | `aiohttp.web`建立在这几个概念之上: 应用(application),路由(router),请求(request)和响应(response)。 8 | 路由(router)是一个可插拔的部分: 用户可以从头开始创建一个新的路由库,不过其他的部分必须与这个新路由无缝契合才行。 9 | AbstractRouter只有一个必须(覆盖)的方法: `AbstractRouter.resolve()`协程方法。同时必须返回`AbstractMatchInfo`实例对象。 10 | 如果所请求的URL的处理器发现`AbstractMatchInfo.handler()`所返回的是web处理器,那`AbstractMatchInfo.http_exception`则为None。 11 | 否则的话`AbstractMatchInfo.http_exceptio`n会是一个`HTTPException`实例,如`404: NotFound` 或 `405: Method Not Allowed`。如果调用`AbstractMatchInfo.handler()`则会抛出`http_exception`。 12 | 13 | *class aiohttp.abc.AbstractRouter* 14 |    此类是一个抽象路由类,可以指定`aiohttp.web.Application`中的router参数来传入此类的实例,使用aiohttp.web.Application.router来查看返回. 15 |    *coroutine **resolve**(request)* 16 |       URL处理方法。该方法是一个抽象方法,需要被继承的类所覆盖。 17 |       **参数:** 18 |          request - 用于处理请求的`aiohttp.web.Request`实例。在处理时`aiohttp.web.Request.match_info`会被设置为None。 19 |       **返回:** 20 |          返回`AbstractMathcInfo`实例对象。 21 | 22 | 23 | *class aiohttp.abc.AbstractMatchInfo* 24 |    抽象的匹配信息,由`AbstractRouter.resolve()`返回。 25 |    **http_exception** 26 |       如果没有匹配到信息则是`aiohttp.web.HTTPException`否则是None。 27 |    *coroutine **handler**(request)* 28 |       执行web处理器程序的抽象方法。 29 |       **参数:** 30 |          request - 用于处理请求的`aiohttp.web.Request`实例。在处理时`aiohttp.web.Request.match_info`会被设置为None。 31 |       **返回:** 32 |          返回`aiohttp.web.StreamResponse`或其派生类。 33 |       **可能会抛出的异常:** 34 |          所抛出的异常类型是`aiohttp.web.HTTPException`。 35 |    *coroutine expect_handler(request)* 36 |       用户处理100-continue的抽象方法。 37 | 38 | ## 抽象类基础视图 39 | `aiohttp`提供抽象类 `AbstractView`来对基于类的视图进行支持,而且还是一个`awaitable`对象(可以应用在`await Cls()`或`yield from Cls()`中,同时还有一个`request`属性。) 40 | 41 | *class aiohttp.AbstarctView* 42 |     一个用于部署所有基于类的视图的基础类。 43 |     \_\_iter\_\_和\_\_await\_\_方法需要被覆盖。 44 |     **request** 45 |       用于处理请求的`aiohttp.web.Request`实例。 46 | 47 | ## 抽象Cookies Jar类 48 | 49 | *class aiohttp.abc.AbstractCookieJar* 50 |    此类所生成的cookie jar实例对象可以作为`ClientSession.cookie_jar`所需要的对象。 51 |    jar中包含用于存储内部cookie数据的Morsel组件。 52 |    提供一个统计所存储的cookies的API: 53 | ``` 54 | len(session.cookie_jar) 55 | ``` 56 |    jar也可以被迭代: 57 | ``` 58 | for cookie in session.cookie_jar: 59 | print(cookie.key) 60 | print(cookie["domain"]) 61 | ``` 62 |    此类用于存储cookies,同时提供**collection.abc.Iterable**和**collections.abc.Sized**的功能。 63 |    *update_cookies(cookies, response_url=None)* 64 |       更新由服务器返回的Set-Cookie头所设置的cookies。 65 |       **参数:** 66 |          cookies - 接受`collection.abc.Mapping`对象(如dict, SimpleCookie等)或由服务器响应返回的可迭代cookies键值对。 67 |          response_url(str) - 响应该cookies的URL,如果设置为None则该cookies为共享cookies。标准的cookies应该带有服务器URL,只在请求此服务器时发送,共享的话则会向全部的客户端请求都发送。 68 |    *filter_cookies(request_url)* 69 |       返回jar中可以被此URL接受的cookies和可发送给给定URL的客户端请求的Cookies 头。 70 |       **参数:** 71 |          request_url (str) - 想要查询cookies的请求URL。 72 |       **返回:** 73 |          返回包含过滤后的cookies的`http.cookies.SimpleCookie`对象。 74 | 75 | ## 抽象访问日志 76 | *class aiohttp.abc.AbstractAccessLogger* 77 |     所有`RequestHandler`中实现`access_logger`的基础类。 78 |     log方法需要被覆盖。 79 |    *log(request, response, time)* 80 |       **参数:** 81 |          request - `aiohttp.web.Request` 对象。 82 |          reponse - `aiohttp.web.Response` 对象。 83 |          time (float) - 响应该请求的时间戳。 -------------------------------------------------------------------------------- /aiohttp文档/ClientReference.md: -------------------------------------------------------------------------------- 1 | ## 客户端会话(Client Session): 2 | 客户端会话(Client Session)是比较推荐使用的发起HTTP请求的接口。 3 | 4 | 会话(Session)封装有一个连接池(连接器实例),默认支持持久连接。除非你要连接非常多不同的服务器,否则建议你在你的应用程序中只使用一个会话(Session),这样有利于连接池. 5 | 6 | 使用案例: 7 | ``` 8 | import aiohttp 9 | import asyncio 10 | 11 | async def fetch(client): 12 | async with client.get('http://python.org') as resp: 13 | assert resp.status == 200 14 | return await resp.text() 15 | 16 | async def main(): 17 | async with aiohttp.ClientSession() as client: 18 | html = await fetch(client) 19 | print(html) 20 | 21 | loop = asyncio.get_event_loop() 22 | loop.run_until_complete(main()) 23 | ``` 24 | *新增于0.17版本。* 25 | 26 | 客户端会话(ClientSession)支持使用上下文管理器在结束时自动关闭。 27 | 28 | *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)* 29 | 30 |    该类用于创建客户端会话以及发起请求。 31 | 32 |     **(参数)Parameters**: 33 | * connector (aiohttp.connector.BaseConnector) – 基础连接器(BaseConnector)的子类实例,用于支持连接池。 34 | * loop – 35 | 事件循环(event loop)用于处理HTTP请求。 36 | 如果loop为None,则从connector中获取(如果也有的话)。 37 | 一般用asyncio.get_event_loop()来获取事件循环。 38 | 自 2.0版本后已被弃用。(2.0版本发布日是2017/3/20,之前的文档内写明遭到弃用的还是会保留一年半。) 39 | * cookies (dict) – 发送请求时所携带的cookies(可选)。 40 | * headers – 41 | 所有请求发送时携带的HTTP头(可选)。 42 | 可以是任何可迭代的键值对(key-value)对象或Mapping.(例如: dict, CIMultiDict) 43 | * skip_auto_headers – 44 | 跳过会自动产生的headers值. 45 | 如果没有显式传递,那么aiohttp会自动生成诸如User-Agent或Content-Type。使用该参数可以指定跳过,但注意Content-Length是不能跳过的。 46 | * auth(aiohttp.BasicAuth) – 表示 HTTP基本认证的对象。 47 | * version – 48 | 使用的HTTP版本,默认是HTTP 1.1。 49 | 新增于0.21版本。 50 | * cookie_jar – 51 | Cookie jar ,AbstractCookieJar的实例。 52 | 默认情况下每个会话实例都有自己的私有cookie jar用于自动处理cookies,但用户也可使用自己的jar重新定义其行为。 53 | 在代理模式下不会处理cookies。 54 | 如果不需要cookie处理,可以传递一个aiohttp.helper.DummyCookieJar实例。 55 | 新增于0.22版本。 56 | * json_serialize (可调用对象) – 57 | 可调用的Json序列化对象,默认是json.dumps()函数。 58 | * raise_for_status (布尔类型) – 59 | 每完成一个响应就自动调用 ClientResponse.raise_for_status(), 默认是False. 60 | 新增于2.0版本。 61 | * read_timeout (浮点数) – 62 | 请求操作的超时时间。read_timeout是累计自所有的请求操作(请求,重定向,响应,数据处理)。默认情况下是 5\*60秒(5分钟)。传递None或0来禁用超时检测。 63 | * conn_timeout (浮点数) – 64 | 建立连接的超时时间(可选)。0或None则禁用超时检测。 65 | * connector_owner (布尔类型) – 66 | 会话关闭时一同关闭连接器实例。 67 | 传递False让构造器允许在多个会话间共享连接池,不过cookies等信息是不共享的。 68 | 新增于2.1版本。 69 | * auto_decompress (布尔类型) – 70 | 自动解压响应体。 71 | 新增于2.3版本。 72 | * trust_env(布尔类型) – 73 | 若该参数设置为True则从环境变量HTTP_PROXY / HTTPS_PROXY中获取代理信息。 74 | 新增于2.3版本。 75 | 76 |    **closed** 77 |       若会话已关闭则返回True,否则返回False。 78 |       该属性只读。 79 | 80 |    **connector** 81 |       返回**aiohttp.connector.BaseConnector**的派生实例对象,connector通常用于会话操作 82 |       该属性只读。 83 | 84 |    **cookie_jar** 85 |       返回会话中的cookies,是**AbstractCookieJar**的实例对象。 86 |       返回的对象可以用于访问和修改cookie jar中的内容。 87 |       该属性只读。 88 |       新增于1.0版本。 89 | 90 |    **requote_redirect_url** 91 |       aiohttp默认会重新编译传入的urls,如果服务器需要未编译的url。将其设置为False来禁用编译。 92 |       新增于2.1版本。 93 | ###     注意: 94 | >      设置后会影响这之后的所有请求。 95 | 96 |    **loop** 97 |       返回用于会话创建的循环(loop)实例对象。 98 |       该属性只读。 99 | 100 | *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)* 101 |     以异步方式执行HTTP请求,返回响应对象。 102 | 103 |    **Parameters**: 104 | * method (字符串) – HTTP方法,接受字符串。 105 | * url – 请求URL, 接受字符串或URL对象。 106 | * params – 107 | 可传入Maaping对象,有键值对的元组或字符串,会在发送新请求时作为查询字符串发送。不会对之后的重定向请求也传入查询字符串。(该参数可选) 108 | 允许传入的值参考: 109 | collections.abc.Mapping : dict, aiohttp.MultiDict 或 aiohttp.MultiDictProxy 110 | collections.abc.Iterable: tuple 或 list 111 | 已被url编码好的str(aiohttp不能对str进行url编码) 112 | * data – 放在请求体中的数据,接受字典(dcit), 字节或类文件对象。(该参数可选) 113 | * json – 任何可被json解码的python对象(该参数可选)。注: json不能与data参数同时使用。 114 | * headers (字典) – 发送请求时携带的HTTP头信息。(该参数可选) 115 | * skip_auto_headers – 跳过会自动生成的headers信息。 116 | aiohttp会自动生成诸如User-Agent或Content-Type的信息(当然如果没有指定的话)。使用skip_auto_headers参数来跳过。 117 | 接受str或istr。(该参数可选) 118 | * auth(aiohttp.BasicAuth) – 接受HTTP基本认证对象。(该参数可选) 119 | * allow_redirects(布尔类型) – 如果设为False,则不允许重定向。默认是True。(该参数可选) 120 | * compress (布尔类型) – 如果请求内容要进行deflate编码压缩可以设为True。如果设置了Content-Encoding和Content-Length则不要使用这个参数。默认是None。(该参数可选) 121 | * chunked (整数) – 允许使用分块编码传输。开发者想使用分块数据流时,用它就没错啦。如果是允许的,aiohttp将会设置为"Transfer-encoding:chunked",这时不要在设置Transfer-encoding和content-length这两个headers了。默认该参数为None。(该参数可选) 122 | * expect100(布尔类型) 服务器返回100时将等待响应(返回100表示服务器正在处理数据)。默认是False。(该参数可选) 123 | * read_until_eof (布尔类型) 如果响应不含Content-Length头信息将会一直读取响应内容直到无内容可读。默认是True。(该参数可选) 124 | * proxy – 代理URL,接受字符串或URL对象(该参数可选) 125 | * proxy_auth(aiohttp.BaicAuth) – 传入表示HTTP代理基础认证的对象。(该参数可选) 126 | * timeout(整数) – 覆盖会话的超时时间。 127 | * verify_ssl(布尔类型) – 对HTTPS请求验证SSL证书(默认是验证的)。如果某些网站证书无效的话也可禁用。 128 | 新增于2.3版本。 129 | * fringerprint (字节) – 传递想要验证的证书(使用DER编码的证书)的SHA256值来验证服务器是否可以成功匹配。对证书固定非常有用。 130 | 警告: 不赞成使用不安全的MD5和SHA1哈希值。 131 | 新增于2.3版本。 132 | * ssl_context (ssl.SLLContext) – 133 | ssl上下文(管理器)用于处理HTTPS请求。(该参数可选) 134 | ssl_context 用于配置证书授权通道,支持SSL选项等作用。 135 | 新增于2.3版本。 136 | * proxy_headers(abc.Mapping) – 如果该参数有提供内容,则会将其做为HTTP headers发送给代理。 137 | 新增于2.3版本。 138 | 139 |    **返回ClientResponse**: 140 |       返回一个**客户端响应(client response)**对象。 141 |       1.0版本新增的内容: 增加`proxy`和`proxy_auth`参数。添加`timeout`参数。 142 |       1.1版本修改的内容: URLs可以是字符串和URL对象。 143 | 144 | *coroutine async-with **get**(url, \*, allow_redirects=True, \*\*kwargs)* 145 |    该方法会进行**GET**请求。 146 |    kwargs用于指定些request的参数。 147 |    **Parameters:** 148 | * url - 请求的URL, 字符串或URL对象。 149 | * allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True。 150 |    返回ClientResponse: 151 |       返回一个**客户端响应(client response)**对象。 152 |    1.1版本修改的内容: URLs可以是字符串和URL对象。 153 | 154 | *coroutine async-with post(url, \*, data=None, \*\*kwargs)* 155 |    该方法会执行POST请求。 156 |    kwargs用于指定些request的参数。 157 |    **Parameters:** 158 | * url - 请求的URL, 字符串或URL对象。 159 | * data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)。 160 | 161 |    **返回ClientResponse:** 162 |       返回一个**客户端响应(client response)**对象。 163 |       1.1版本修改的内容: URLs可以是字符串和URL对象。 164 | 165 | *coroutine async-with put(url, \*, data=None, \*\*kwargs)* 166 |    该方法会执行PUT请求。 167 |    kwargs用于指定些request的参数。 168 |    **Parameters:** 169 | * url - 请求的URL, 字符串或URL对象。 170 | * data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)。 171 | 172 |     **返回ClientResponse:** 173 |       返回一个**客户端响应(client response)**对象。 174 |       1.1版本修改的内容: URLs可以是字符串和URL对象。 175 | 176 | *coroutine async-with delete(url, \*\*kwargs)* 177 |    该方法会执行DELETE请求。 178 |    kwargs用于指定些request的参数。 179 |    **Parameters:** 180 | * url - 请求的URL, 字符串或URL对象。 181 | 182 |     **返回ClientResponse:** 183 |       返回一个**客户端响应(client response)**对象。 184 |       1.1版本修改的内容: URLs可以是字符串和URL对象。 185 | 186 | *coroutine async-with head(url, \*, allow_redirects=False, \*\*kwargs)* 187 |    该方法执行HEAD请求。 188 |    kwargs用于指定些request的参数。 189 |    **Parameters:** 190 | * url - 请求的URL, 字符串或URL对象。 191 | * allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True。 192 | 193 |     **返回ClientResponse:** 194 |       返回一个**客户端响应(client response)**对象。 195 |       1.1版本修改的内容: URLs可以是字符串和URL对象。 196 | 197 | *coroutine async-with options(url, \*, allow_redirects=True, \*\*kwargs)* 198 |    该方法执行options请求。 199 |    kwargs用于指定些request的参数。 200 |    **Parameters:** 201 | * url - 请求的URL, 字符串或URL对象。 202 | * allow_redirects (布尔类型) - 如果设为False,则不允许重定向。默认是True。 203 | 204 |    **返回ClientResponse:** 205 |       返回一个**客户端响应(client response)**对象。 206 |       1.1版本修改的内容: URLs可以是字符串和URL对象。 207 | 208 | *coroutine async-with patch(url, \*, data=None, \*\*kwargs)* 209 |    该方法执行patch请求。 210 |    kwargs用于指定些request的参数。 211 |    **Parameters:** 212 | * url - 请求的URL, 字符串或URL对象。 213 | * data - 接受字典,字节或类文件对象,会作为请求主体发送(可选)。 214 | 215 |    **返回ClientResponse:** 216 |       返回一个**客户端响应(client response)**对象。 217 |       1.1版本修改的内容: URLs可以是字符串和URL对象。 218 | 219 | *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)* 220 |    创建一个websocket连接。返回ClientWebSocketResponse对象。 221 | 222 |    **Parameters:** 223 | * url - Websocket服务器url, str或URL对象。 224 | * protocols(元组) - websocket 协议。 225 | * timeout(浮点数) - 超过超时时间后关闭websocket。默认是10秒。 226 | * receive_timeout(浮点数) - 超过超时时间后不在从websocket接受消息。默认是None(无限时间)。 227 | * auth (aiohttp.BasicAuth) - 表示HTTP基础认证的对象。(该参数可选) 228 | * autoclose(布尔类型)- 从服务器接受完消息后自动关闭websocket. 如果该参数为False,那么需要手动进行关闭。 229 | * autoping(布尔类型) - 当从服务器收到ping信息后自动回复pong信息。 230 | * heartbeat(浮点数) - 每到心跳时间则发送ping信息并等待pong信息想要,如果没有收到pong信息则关闭连接。 231 | * origin(字符串) - 发送到服务器的origin信息。 232 | * proxy(字符串) - 代理URL,接受字符串或URL对象(该参数可选)。 233 | * proxy_auth(aiohttp.BasicAuth) - 表示代理HTTP基础认证的对象。(该参数可选) 234 | * verify_ssl(布尔类型) - 对HTTPS请求验证SSL证书(默认是验证的)。如果某些网站证书无效的话也可禁用。(该参数可选) 新增于2.3版本。 235 | * fringerprint (字节) – 传递想要验证的证书(使用DER编码的证书)的SHA256值来验证服务器是否可以成功匹配。对证书固定非常有用。 236 | 警告: 不赞成使用不安全的MD5和SHA1哈希值。 237 | 新增于2.3版本。 238 | * ssl_context (ssl.SLLContext) – 239 | ssl上下文(管理器)用于处理HTTPS请求。(该参数可选) 240 | ssl_context 用于配置证书授权通道,支持SSL选项等作用。 241 | 新增于2.3版本。 242 | * proxy_headers(abc.Mapping) – 如果该参数有提供内容,则会将其做为HTTP headers发送给代理。 243 | 新增于2.3版本。 244 | * compress (整数) - 245 | `!此处有疑问!` 246 | 原文: 247 | ``` 248 | Enable Per-Message Compress Extension support. 249 | 0 for disable, 9 to 15 for window bit support. Default value is 0. 250 | ``` 251 | 允许支持(Per-Message Compress Extension). 252 | 0表示不使用,9-15表示支持的窗口位数。默认是0. 253 | ``` 254 | 由于没用过websocket这里不是很懂,从源码来看是一个headers中的信息。可自行查看https://tools.ietf.org/html/rfc7692#section-4 255 | ``` 256 | 新增于2.3版本。 257 | 258 |    *coroutine close()* 259 |       关闭底层连接器。 260 |       释放所有占用的资源。 261 | 262 |    detach() 263 |       从会话中分离出连接器但不关闭连接器。(之前说过每个会话拥有某连接器的所有权。) 264 |       会话会切换到关闭状态。 265 | 266 | # 基本API。 267 | 我们鼓励使用客户端会话(ClientSession)但同时也提供一个可以更简单的发起HTTP请求的协程方法。 268 | 基本API对于不需要持久连接(keepaliving), cookies和复杂的连接附件(如SSL证书)的HTTP请求来说是比较好用的。 269 | 270 | *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)* 271 |    异步的执行HTTP请求。返回一个响应对象(ClientResponse或其衍生对象) 272 | 273 |    **(参数)Parameters**: 274 | * method (字符串) - HTTP方法,接受字符串。 275 | * url - 请求URL, 接受str或URL对象。 276 | * params (字典) – 与原URL组合成带有查询条件的新URL。(该参数可选) 277 | * data – 放在请求体中的数据,接受字典(dcit), 字节或类文件对象。(该参数可选) 278 | * json – 任何可被json解码的python对象(改参数可选)。注: json不能与data参数同时使用。 279 | * headers (字典) – 发送请求时携带的HTTP头信息。(该参数可选) 280 | * cookies (字典) - 请求时携带的cookies。(该参数可选) 281 | * auth(aiohttp.BasicAuth) – 用于表示HTTP基础认证的对象。(该参数可选) 282 | * allow_redirects(布尔类型) – 如果设为False,则不允许重定向。默认是True。(该参数可选) 283 | * version (aiohttp.protocols.HttpVersion) - 请求时选用的HTTP版本。(该参数可选) 284 | * compress (布尔类型) – 如果请求内容要进行deflate编码压缩可以设为True。如果设置了Content-Encoding和Content-Length则不要使用这个参数。默认是None。(该参数可选) 285 | * chunked (整数) – 允许使用分块编码传输。开发者想使用分块数据流时,用它就没错啦。如果是允许的,aiohttp将会设置为"Transfer-encoding:chunked",这时不要在设置Transfer-encoding和content-length这两个headers了。默认该参数为None。(该参数可选) 286 | * expect100(布尔类型) 服务器返回100时将等待响应(返回100表示服务器正在处理数据)。默认是False。(该参数可选) 287 | * connector(aiohttp.connector.BaseConnector) - 接受BaseConnector的子类实例用于支持连接池。 288 | * read_until_eof (布尔类型) 如果响应不含Content-Length头信息将会一直读取响应内容直到无内容可读。默认是True。(该参数可选) 289 | * loop - 290 | 事件循环(event loop) 291 | 用于处理HTTP请求。如果参数为None,将会用asyncio.get_event_loop()获取。 292 | 2.0版本后不赞成使用。 293 | 294 |    **返回ClientResponse:** 295 |       返回一个**客户端响应**对象。 296 | 小例子: 297 | ``` 298 | import aiohttp 299 | 300 | async def fetch(): 301 | async with aiohttp.request('GET', 'http://python.org/') as resp: 302 | assert resp.status == 200 303 | print(await resp.text()) 304 | ``` 305 | 1.1版本修改的内容: URLs可以是字符串和URL对象。 306 | 307 | # 连接器 308 | 连接器用于支持aiohttp客户端API传输数据。 309 | 这俩是标准连接器: 310 | 311 | 1. TCPConnector 用于使用TCP连接(包括HTTP和HTTPS连接)。 312 | 2. UnixConnector 用于使用UNIX连接(大多数情况下都用于测试的目的)。 313 | 所有的连接器都应是BaseConnector的子类。 314 | 默认情况下所有连接器都支持持久连接(keep-alive)(该行为由force_close参数控制) 315 | 316 | # BaseConnector 317 | 318 | *class aiohttp.BaseConnector(\*, keepalive_timeout=15, force_close=False, limit=100, limit_per_host=0, enable_cleanup_closed=False, loop=None)* 319 |    所有连接器的基类。 320 | 321 |    **(参数)Parameters**: 322 | * keepalive_timeout(浮点数) - 释放后到再次使用的超时时间(可选)。禁用keep-alive可以传入0,或使用force_close=True。 323 | * limit(整数) - 并发连接的总数。如果为None则不做限制。(默认为100) 324 | * limit_per_host - 向同一个端点并发连接的总数。同一端点是具有相同 (host, port, is_ssl)信息的玩意 x 3! 如果是0则不做限制。(默认为0) 325 | * force_close(布尔类型) - 连接释放后关闭底层网络套接字。(该参数可选) 326 | * enable_cleanup_closed (布尔类型) - 一些SSL服务器可能会没有正确的完成SSL关闭过程,这种时候asyncio会泄露SSL连接。如果设置为True,aiohttp会在两秒后额外执行一次停止。此功能默认不开启。 327 | * loop - 328 | 事件循环(event loop) 329 | 用于处理HTTP请求。如果参数为None,将会用asyncio.get_event_loop()获取。 330 | 2.0版本后不赞成使用。 331 | 332 |    closed 333 |       只读属性,如果连接器关闭则返回True。 334 | 335 |    force_close 336 |       只读属性,如果连接释放后关闭网络套接字则返回True否则返回False。 337 |       新增于0.16版本。 338 | 339 |    limit 340 |       并发连接的总数。如果是0则不做限制。默认是100. 341 | 342 |    limit_per_host 343 |       向同一个端点并发连接的总数。同一端点是具有相同 (host, port, is_ssl)信息的玩意 x 3! 如果是None则不做限制。(默认为0) 344 |       只读属性。 345 | 346 |    close() 347 |       关闭所有打开的连接。 348 |       新增于2.0版本。 349 | 350 |    *coroutine connect(request)* 351 |       从连接池中获取一个空闲的连接,如果没有空闲则新建一个连接。 352 |       如果达到限制(limit)的最大上限则挂起直到有连接处于空闲为止。 353 | 354 |       **(参数)Parameters**: 355 |          request (aiohttp.client.ClientRequest) - 发起连接的请求对象。 356 | 357 |       **返回**: 358 |          返回连接(Connection)对象。 359 | 360 |    *coroutine _create_connection(req)* 361 |       建立实际连接的抽象方法,需被子类覆盖。 362 | 363 | # TCPConnector 364 | 365 | *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)* 366 |    用于使用TCP处理HTTP和HTTPS的连接器。 367 |    如果你不知道该用什么连接器传输数据,那就用它吧。 368 |    TCPConnector继承于BaseConnector. 369 |    接受BaseConnector所需的所有参数和几个额外的TCP需要的参数。 370 | 371 |    **(参数)Parameters**: 372 | * verify_ssl (布尔类型) – 373 | 对HTTPS请求验证SSL证书(默认是验证的)。如果某些网站证书无效的话也可禁用。(该参数可选) 374 | 2.3版本后不赞成通过ClientSession.get()方法传递该参数。 375 | 376 | * fingerprint (字节码) - 377 | 传递所期望的SHA256值(使用DER编码)来验证服务器是否可以成功匹配。对证书固定非常有用。 378 | 警告: 不赞成使用不安全的MD5和SHA1哈希值。 379 | 新增于0.16版本。 380 | 2.3版本后不赞成通过ClientSession.get()方法传递该参数。 381 | 382 | * use_dns_cache (布尔类型) - 383 | 使用内部缓存进行DNS查找,默认为True。 384 | 这个选项可能会加速建立连接的时间,有时也会些副作用。 385 | 新增于0.17版本。 386 | 自1.0版本起该参数默认为True。 387 | 388 | * ttl_dns_cache - 389 | 查询过的DNS条目的失效时间,None表示永不失效。默认是10秒。 390 | 默认情况下DNS会被永久缓存,一些环境中的一些HOST对应的IP地址会在特定时间后改变。可以使用这个参数来让DNS刷新。 391 | 新增于2.0.8版本。 392 | 393 | * limit (整数) - 并发连接的总数。如果为None则不做限制。(默认为100) 394 | 395 | * limit_per_host - 向同一个端点并发连接的总数。同一端点是具有相同 (host, port, is_ssl)信息的玩意 x 3! 如果是0则不做限制。(默认为0) 396 | 397 | * resolver (aiohttp.abc.AbstructResolver) - 传入自定义的解析器实例。默认是aiohttp.DefaultResolver(如果aiodns已经安装并且版本>1.1则是异步的)。 398 | 自定义解析器可以配置不同的解析域名的方法。 399 | 1.1版本修改的内容: 默认使用aiohttp.ThreadResolver, 异步版本在某些情况下会解析失败。 400 | 401 | * family (整数) - 402 | 代表TCP套接字成员,默认有IPv4和IPv6. 403 | IPv4使用的是socket.AF_INET, IPv6使用的是socket.AF_INET6. 404 | 0.18版本修改的内容: 默认是0,代表可接受IPv4和IPv6。可以传入socket.AF_INET或socket.AF_INET6来明确指定只接受某一种类型。 405 | 406 | * ssl_context (ssl.SSLContext) - 407 | ssl上下文(管理器)用于处理HTTPS请求。(该参数可选) 408 | ssl_context 用于配置证书授权通道,支持SSL选项等作用。 409 | 410 | * local_addr (元组) - 411 | 包含(local_host, local_post)的元组,用于绑定本地socket。 412 | 新增于0.21版本。 413 | 414 | * force_close (布尔类型) - 连接释放后关闭底层网络套接字。(该参数可选) 415 | 416 | * enable_cleanup_closed(元组)(这里原文应该写错了,应该是布尔类型,不管是之前的文档还是源码都是接受的布尔值。) - 417 | 一些SSL服务器可能会没有正确的完成SSL关闭过程,这种时候asyncio会泄露SSL连接。如果设置为True,aiohttp会在两秒后额外执行一次停止。此功能默认不开启。 418 | 419 |    verify_ssl 420 |       如果返回True则会进行ssl证书检测。 421 |       该属性只读。 422 | 423 |    ssl_context 424 |       返回用于https请求的ssl.SSLContext实例,该属性只读。 425 | 426 |    family 427 |       TCP套接字成员, 比如socket.AF_INET 或 socket.AF_INET6。 428 |       该属性只读。 429 | 430 |    dns_cache 431 |       如果DNS缓存可用的话返回True,否则返回False。 432 |       该属性只读。 433 |       新增于0.17版本。 434 | 435 |    cached_hosts 436 |       如果dns缓存可用,则返回已解析的域名缓存。 437 |       该属性只读,返回的类型为types.MappingProxyType。 438 |       新增于0.17版本。 439 | 440 |    fingerprint 441 |       返回传入的DER格式证书的MD5,SHA1或SHA256哈希值 ,如果没有的话会返回None. 442 |       该属性只读。 443 |       新增于0.16版本。 444 | 445 |    clear_dns_cache(self, host=None, port=None) 446 |       清除内部DNS缓存。 447 |       如果host和port指定了信息会删除指定的这个,否则清除所有的。 448 |       新增于0.17版本。 449 | 450 | # UnixConnector 451 | *class aiohttp.UnixConnector(path, *, conn_timeout=None, keepalive_timeout=30, limit=100, force_close=False, loop=None)* 452 |    Unix 套接字连接器。 453 |    使用UnixConnector发送HTTP/HTTPS请求。底层通过UNIX套接字传输。 454 |    UNIX套接字对于测试并快速在同一个主机上的进程间建立连接非常方便。 455 |    UnixConnector继承于BaseConnector。 456 | 457 |    使用: 458 | ``` 459 | conn = UnixConnector(path='/path/to/socket') 460 | session = ClientSession(connector=conn) 461 | async with session.get('http://python.org') as resp: 462 | ``` 463 |    接受所有BaseConnector的参数,还有一个额外的参数: 464 |    **(参数)Parameters**: path(字符串) - Unix套接字路径。 465 |     **path** 466 |       返回UNIX套接字路径,该属性只读。 467 | 468 | 469 | # Connection 470 | *class* aiohttp.Connection 471 |    连接器对象中封装的单个连接。 472 |    终端用户不要手动创建Connection实例,但可调用BaseConnector.connect()来获取Connection实例,这个方法是协程方法。 473 | 474 | * closed 475 | 只读属性,返回布尔值。如果该连接已关闭,释放或从某一连接器分离则返回Ture。 476 | 477 | * loop 478 | 返回处理连接的事件循环。 479 | 480 | * transport 481 | 返回该连接的传输通道。 482 | 483 |    close() 484 |       关闭连接并强制关闭底层套接字。 485 | 486 |    release() 487 |       从连接器中将该连接释放。 488 |       底层套接字并未关闭,如果超时时间(默认30秒)过后该连接仍可用则会被重新占用。 489 | 490 |    detach() 491 |       将底层套接字从连接中分离。 492 |       底层套接字并未关闭, 之后调用close()或release()也不会有空闲连接池对该套接字有其他操作。 493 | 494 | # 响应对象(Response object) 495 | 496 | *class aiohttp.ClientResponse* 497 |    ClientSession.requests() 及其同类成员的返回对象。 498 |    用户不要创建ClientResponse的实例,它是由调用API获得的返回。 499 | 500 |    ClientResponse支持async上下文管理器: 501 | 502 | ``` 503 | resp = await client_session.get(url) 504 | async with resp: 505 | assert resp.status == 200 506 | ``` 507 |    这样退出后会自动释放。(详情看release()协程方法) 508 |    此语法于0.18版本开始支持。 509 | 510 | * version 511 | 返回响应的版本信息,是HttpVersion实例对象。 512 | 513 | * status 514 | 返回响应的HTTP状态码(整数),比如: 200。 515 | 516 | * reason 517 | 返回响应的HTTP叙述(字符串),比如"OK"。 518 | 519 | * method 520 | 返回请求的HTTP方法(字符串)。 521 | 522 | * url 523 | 返回请求的URL(URL对象)。 524 | 525 | * connection 526 | 返回处理响应的连接。 527 | 528 | * content 529 | 包含响应主体(StreamReader)的载体流。支持多种读取方法。服务器使用分块传输编码时同样允许分块接受。 530 | 读取时可能会抛出aiohttp.ClientPayloadError,这种情况发生在响应对象在接受所有的数据前就关闭了或有任何传输编码相关的错误如错误的压缩数据所造成的不规则分块编码。 531 | * cookies 532 | 响应中的HTTP cookies,(由Set-Cookie HTTP头信息设置, 属于SimpleCookie) 533 | 534 | * headers 535 | 返回响应的HTTP头信息,是一个大小写不敏感的并联字典(CIMultiDictProxy)。 536 | * raw_headers 537 | 返回原始HTTP头信息,未经编码,格式是键值对形式。 538 | 539 | * content_type 540 | 返回Content-Type头信息的内容。该属性只读。 541 | 542 | > ###注意 543 | 根据RFC2616,如果没有Content-Type包含其中则它的值为'application/octet-stream'.可以用使用'CONTENT-TYPE' not in resp.headers(raw_headers)来弄清楚服务器的响应是否包含Content-type 544 | 545 | * charset 546 | 返回请求的主体的编码。 547 | 该值来自于Content-Type HTTP头信息。 548 | 返回的值是类似于'utf-8'之类的字符串,如果HTTP头中不含Content-Type或其中没有charset信息则是None。 549 | 550 | * history 551 | 返回包含所有的重定向请求(都是ClientResponse对象,最开始的请求在最前面)的序列,如果没用重定向则返回空序列。 552 | 553 |    **close()** 554 |       关闭响应和底层连接。 555 |       要关闭持久连接请看release(). 556 | 557 |    **coroutine read()** 558 |       以字节码形式读取所有响应内容。 559 |       如果读取数据时得到一个错误将会关闭底层连接,否则将会释放连接。 560 |       如果不可读则会抛出aiohttp.ClientResponseError错误。 561 |       返回响应内容的字节码。 562 |       参见: 563 |          close(), release(). 564 | 565 |    **coroutine release()** 566 |       一般不需要调用release。当客户端接受完信息时,底层连接将会自动返回到连接池中。如果载体中的内容没有全部读完,连接也会关闭。 567 | 568 | 569 |    **raise_for_status()** 570 |       如果响应的状态码是400或更高则会抛出aiohttp.ClientResponseError 571 |       如果小于400则什么都不会做。 572 | 573 |    **coroutine text(encoding=None)** 574 |       读取响应内容并返回解码后的信息。 575 |       如果encoding为None则会从Content-Type中获取,如果Content-Type中也没有会用chardet获取。 576 |       如果有cchardet会优先使用cchardet。 577 |       如果读取数据时得到一个错误将会关闭底层连接,否则将会释放连接。 578 |       **Parameters:** encoding(字符串) - 指定以该编码方式解码内容,None则自动获取编码方式(默认为None)。 579 |       **Return 字符串:** 解码后的内容。 580 | > ###注意 581 | 如果Content-Type中不含charset信息则会使用cchardet/chardet获取编码。 582 | 这两种方法都会拖慢执行效率。如果知道页面所使用的编码直接指定是比较好的做法: 583 | ``` 584 | await resp.text('ISO-8859-1') 585 | ``` 586 | 587 | **coroutine json(\*, encoding=None, loads=json.loads, content_type='application/json')** 588 |    以JSON格式读取响应内容,解码和解析器可由参数指定。如果数据不能read则会直接结束。 589 |    如果encoding为None,会使用cchardet或chardet获取编码。 590 |    如果响应中的Content-type不能与参数中的content_type的值相匹配则会抛出aiohttp.ContentTypeError错误。可传入None跳过此检查。 591 | 592 |    **Parameters:** 593 | * encoding (字符串) - 传入用于解码内容的编码名,或None自动获取。 594 | * loads (可调用对象) - 用于加载JSON数据,默认是json.loads. 595 | * content_type (字符串) - 传入字符串以查看响应中的content-type是否符合预期,如果不符合则抛出aiohttp.ContentTypeError错误。传入None可跳过该检测,默认是application/json 。 596 | * 597 |    **Returns:** 598 |       返回使用loads进行JSON编码后的数据,如果内容为空或内容只含空白则返回None。 599 | 600 |    **request_info** 601 |       存放有headers和请求URL的nametuple(一种方便存放数据的扩展类,存在于collections模块中。)属于aiohttp.RequestInfo实例。 602 | 603 | # ClientWebSocketResponse 604 |    使用协程方法aiohttp.ws_connect()或aiohttp.ClientSession.ws_connect()连接使用websocket的服务器,不要手动来创建ClientWebSocketResponse。 605 | 606 | *class aiohttp.ClientWebSocketResponse* 607 |     用于处理客户端websockets的类 608 | 609 |    **closed** 610 |       如果close()已经调用过或接受了CLOSE消息则返回True。 611 |       该属性只读。 612 | 613 |    **protocol** 614 |       调用start()后选择的websocket协议。 615 |       如果服务器和客户端所选协议不一致则是None。 616 | 617 |    **get_extra_info(name, default=None)** 618 |       返回从连接的transport中读取到的额外的信息。 619 | 620 |    **exception()** 621 |       如果有错误则返回那个错误,否则返回None。 622 | 623 |    **ping(message=b'')** 624 |       向服务器发送PING. 625 |       **Parameters:** message – 发送PING时携带的消息,类型是由UTF-8编码的字符串或字节码。(可选) 626 | 627 |    **coroutine send_str(data)** 628 |       向服务器发送文本消息。 629 |       Parameters: data (字符串) – 要发送的消息. 630 |       Raises: 如果不是字符串会抛出TypeError错误。 631 | 632 |    **coroutine send_bytes(data)** 633 |       向服务器发送二进制消息。 634 |       Parameters: data – 要发送的消息。 635 |       Raises: 如果不是字节码,字节数组或memoryview将抛出TypeError错误。 636 | 637 |    **coroutine send_json(data, \*, dumps=json.dumps)** 638 |       向服务器发送json字符串。 639 |       **Parameters:** 640 | * data – 要发送的消息. 641 | * dumps (可调用对象) –任何可接受某个对象并返回JSON字符串的可调用对象。默认是json.dumps() 642 | 643 |       **Raises:** 644 | * **RuntimeError** – 如果连接没有启动或已关闭会抛出这个错误。 645 | * **ValueError** – 如果数据不是可序列化的对象会抛出这个错误。 646 | * **TypeError** – 如果由dumps调用后返回的不是字符串会抛出这个错误。 647 | 648 |    **coroutine close(\*, code=1000, message=b'')** 649 |       用于向服务器发起挥手(CLOSE)信息,请求关闭连接。它会等待服务器响应。这是一个异步方法,所以如果要添加超时时间可以用asyncio.wait()或asyncio.wait_for()包裹。 650 | 651 |       **Parameters:** 652 | * code (整数) – 关闭状态码。 653 | * message – pong消息携带的信息,类型是由UTF-8编码的字符串或字节码。(可选) 654 | 655 |    **coroutine receive()** 656 |        等待服务器传回消息并在接受后将其返回。 657 |       此方法隐式的处理PING, PONG, CLOSE消息。(不会返回这些消息) 658 | 659 |       **Returns:** WSMessage 660 | 661 |    **coroutine receive_str()** 662 |       调用receive()并判断该消息是否是文本。 663 | 664 |       **Return 字符串:** 服务器传回的内容。 665 |       **Raises:** 如果消息是二进制则会抛出TypeError错误。 666 | 667 |    **coroutine receive_bytes()** 668 |       调用receive()并判断该消息是否是二进制内容。 669 | 670 |       **Return 字符串:** 服务器传回的内容。 671 |       **Raises:** 如果消息是文本则会抛出TypeError错误。 672 | 673 |    **coroutine receive_json(\*, loads=json.loads)** 674 |       调用receive_str()并尝试将JSON字符串转成Python中的dict(字典)。 675 | 676 |       **Parameters:** 677 | * loads (可调用对象) – 任何可接受字符串并返回字典的可调用对象。默认是json.loads() 678 | 679 |       **Return dict:** 680 |          返回处理过的JSON内容。 681 | 682 |       **Raises:** 683 |          如果消息是二进制则会抛出TypeError错误。 684 |          如果不是JSON消息则会抛出ValueError错误。 685 | 686 | # RequestInfo 687 | *class aiohttp.RequestInfo* 688 |    存放请求URL和头信息的namedtuple,使用ClientResponse.request_info属性可访问。 689 | 690 |    **url** 691 |       请求的URL,是yarl.URL实例对象。 692 | 693 |    **method** 694 |       请求时使用的HTTP方法,如'GET', 'POST',是个字符串。 695 | 696 |    **headers** 697 |       请求时携带的HTTP头信息,是multidict.CIMultiDict 实例对象。 698 | 699 | # BasicAuth 700 | *class aiohttp.BasicAuth(login, password='', encoding='latin1')* 701 |    用于协助进行HTTP基础认证的类。 702 | 703 |    **Parameters:** 704 | * login (字符串) - 用户名。 705 | * password (字符串) – 密码。 706 | * encoding (字符串) – 编码信息(默认是'latin1')。 707 | 一般在客户端 API中使用,比如给ClientSession.request()指定auth参数。 708 | 709 |    *classmethod decode(auth_header, encoding='latin1')* 710 |        解码HTTP基本认证信息。 711 | 712 |       **Parameters:** 713 | * auth_header (字符串) – 需要解码的 Authorization 头数据。 714 | * encoding (字符串) – (可选) 编码信息(默认是‘latin1’) 715 | 716 |       **Returns:** 717 |          返回解码后的认证信息。 718 | 719 |    *classmethod from_url(url)* 720 |        从URL中获取用户名和密码。 721 | 722 |       **Returns:** 返回BasicAuth,如果认证信息未提供则返回None。新增于2.3版本。 723 |    *encode()* 724 |       将认证信息编码为合适的 Authorization头数据。 725 | 726 |       **Returns:** 返回编码后的认证信息。 727 | 728 | # CookieJar 729 | *class aiohttp.CookieJar(\*, unsafe=False, loop=None)* 730 |    cookie jar实例对象可用在ClientSession.cookie_jar中。 731 |    jar对象包含用来存储内部cookie数据的Morsel组件。 732 |    可查看保存的cookies数量: 733 | ``` 734 | len(session.cookie_jar) 735 | ``` 736 |    也可以被迭代: 737 | ``` 738 | for cookie in session.cookie_jar: 739 | print(cookie.key) 740 | print(cookie["domain"]) 741 | ``` 742 |    该类提供collections.abc.Iterable, collections.abc.Sized 和 aiohttp.AbstractCookieJar中的方法接口。 743 |    提供符合RFC 6265规定的cookie存储。 744 |    **Parameters:** 745 | * unsafe (布尔类型) – (可选)是否可从IP(的HTTP请求)中接受cookies。 746 | * oop (布尔类型) – 一个事件循环实例。请看aiohttp.abc.AbstractCookieJar。2.0版本后不赞成使用。 747 | 748 | 749 | *update_cookies(cookies, response_url=None)* 750 |    从服务器返回的Set-Cookie信息中更新cookies。 751 | 752 |    **Parameters:** 753 | * cookies – 需要collections.abc.Mapping对象(如dict, SimpleCookie) 或包含cookies的可迭代键值对对象。 754 | * response_url (字符串) – cookies所属的URL,如果要共享cookies则不要填。标准的cookies应是与服务器URL成对出现,只在向该服务器请求时被发送出去,如果是共享的则会发送给所有的服务器。 755 | 756 |    **filter_cookies(request_url)** 757 |       返回与request_url相匹配的cookies,和能给这个URL携带的cookie(一般是设置为None也就是共享的cookie)。 758 | 759 |       **Parameters:** response_url (字符串) – 需要筛选的URL。 760 |       **Returns:** 返回带有给定URL cookies的http.cookies.SimpleCookie。 761 | 762 |    **save(file_path)** 763 |       以pickle形式将cookies信息写入指定路径。 764 | 765 |       **Parameters:** file_path – 要写入的路径。字符串或pathlib.Path实例对象。 766 | 767 |    **load(file_path)** 768 |       从给定路径读取被pickle的cookies信息。 769 | 770 |       **Parameters:** file_path – 要导入的路径, 字符串或pathlib.Path实例对象。 771 | 772 | *class aiohttp.DummyCookieJar(\*, loop=None)* 773 |    假人cookie jar,用于忽略cookie。 774 |    在一些情况下是很有用的: 比如写爬虫时不需要保存cookies信息,只要一直下载内容就好了。 775 |    将它的实例对象传入即可使用: 776 | ``` 777 | jar = aiohttp.DummyCookieJar() 778 | session = aiohttp.ClientSession(cookie_jar=DummyCookieJar()) 779 | ``` 780 | 781 | # Client exceptions 782 |    异常等级制度在2.0版本有较大修改。aiohttp只定义连接处理部分和服务器没有正确响应的异常。一些由开发者引起的错误将使用python标准异常如ValueError,TypeError。 783 |    读取响应内容可能会抛出ClientPayloadError异常。该异常表示载体编码时有些问题。比如有无效的压缩信息,不符合分块编码要求的分块内容或内容与content-length指定的大小不一致。 784 |    所有的异常都可当做aiohttp模块使用。 785 | 786 |    *exception aiohttp.ClientError* 787 |       所有客户端异常的基类。 788 |       继承于Exception。 789 | 790 |    *class aiohttp.ClientPayloadError* 791 |       该异常只会因读取响应内容时存在下列错误而抛出: 792 |        1. 存在无效压缩信息。 793 |        2. 错误的分块编码。 794 |        3. 与Conent-Length不匹配的内容。 795 |       继承于ClientError. 796 | 797 |    *exception aiohttp.InvalidURL* 798 |       不合理的URL会抛出此异常。比如不含域名部分的URL。 799 |       继承于ClientError和ValueError。 800 | 801 |       **url** 802 |          返回那个无效的URL, 是yarl.URL的实例对象。 803 | 804 | # Response errors 805 | 806 | *exception aiohttp.ClientResponseError* 807 |    这个异常有时会在我们从服务器得到响应后抛出。 808 |    继承于ClientError 809 | 810 |    **request_info** 811 |       该属性是RequestInfo实例对象,包含请求信息。 812 | 813 |    **code** 814 |       该属性是响应的HTTP状态码。如200. 815 | 816 |    **message** 817 |       该属性是响应消息。如"OK" 818 | 819 |    **headers** 820 |       该属性是响应头信息。数据类型是列表。 821 | 822 |    **history** 823 |       该属性存储失败的响应内容,如果该选项可用的话,否则是个空元组。 824 |       元组中的ClientResponse对象用于处理重定向响应。 825 | 826 | *class aiohttp.WSServerHandshakeError* 827 |    Web socket 服务器响应异常。 828 |    继承于ClientResponseError 829 | 830 | *class aiohttp.WSServerHandshakeError* 831 |    Web socket 服务器响应异常。 832 |    继承于ClientResponseError 833 | 834 | *class aiohttp.ContentTypeError* 835 |    无效的content type。 836 |    继承于ClientResponseError 837 |    新增于2.3版本。 838 | 839 | # Connection errors 840 | *class aiohttp.ClientConnectionError* 841 |    该类异常与底层连接的问题相关。 842 |    继承于ClientError。 843 | 844 | *class aiohttp.ClientOSError* 845 |    连接错误的子集,与OSError属同类异常。 846 |    继承于ClientConnectionError和OSError. 847 | 848 | *class aiohttp.ClientConnectorError* 849 |    连接器相关异常。 850 |    继承于ClientOSError 851 | 852 | *class aiohttp.ClientProxyConnectionError* 853 |    继承于ClientConnectonError。(由源码知继承于ClientConnectorError原文写错了。) 854 | 855 | *class aiohttp.ServerConnectionError* 856 |    继承于ClientConnectonError。(由源码知继承于ClientConnectionError原文写错了。) 857 | 858 | *class aiohttp.ClientSSLError* 859 |    继承于ClientConnectonError。(由源码知继承于ClientConnectorError原文写错了。) 860 | 861 | *class aiohttp.ClientConnectorSSLError* 862 |    用于响应ssl错误。 863 |    继承于ClientSSLError和ssl.SSLError 864 | 865 | *class aiohttp.ClientConnectorCertificateError* 866 |    用于响应证书错误。 867 |    继承于 ClientSSLError 和 ssl.CertificateError 868 | 869 | *class aiohttp.ServerDisconnectedError* 870 |    服务器无法连接时抛出的错误。 871 |    继承于ServerDisconnectionError。 872 | 873 |    **message** 874 |       包含部分已解析的HTTP消息。(可选) 875 | 876 | *class aiohttp.ServerTimeoutError* 877 |    进行服务器操作时超时,如 读取超时。 878 |    继承于ServerConnectionError 和 asyncio.TimeoutError。 879 | 880 | *class aiohttp.ServerFingerprintMismatch* 881 |    无法与服务器指纹相匹配时的错误。 882 |    继承于ServerDisconnectionError。 883 | 884 | 异常等级图: 885 | * ClientError 886 | * - ClientResponseError 887 | 888 | * - - ContentTypeError 889 | * - - WSServerHandshakeError 890 | * - - ClientHttpProxyError 891 | * - ClientConnectionError 892 | 893 | * - - ClientOSError 894 | 895 | * - - - ClientConnectorError 896 | 897 | * - - - - ClientSSLError 898 | * - - - - - ClientConnectorCertificateError 899 | * - - - - - ClientConnectorSSLError 900 | * - - - - ClientProxyConnectionError 901 | * - - - ServerConnectionError 902 | 903 | * - - - - ServerDisconnectedError 904 | * - - - - ServerTimeoutError 905 | * - - - ServerFingerprintMismatch 906 | 907 | * - ClientPayloadError 908 | 909 | * - InvalidURL -------------------------------------------------------------------------------- /aiohttp文档/ClientUsage.md: -------------------------------------------------------------------------------- 1 | # 客户端使用 2 | 3 | ## 发起请求 4 | 让我们从导入aiohttp模块开始: 5 | 6 | `import aiohttp` 7 | 8 | 好啦,我们来尝试获取一个web页面。比如我们来获取下GitHub的时间轴。 9 | ``` 10 | async with aiohttp.ClientSession() as session: 11 | async with session.get('https://api.github.com/events') as resp: 12 | print(resp.status) 13 | print(await resp.text()) 14 | ``` 15 | 我们现在有了一个`会话(session)`对象,由**ClientSession**对象赋值而来,还有一个变量`resp`,它其实是**ClientResponse**对象。我们可以从这个响应对象中获取我们任何想要的信息。协程方法`ClientSession.get()`的主要参数接受一个HTTP URL。 16 | 17 | 发起HTTP POST请求我们可以使用协程方法**ClientSession.post**(): 18 | ``` 19 | session.post('http://httpbin.org/post', data=b'data') 20 | ``` 21 | 其他的HTTP方法也同样支持: 22 | ``` 23 | session.put('http://httpbin.org/put', data=b'data') 24 | session.delete('http://httpbin.org/delete') 25 | session.head('http://httpbin.org/get') 26 | session.options('http://httpbin.org/get') 27 | session.patch('http://httpbin.org/patch', data=b'data') 28 | ``` 29 | > ### 注意: 30 | 不要为每个请求都创建一个会话。大多数情况下每个应用程序只需要一个会话就可以执行所有的请求。 31 | 每个会话对象都包含一个连接池,可复用的连接和持久连接状态(keep-alives,这两个是默认的)可提升总体的执行效率。 32 | 33 | ## 发起JSON请求: 34 | 每个会话的请求方法都可接受json参数。 35 | ``` 36 | async with aiohttp.ClientSession() as session: 37 | async with session.post(json={'test': 'object'}) 38 | ``` 39 | 默认情况下会话(session)使用Python标准库里的json模块解析json信息。但还可使用其他的json解析器。可以给ClientSession指定*json_serialize*参数来实现: 40 | ``` 41 | import ujson 42 | 43 | async with aiohttp.ClientSession(json_serialize=ujson.dumps) as session: 44 | async with session.post(json={'test': 'object'}) 45 | ``` 46 | 47 | ## 传递URL中的参数: 48 | 你可能经常想在URL中发送一系列的查询信息。如果你手动构建他们,这些信息会以键值对的形式出现在?后面,比如: `httpbin.org/get?key=val`。请求对象允许你使用**dict(字典,python中的数据类型)**发送它们,使用`params`参数即可。例如: 如果你要把 `key1=value1,key2=value2`放到`httpbin.org/get`后面,你可以用下面的方式: 49 | 50 | ``` 51 | params = {'key1': 'value1', 'key2': 'value2'} 52 | async with session.get('http://httpbin.org/get', 53 | params=params) as resp: 54 | assert str(resp.url) == 'http://httpbin.org/get?key2=value2&key1=value1' 55 | ``` 56 | 看,URL已经被正确的编码啦。 57 | 同键不同值的**并联字典(MultiDict) **也同样支持。 58 | 可使用带有两个tuples(元组,python中的数据类型)的list(列表,python中的数据类型)来构建: 59 | ``` 60 | params = [('key', 'value1'), ('key', 'value2')] 61 | async with session.get('http://httpbin.org/get', 62 | params=params) as r: 63 | assert str(r.url) == 'http://httpbin.org/get?key=value2&key=value1' 64 | ``` 65 | 同样也允许你传递**str**(字符串)给params,但要小心一些不能被编码的字符。`+`就是一个不能被编码的字符: 66 | ``` 67 | async with session.get('http://httpbin.org/get', 68 | params='key=value+1') as r: 69 | assert str(r.url) == 'http://httpbin.org/get?key=value+1' 70 | ``` 71 | > ### 注意: 72 | *aiohttp*会在发送请求前标准化URL。 73 | 域名部分会用IDNA 编码,路径和查询条件会重新编译(requoting)。 74 | 比如:`URL('http://example.com/путь%30?a=%31')` 会被转化为`URL('http://example.com/%D0%BF%D1%83%D1%82%D1%8C/0?a=1')` 75 | 如果服务器需要接受准确的表示并不要求编译URL,那标准化过程应是禁止的。 76 | 禁止标准化可以使用`encoded=True`: 77 | ``` 78 | await session.get(URL('http://example.com/%30', encoded=True)) 79 | ``` 80 | 81 | > ### 警告: 82 | 传递*params*时不要用`encode=True`,这俩参数不能同时使用。 83 | 84 | ## 获取响应内容 85 | 我们可以读取服务器的响应内容。想想我们获取GitHub时间轴的例子: 86 | ``` 87 | async with session.get('https://api.github.com/events') as resp: 88 | print(await resp.text()) 89 | ``` 90 | 这样会打印出类似于下面的信息: 91 | ``` 92 | '[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{... 93 | ``` 94 | `aiohttp`将会自动解码内容。你可以为**text**()方法指定编码(使用encoding参数): 95 | ``` 96 | await resp.text(encoding='windows-1251') 97 | ``` 98 | 99 | ## 获取二进制响应内容 100 | 你也可以以字节形式获取响应,这样得到的就不是文本了: 101 | ``` 102 | print(await resp.read()) 103 | b'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{... 104 | ``` 105 | `gzip`和`defalte`传输编码会自动解码。 106 | 你也可以使其支持`brotli`传输编码的解码,只需安装brotlipy即可。 107 | 108 | ## 获取JSON响应内容 109 | 以防你需要处理JSON数据,内置了一个JSON解码器: 110 | ``` 111 | async with session.get('https://api.github.com/events') as resp: 112 | print(await resp.json()) 113 | ``` 114 | 如果JSON解码失败,**json**()方法将会抛出一个异常。你还可以在调用**json**()时指定编码器和解码器函数。 115 | 116 | > ### 注意: 117 | 这些方法会读出内存中所有响应的内容。如果你要读非常多的数据,考虑使用流式响应方法进行读取。请看之后的文档。 118 | 119 | ## 获取流式响应内容 120 | **read**(), **json**(), **text**()等方法使用起来很方便,但也要注意谨慎地使用。上述方法会将所有的响应内容加载到内存。举个例子,如果你要下载几个G的文件,这些方法还是会将所有内容都加载到内存,内存会表示"臣妾做不到啊~"(如果内存不够的话)。作为代替你可以用**content**属性。content其实是 **aiohttp.StreamReader**类的实例。`gzip`和`deflate`传输编码同样会自动解码。 121 | 122 | ``` 123 | async with session.get('https://api.github.com/events') as resp: 124 | await resp.content.read(10) 125 | ``` 126 | 一般情况下你可以使用下列模式将内容保存在一个文件中: 127 | ``` 128 | with open(filename, 'wb') as fd: 129 | while True: 130 | chunk = await resp.content.read(chunk_size) 131 | if not chunk: 132 | break 133 | fd.write(chunk) 134 | ``` 135 | 在使用**content**读了数据后,就不要在用**read**(), **json**(), **text**()了。 136 | 137 | ## 获取请求信息 138 | *ClientResponse(客户端响应)*对象含有request_info(请求信息),主要是*url*和*headers*信息。 *raise_for_status*结构体上的信息会被复制给ClientResponseError实例。 139 | 140 | ## 自定义Headers 141 | 如果你需要给某个请求添加HTTP头,可以使用headers参数,传递一个**dict**对象即可。 142 | 比如,如果你想给之前的例子指定 content-type可以这样: 143 | ``` 144 | import json 145 | url = 'https://api.github.com/some/endpoint' 146 | payload = {'some': 'data'} 147 | headers = {'content-type': 'application/json'} 148 | 149 | await session.post(url, 150 | data=json.dumps(payload), 151 | headers=headers) 152 | ``` 153 | 154 | ## 自定义Cookies 155 | 发送你自己的cookies给服务器,你可以为**ClientSession**对象指定*cookies*参数: 156 | ``` 157 | url = 'http://httpbin.org/cookies' 158 | cookies = {'cookies_are': 'working'} 159 | async with ClientSession(cookies=cookies) as session: 160 | async with session.get(url) as resp: 161 | assert await resp.json() == { 162 | "cookies": {"cookies_are": "working"}} 163 | ``` 164 | > ### 注意: 165 | 访问`httpbin.org/cookies` 会看到以JSON形式返回的cookies。查阅会话中的cookies请看ClientSession.cookie_jar。 166 | 167 | ## 发起更复杂的POST请求 168 | 一般来说,如果你想以表单形式发送一些数据 - 就像HTML表单。那么只需要简单的将一个dict通过*data*参数传递就可以。传递的dict数据会自动编码: 169 | ``` 170 | payload = {'key1': 'value1', 'key2': 'value2'} 171 | async with session.post('http://httpbin.org/post', 172 | data=payload) as resp: 173 | print(await resp.text()) 174 | { 175 | ... 176 | "form": { 177 | "key2": "value2", 178 | "key1": "value1" 179 | }, 180 | ... 181 | } 182 | ``` 183 | 如果你想发送非表单形式的数据你可用`str(字符串)`代替`dict(字典)`。这些数据会直接发送出去。 184 | 例如,GitHub API v3 接受JSON编码POST/PATCH数据: 185 | ``` 186 | import json 187 | url = 'https://api.github.com/some/endpoint' 188 | payload = {'some': 'data'} 189 | 190 | async with session.post(url, data=json.dumps(payload)) as resp: 191 | ... 192 | ``` 193 | 194 | ## 发送多部分编码文件(Multipart-Encoded) 195 | 196 | 上传多部分编码文件: 197 | ``` 198 | url = 'http://httpbin.org/post' 199 | files = {'file': open('report.xls', 'rb')} 200 | 201 | await session.post(url, data=files) 202 | ``` 203 | 你也可以显式地设置文件名,文件类型: 204 | ``` 205 | url = 'http://httpbin.org/post' 206 | data = FormData() 207 | data.add_field('file', 208 | open('report.xls', 'rb'), 209 | filename='report.xls', 210 | content_type='application/vnd.ms-excel') 211 | 212 | await session.post(url, data=data) 213 | ``` 214 | 如果你把一个文件对象传递给data参数,aiohttp会自动将其以流的形式上传。查看**StreamReader**以获取支持的格式信息。 215 | 216 | > ### 参见: 217 | 使用Multipart. 218 | 219 | ## 流式上传 220 | **aiohttp** 支持多种形式的流式上传,允许你直接发送大文件而不必读到内存。 221 | 222 | 下面是个简单的例子,提供类文件对象即可: 223 | ``` 224 | with open('massive-body', 'rb') as f: 225 | await session.post('http://httpbin.org/post', data=f) 226 | ``` 227 | 或者你也可以使用*aiohttp.streamer*对象: 228 | ``` 229 | @aiohttp.streamer 230 | def file_sender(writer, file_name=None): 231 | with open(file_name, 'rb') as f: 232 | chunk = f.read(2**16) 233 | while chunk: 234 | yield from writer.write(chunk) 235 | chunk = f.read(2**16) 236 | 237 | # 之后你可以使用’file_sender‘传递给data: 238 | 239 | async with session.post('http://httpbin.org/post', 240 | data=file_sender(file_name='huge_file')) as resp: 241 | print(await resp.text()) 242 | ``` 243 | 同样可以使用**StreamReader**对象. 244 | 245 | 我们来看下如何把来自于另一个请求的内容作为文件上传并计算其SHA1值: 246 | ``` 247 | async def feed_stream(resp, stream): 248 | h = hashlib.sha256() 249 | 250 | while True: 251 | chunk = await resp.content.readany() 252 | if not chunk: 253 | break 254 | h.update(chunk) 255 | stream.feed_data(chunk) 256 | 257 | return h.hexdigest() 258 | 259 | resp = session.get('http://httpbin.org/post') 260 | stream = StreamReader() 261 | loop.create_task(session.post('http://httpbin.org/post', data=stream)) 262 | 263 | file_hash = await feed_stream(resp, stream) 264 | ``` 265 | 因为响应对象的content属性是一个`StreamReader`实例,所以你可以将get和post请求连在一起用: 266 | ``` 267 | r = await session.get('http://python.org') 268 | await session.post('http://httpbin.org/post', 269 | data=r.content) 270 | ``` 271 | 272 | ## 上传预压缩过的数据 273 | 上传一个已经压缩过的数据,需要为Headers中的`Content-Encoding`指定算法名(通常是deflate或者是zlib). 274 | ``` 275 | async def my_coroutine(session, headers, my_data): 276 | data = zlib.compress(my_data) 277 | headers = {'Content-Encoding': 'deflate'} 278 | async with session.post('http://httpbin.org/post', 279 | data=data, 280 | headers=headers) 281 | pass 282 | ``` 283 | 284 | ## 持久连接(keep-alive), 连接池和cookies共享 285 | **ClientSession**可以在多个请求之间共享cookies: 286 | ``` 287 | async with aiohttp.ClientSession() as session: 288 | await session.get( 289 | 'http://httpbin.org/cookies/set?my_cookie=my_value') 290 | filtered = session.cookie_jar.filter_cookies('http://httpbin.org') 291 | assert filtered['my_cookie'].value == 'my_value' 292 | async with session.get('http://httpbin.org/cookies') as r: 293 | json_body = await r.json() 294 | assert json_body['cookies']['my_cookie'] == 'my_value' 295 | ``` 296 | 你也可以为所有的会话请求设置headers: 297 | ``` 298 | async with aiohttp.ClientSession( 299 | headers={"Authorization": "Basic bG9naW46cGFzcw=="}) as session: 300 | async with session.get("http://httpbin.org/headers") as r: 301 | json_body = await r.json() 302 | assert json_body['headers']['Authorization'] == \ 303 | 'Basic bG9naW46cGFzcw==' 304 | ``` 305 | **ClientSession**支持持久连接和连接池,可直接使用,不需要额外操作。 306 | 307 | ## 安全cookies 308 | **ClientSession**中的默认的**aiohttp.CookiesJar**使用的是严苛模式,RFC 2109明确禁止使用ip地址形式的URL携带cookies信息。比如: *http://127.0.0.1:80/cookie* 309 | 这样很好,不过有些时候我们测试时需要允许携带cookies。在**aiohttp.CookiesJar**中传递*unsafe=True*来实现这一效果: 310 | 311 | ``` 312 | jar = aiohttp.CookieJar(unsafe=True) 313 | session = aiohttp.ClientSession(cookie_jar=jar) 314 | ``` 315 | 316 | ## 使用虚假Cookie Jar 317 | 有时不想处理cookie。这时可以在会话中使用**aiohttp.DummyCookieJar**来达到目的。 318 | ``` 319 | jar = aiohttp.DummyCookieJar() 320 | session = aiohttp.ClientSession(cookie_jar=jar) 321 | ``` 322 | 323 | ## 使用连接器 324 | 想要调整请求的传输层你可以为**ClientSession**及其同类组件传递自定义的连接器。例如: 325 | ``` 326 | conn = aiohttp.TCPConnector() 327 | session = aiohttp.ClientSession(connector=conn) 328 | ``` 329 | 330 | > ### 注解: 331 | 不要给多个会话对象使用同一个连接器,某一会话对象拥有其所有权。 332 | 333 | > ### 参见: 334 | 查看连接器部分了解更多不同的连接器类型和配置选项信息。 335 | 336 | ## 限制连接池的容量 337 | 限制同一时间打开的连接数可以传递limit参数: 338 | ``` 339 | conn = aiohttp.TCPConnector(limit=30) 340 | ``` 341 | 这样就将总数限制在30. 342 | 343 | 默认情况下是100. 344 | 345 | 如果你不想有限制,传递0即可: 346 | ``` 347 | conn = aiohttp.TCPConnector(limit=0) 348 | ``` 349 | 350 | 限制同一时间在同一个端点((`host`, `port`, `is_ssl`) 3者都一样的情况)打开的连接数可指定limit_per_host参数: 351 | ``` 352 | conn = aiohttp.TCPConnector(limit_per_host=30) 353 | ``` 354 | 这样会限制在30. 355 | 默认情况下是0(也就是不做限制)。 356 | 357 | ## 使用自定义域名服务器 358 | 底层需要aiodns支持: 359 | ``` 360 | from aiohttp.resolver import AsyncResolver 361 | 362 | resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"]) 363 | conn = aiohttp.TCPConnector(resolver=resolver) 364 | ``` 365 | 366 | ## 为TCP sockets添加SSL控制: 367 | 默认情况下aiohttp总会对使用了HTTPS协议(的URL请求)查验其身份。但也可将*verify_ssl*设置为`False`让其不检查: 368 | ``` 369 | r = await session.get('https://example.com', verify_ssl=False) 370 | ``` 371 | 如果你需要设置自定义SSL信息(比如使用自己的证书文件)你可以创建一个**ssl.SSLContext**实例并传递到**ClientSession**中: 372 | ``` 373 | sslcontext = ssl.create_default_context( 374 | cafile='/path/to/ca-bundle.crt') 375 | r = await session.get('https://example.com', ssl_context=sslcontext) 376 | ``` 377 | 如果你要验证*自签名*的证书,你也可以用之前的例子做同样的事,但是用的是load_cert_chain(): 378 | ``` 379 | sslcontext = ssl.create_default_context( 380 | cafile='/path/to/ca-bundle.crt') 381 | sslcontext.load_cert_chain('/path/to/client/public/device.pem', 382 | '/path/to/client/private/device.jey') 383 | r = await session.get('https://example.com', ssl_context=sslcontext) 384 | ``` 385 | SSL验证失败时抛出的错误: 386 | 387 | **aiohttp.ClientConnectorSSLError**: 388 | ``` 389 | try: 390 | await session.get('https://expired.badssl.com/') 391 | except aiohttp.ClientConnectorSSLError as e: 392 | assert isinstance(e, ssl.SSLError) 393 | ``` 394 | **aiohttp.ClientConnectorCertificateError**: 395 | ``` 396 | try: 397 | await session.get('https://wrong.host.badssl.com/') 398 | except aiohttp.ClientConnectorCertificateError as e: 399 | assert isinstance(e, ssl.CertificateError) 400 | ``` 401 | 如果你需要忽略所有SSL的错误: 402 | 403 | **aiohttp.ClientSSLError**: 404 | ``` 405 | try: 406 | await session.get('https://expired.badssl.com/') 407 | except aiohttp.ClientSSLError as e: 408 | assert isinstance(e, ssl.SSLError) 409 | 410 | try: 411 | await session.get('https://wrong.host.badssl.com/') 412 | except aiohttp.ClientSSLError as e: 413 | assert isinstance(e, ssl.CertificateError) 414 | ``` 415 | 你还可以通过*SHA256*指纹验证证书: 416 | ``` 417 | # Attempt to connect to https://www.python.org 418 | # with a pin to a bogus certificate: 419 | bad_fingerprint = b'0'*64 420 | exc = None 421 | try: 422 | r = await session.get('https://www.python.org', 423 | fingerprint=bad_fingerprint) 424 | except aiohttp.FingerprintMismatch as e: 425 | exc = e 426 | assert exc is not None 427 | assert exc.expected == bad_fingerprint 428 | 429 | # www.python.org cert's actual fingerprint 430 | assert exc.got == b'...' 431 | ``` 432 | 注意这是以DER编码的证书的指纹。如果你的证书是PEM编码,你需要转换成DER格式: 433 | ``` 434 | openssl x509 -in crt.pem -inform PEM -outform DER > crt.der 435 | ``` 436 | > ### 注解: 437 | 提示: 从16进制数字转换成二进制字节码,你可以用**binascii.unhexlify**(). 438 | 439 | **TCPConnector**中设置的*verify_ssl, fingerprint和ssl_context*都会被当做默认的verify_ssl, fingerprint和ssl_context,**ClientSession**或其他同类组件中的设置会覆盖默认值。 440 | 441 | > ### 警告: 442 | *verify_ssl 和 ssl_context*是*互斥*的。 443 | *MD5*和*SHA1*指纹虽不赞成使用但是是支持的 - 这俩是非常不安全的哈希函数。 444 | 445 | ## Unix 域套接字 446 | 如果你的服务器使用UNIX域套接字你可以用**UnixConnector**: 447 | 448 | ``` 449 | conn = aiohttp.UnixConnector(path='/path/to/socket') 450 | session = aiohttp.ClientSession(connector=conn) 451 | ``` 452 | 453 | ## 代理支持 454 | aiohttp 支持 HTTP/HTTPS形式的代理。你需要使用*proxy*参数: 455 | ``` 456 | async with aiohttp.ClientSession() as session: 457 | async with session.get("http://python.org", 458 | proxy="http://some.proxy.com") as resp: 459 | print(resp.status) 460 | ``` 461 | 同时支持认证代理: 462 | ``` 463 | async with aiohttp.ClientSession() as session: 464 | proxy_auth = aiohttp.BasicAuth('user', 'pass') 465 | async with session.get("http://python.org", 466 | proxy="http://some.proxy.com", 467 | proxy_auth=proxy_auth) as resp: 468 | print(resp.status) 469 | ``` 470 | 也可将代理的验证信息放在url中: 471 | ``` 472 | session.get("http://python.org", 473 | proxy="http://user:pass@some.proxy.com") 474 | ``` 475 | 与`requests(另一个广受欢迎的http包)`不同,aiohttp默认不会读取环境变量中的代理值。但你可以通过传递`trust_env=True`来让**aiohttp.ClientSession**读取*HTTP_PROXY*或*HTTPS_PROXY*环境变量中的代理信息(不区分大小写)。 476 | ``` 477 | async with aiohttp.ClientSession() as session: 478 | async with session.get("http://python.org", trust_env=True) as resp: 479 | print(resp.status) 480 | ``` 481 | 482 | ## 查看响应状态码 483 | 我们可以查询响应状态码: 484 | ``` 485 | async with session.get('http://httpbin.org/get') as resp: 486 | assert resp.status == 200 487 | ``` 488 | 489 | ## 获取响应头信息 490 | 我们可以查看服务器的响应信息, **ClientResponse.headers**使用的数据类型是**CIMultiDcitProxy**: 491 | ``` 492 | >>> resp.headers 493 | {'ACCESS-CONTROL-ALLOW-ORIGIN': '*', 494 | 'CONTENT-TYPE': 'application/json', 495 | 'DATE': 'Tue, 15 Jul 2014 16:49:51 GMT', 496 | 'SERVER': 'gunicorn/18.0', 497 | 'CONTENT-LENGTH': '331', 498 | 'CONNECTION': 'keep-alive'} 499 | ``` 500 | 这是一个特别的字典,它只为HTTP头信息而生。根据 RFC 7230,HTTP头信息中的名字是不区分大小写的。同时也支持多个不同的值对应同一个键。 501 | 502 | 所以我们可以通过任意形式访问它: 503 | ``` 504 | >>> resp.headers['Content-Type'] 505 | 'application/json' 506 | 507 | >>> resp.headers.get('content-type') 508 | 'application/json' 509 | ``` 510 | 所有的header信息都是由二进制数据转换而来,使用带有`surrogateescape`选项的UTF-8编码方式(surrogateescape是一种错误处理方式,详情看))。大部分时候都可以很好的工作,但如果服务器使用的不是标准编码就不能正常解码了。从 RFC 7230的角度来看这样的headers并不是合理的格式,你可以用**ClientReponse.resp.raw_headers**来查看原形: 511 | ``` 512 | >>> resp.raw_headers 513 | ((b'SERVER', b'nginx'), 514 | (b'DATE', b'Sat, 09 Jan 2016 20:28:40 GMT'), 515 | (b'CONTENT-TYPE', b'text/html; charset=utf-8'), 516 | (b'CONTENT-LENGTH', b'12150'), 517 | (b'CONNECTION', b'keep-alive')) 518 | ``` 519 | 520 | ## 获取响应cookies: 521 | 如果某响应包含一些Cookies,你可以很容易地访问他们: 522 | ``` 523 | url = 'http://example.com/some/cookie/setting/url' 524 | async with session.get(url) as resp: 525 | print(resp.cookies['example_cookie_name']) 526 | ``` 527 | > ### 注意: 528 | 响应中的cookies只包含重定向链中最后一个请求中的`Set-Cookies`头信息设置的值。如果每一次重定向请求都收集一次cookies请使用 aiohttp.ClientSession对象. 529 | 530 | ## 获取响应历史 531 | 如果一个请求被重定向了,你可以用**history**属性查看其之前的响应: 532 | ``` 533 | >>> resp = await session.get('http://example.com/some/redirect/') 534 | >>> resp 535 | 536 | >>> resp.history 537 | (,) 538 | ``` 539 | 如果没有重定向或`allow_redirects`设置为`False`,history会被设置为空。 540 | 541 | ## 使用WebSockets 542 | **aiohttp**提供开箱即用的客户端websocket。 543 | 你需要使用**aiohttp.ClientSession.ws_connect**()协程对象。它的第一个参数接受URL,返回值是**ClientWebSocketResponse**,这样你就可以用响应的方法与websocket服务器进行通信。 544 | ``` 545 | session = aiohttp.ClientSession() 546 | async with session.ws_connect('http://example.org/websocket') as ws: 547 | 548 | async for msg in ws: 549 | if msg.type == aiohttp.WSMsgType.TEXT: 550 | if msg.data == 'close cmd': 551 | await ws.close() 552 | break 553 | else: 554 | await ws.send_str(msg.data + '/answer') 555 | elif msg.type == aiohttp.WSMsgType.CLOSED: 556 | break 557 | elif msg.type == aiohttp.WSMsgType.ERROR: 558 | break 559 | ``` 560 | 你只能使用一种读取方式(例如`await ws.receive()` 或者 `async for msg in ws:`)和写入方法,但可以有多个写入任务,写入任务也是异步完成的(`ws.send_str('data')`)。 561 | 562 | ## 设置超时 563 | 默认情况下每个IO操作有5分钟超时时间。可以通过给**ClientSession.get**()及其同类组件传递`timeout`来覆盖原超时时间: 564 | ``` 565 | async with session.get('https://github.com', timeout=60) as r: 566 | ... 567 | ``` 568 | `None` 或者`0`则表示不检测超时。 569 | 还可通过调用**async_timeout.timeout**上下文管理器来为连接和解析响应内容添加一个总超时时间: 570 | ``` 571 | import async_timeout 572 | 573 | with async_timeout.timeout(0.001): 574 | async with session.get('https://github.com') as r: 575 | await r.text() 576 | ``` 577 | > ### 注意: 578 | 超时时间是累计的,包含如发送情况,重定向,响应解析,处理响应等所有操作在内... 579 | 580 | ## 愉快地结束: 581 | 当一个包含`ClientSession`的`async with`代码块的末尾行结束时(或直接调用了`.close()`),因为asyncio内部的一些原因底层的连接其实没有关闭。在实际使用中,底层连接需要有一个缓冲时间来关闭。然而,如果事件循环在底层连接关闭之前就结束了,那么会抛出一个 资源警告: 存在未关闭的传输(通道)(`ResourceWarning: unclosed transport`),如果警告可用的话。 582 | 为了避免这种情况,在关闭事件循环前加入一小段延迟让底层连接得到关闭的缓冲时间。 583 | 对于非SSL的`ClientSession`, 使用0即可(`await asyncio.sleep(0)`): 584 | ``` 585 | async def read_website(): 586 | async with aiohttp.ClientSession() as session: 587 | async with session.get('http://example.org/') as response: 588 | await response.read() 589 | 590 | loop = asyncio.get_event_loop() 591 | loop.run_until_complete(read_website()) 592 | # Zero-sleep to allow underlying connections to close 593 | loop.run_until_complete(asyncio.sleep(0)) 594 | loop.close() 595 | ``` 596 | 对于使用了SSL的`ClientSession`, 需要设置一小段合适的时间: 597 | ``` 598 | ... 599 | # Wait 250 ms for the underlying SSL connections to close 600 | loop.run_until_complete(asyncio.sleep(0.250)) 601 | loop.close() 602 | ``` 603 | 合适的时间因应用程序而异。 604 | 605 | 当asyncio内部的运行机制改变时就可以让aiohttp去等待底层连接关闭在退出啦,上面这种额外的方法总会废弃啦。你也可以跟进问题#1925来参与改进。 606 | -------------------------------------------------------------------------------- /aiohttp文档/Contributing.md: -------------------------------------------------------------------------------- 1 | # 贡献须知 2 | ## 贡献者说明 3 | 首先需要clone GitHub上的仓库: 打开链接,并位于右上角的点击“Fork”按钮。 4 | :) 我想应该所有人都会使用git和github吧~。 5 | 6 | 之后要做的步骤很清晰: 7 | 1. clone这个GitHub仓库。 8 | 2. 进行修改。 9 | 3. 确保所有代码测试通过。 10 | 4. 在CHANGES文件夹中添加一个说明文件(用于更新Changelog)。 11 | 5. 提交到自己的aiohttp仓库。 12 | 6. 发起一个关于你的修改的PR。 13 | 14 | ### 注意 15 | 如果你的PR有很长历史记录或者很多提交,请在创建PR之前重新从主仓库创建一次,确保它没有这些记录。 16 | 17 | 18 | ## 运行aiohttp测试组件的准备 19 | 我们建议你使用pyhton虚拟环境来运行我们的测试。 20 | 关于创建虚拟环境有以下几种方法: 21 | 22 | 如果你喜欢使用virtualenv,可以这样用: 23 | ``` 24 | $ cd aiohttp 25 | $ virtualenv --python=`which python3` venv 26 | $ . venv/bin/activate 27 | ``` 28 | 或者使用python 标准库中的venv: 29 | ``` 30 | $ cd aiohttp 31 | $ python3 -m venv venv 32 | $ . venv/bin/activate 33 | ``` 34 | 还可以使用virtualenvwarapper: 35 | ``` 36 | $ cd aiohttp 37 | $ mkvirtualenv --python=`which python3` aiohttp 38 | ``` 39 | 还有其他可用的工具比如`pyvenv`,不过我们只是要让你知道: 需要创建一个Python3虚拟环境并使用它。 40 | 41 | 弄完之后我们要安装开发所需的包: 42 | ``` 43 | $ pip install -r requirements/dev.txt 44 | ``` 45 | ### 注意 46 | 如果你计划在测试组件中使用pdb或ipdb,执行: 47 | ``` 48 | $ py.test tests -s 49 | ``` 50 | 使用命令禁止输出捕获。 51 | 恭喜,到这一步我们就准备好我们的测试组件了。 52 | 53 | ## 运行aiohttp测试组件 54 | 全部准备完之后,是时候运行了: 55 | ``` 56 | $ make test 57 | ``` 58 | 第一次使用命令运行时会运行`flask8`工具(抱歉,我们不接受关于pep8和pyflakes相关错误的PR)。 59 | `flake8`成功运行后,测试也会随之运行。 60 | 请稍微注意下输出的语句。 61 | 任何额外的文本信息(打印的条款等)都会被删除。 62 | 63 | ## 测是覆盖 64 | 我们正尽可能地有一个好的测试覆盖率,请不要把它变得更糟。 65 | 运行下这个命令: 66 | ``` 67 | $ make cov 68 | ``` 69 | 来加载测试组件并收集覆盖信息。一旦命令执行完成,会在最后一行显示如下输出:`open file:///.../aiohttp/coverage/index.html` 70 | 请看一下这个链接并确保你的修改已被覆盖到。 71 | aiohttp使用`codecov.io`来存储覆盖结果。 你可以去这个页面查看: https://codecov.io/gh/aio-libs/aiohttp 其他详细信息。 72 | 强烈推荐浏览器扩展`https://docs.codecov.io/docs/browser-extension`,仅在GitHub Pull Request上的"已修改文件"标签上就能做分析。 73 | 74 | ## 文档 75 | 我们鼓励完善文档。 76 | 发起一个关于修改文档的PR时请先运行下这个命令: 77 | ``` 78 | $ make doc 79 | ``` 80 | 完成后会输出一个索引页面: `file:///.../aiohttp/docs/_build/html/index.html` 81 | 请看一下,确保样式没问题。 82 | 83 | ## 拼写检测 84 | 我们使用`pyenchant`和`sphinxcontrib-spelling`检测文档中的拼写: 85 | ``` 86 | $ make doc-spelling 87 | ``` 88 | 不幸的是,在MacOS X上会有些问题。 89 | 在Linux上运行拼写检测前要先安装下: 90 | ``` 91 | $ sudo apt-get install enchant 92 | $ pip install sphinxcontrib-spelling 93 | ``` 94 | 95 | ## 更新 修改日志 96 | CHANGES.rst文件使用towncrier工具进行管理,所有重要的修改都要有一个条目。 97 | 要为新文件增加一个条目,首要需要创建一个关于你想怎么做的issue。一个PR本身的功能也和这个一样,但有一个正经关于此修改的issue更好(举个例子,万一因为代码质量问题驳回了PR呢...)。 98 | 99 | 一旦你有了一个issue或PR,你会得到一个号码并在CHANGES/目录内以此issue的号码和其扩展内容如 `.removal, .feature, .bugfix, .doc`创建一个文件。比如你的issue或PR号码是1234,是关于修复bug的,这样就会创建一个`CHANGES/1234.bugfixs`的文件。PR们可以创建多个类别的说明文件(比如,你添加了一个新功能,并且要删除或不在赞成使用某个旧功能,那就创建`CHANGES/NNNN.feature`和`CHANGES/NNNN.removal`)。同样地,如果一个PR涉及到多个issues/PR你可以为它们每个都创建相同的内容,Towncrier会删除重复的部分。 100 | 101 | 文件的内容使用`reStruredText`格式化内容,格式化后的内容会作为新文件条目来使用。你不需要为issue或PR添加关联,towncrier会自动为所有涉及到的issues添加关联。 102 | 103 | ## 最后 104 | 做完这些之后,请在GitHub上发起PR,谢谢~。 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /aiohttp文档/Essays.md: -------------------------------------------------------------------------------- 1 | * 0.21版本时的路由重构 2 | * - 合理性方面 3 | * - 部署方面 4 | * - 向后兼容方面 5 | * 1.1中的新内容 6 | * - YARL 和 URL 编码 7 | * - 新API 8 | * - URL编码 9 | * - 子应用 10 | * - Url倒推 11 | * - 应用冻结 12 | * 走向2.x 13 | * - 客户端方面 14 | * - - 分块 15 | * - - 压缩 16 | * - - 客户端连接器 17 | * - - ClientResponse.release 18 | * - - 客户端异常 19 | * - - 客户端payload 20 | * - - 其他杂项 21 | * - 服务器方面 22 | * - - ServerHttpProtocal和底层服务器的一些详情 23 | * - - 应用方面 24 | * - - WebRequest 和 WebResponse 25 | * - - RequestPayloadError 26 | * - - WSGI -------------------------------------------------------------------------------- /aiohttp文档/Essays/MigrationTo2.0.md: -------------------------------------------------------------------------------- 1 | # 走进2.x 2 | 3 | ## 客户端方面 4 | ### 分块 5 | aiohttp现在不支持自定义分块大小了。至于分块的多大取决于开发者决定要放多少分块数据。如果`chunking`是允许的,aiohttp会将分块的内容以“Transfer-encoding: chunked”提供的格式编码。 6 | aiohttp并不能自动编码分块内容,即使指定了`transfer-encoding`头信息也不行, 必须显式地设置`chunked`。如果设置了`chunked`,`Transfer-encoding`和`content-length`头信息就不能指定了。 7 | 8 | ### 压缩 9 | 可以显式地使用compress参数来开启压缩功能。如果压缩功能开启,就不能添加`content-encoding`头信息了。压缩同样适用于分块传输编码。同样,压缩也并不能和`Content-Length`一块使用。 10 | 11 | ### 客户端连接器 12 | 默认只有一个连接器对象管理着所有的连接。1.x版本的规则是每台主机有多少限制。到了2.x,`limit`参数定义当前有多少连接可以打开,新的参数`limit_per_host`则是指定每台主机的限制。默认是无限制的。 13 | 与1.x不同的是,`BaseConnector.close`现在是常规方法。 14 | `BaseConnector.conn_timeout`也移动到了`ClientSession`中。 15 | 16 | ### ClientResponse.release 17 | 内部结构有了很大改变,做了很多调整。得到响应对象后不需要再调用`release`。 当客户端完全接受到内容后,底层连接会自动返回到连接池中。如果内容并没有完全读取,连接将会关闭。 18 | 19 | ### 客户端异常 20 | 异常的等级制度也有了极大改变。aiohttp现在只定义了一个异常,它覆盖了连接处理和服务器异常响应。对于开发者的错误,aiohttp现在使用python的标准异常如`ValueError`,·`TypeError`。 21 | 读取响应内容可能会抛出`ClientPayloadError`异常。 这个异常表示载体的编码出了错误。像是无效的压缩信息,不合理的分块编码块或是内容长度与`content-length`指定的不匹配等等。 22 | 所有的异常已被移动到顶级模块aiohttp.errors模块。 23 | 新的异常等级制度: 24 | * ClientError - 所以异常的基类。 25 | * - ClientResponseError - 从服务器获得相应后有可能被抛出。 26 | * - - WSServerHandshakeError - web socket服务器响应错误。 27 | * - - - ClientHttpProxyError - 代理响应错误。 28 | * - ClientConnectionError - 表示低级连接方面的相关问题。 29 | * - - ClientOSError - 连接错误的子集,继承于OSError异常。 30 | * - - - ClientConnectorError - 连接器方面的相关问题。 31 | * - - - - ClientProxyConnectionError - 代理连接初始化时的问题。 32 | * - - - - - ServerConnectionError - 服务器连接方面的相关问题。 33 | * - - - - ServerDisconnectedError - 服务器断开连接的异常。 34 | * - - - - ServerTimeoutError - 服务器操作超时的异常(比如读取超时)。 35 | * - - - - ServerFingerprintMismatch - 服务器指纹不匹配的异常。 36 | * - ClientPayloadError - 此错误只会在读取响应载体时发生以下错误: 无效的压缩信息,不合理的分块编码或长度不匹配的内容才会被抛出。 37 | 38 | ### 客户端payload(form-data) 39 | 为了整合form-data/payload(载体)的处理,引入了一个新的Payload系统。它可以执行现存类型的处理,也可以处理由用户定义的类型。 40 | `FormData.__call__`不再需要`encoding`参数,返回的内容也由迭代器对象或字节变为Payload实例。对于标准类型如`str`,`byte`,`io.IOBase`,·`StreamReader`,`DataQueue`,aiohttp都提供相关payload适配器。 41 | 生成器不能再作为数据生成器提供,你可以用streamer代替。比如,上传文件可以这样: 42 | ``` 43 | @aiohttp.streamer 44 | def file_sender(writer, file_name=None): 45 | with open(file_name, 'rb') as f: 46 | chunk = f.read(2**16) 47 | while chunk: 48 | yield from writer.write(chunk) 49 | chunk = f.read(2**16) 50 | 51 | # Then you can use `file_sender` like this: 52 | 53 | async with session.post('http://httpbin.org/post', 54 | data=file_sender(file_name='huge_file')) as resp: 55 | print(await resp.text()) 56 | ``` 57 | 58 | ### 其他杂项 59 | `ClientSession.request()`中不再赞成使用`encoding`参数。Payload编码由payload层控制。为payload实例指定编码还是可以的。 60 | `version`参数从`ClientSession.request()`中移除,客户端版本可以在`ClientSession`构造器中指定。 61 | 使用`aiohttp.WSMsgType`代替`aiohttp.MsgType`。 62 | `ClientResponse.url`现在是一个yarl.URL实例(url_obj不在赞成使用)。 63 | `ClientResponse.raise_for_status()`将抛出`aiohttp.ClientResponseError`异常。 64 | `ClientResponse.json()`严格按照响应的内容类型来解析。如果内容类型不匹配,将会抛出`aiohttp.ClientResponseError`异常。你可以通过传递`content_type=None`来关闭内容类型检测。 65 | 66 | # 服务器方面 67 | 68 | ## ServerHttpProtocol和底层服务器的一些详情 69 | 为了提供更好的执行效率和支持HTTP流水线,内部重新进行了设计。`ServerHttpProtocol`已移除,与`RequestHandler`合并在一起,很多底层api也被移除。 70 | 71 | ## 应用方面 72 | 1. 构造器的loop已不赞成再使用。Loop会从应用运行器中获取,*run_app*函数可以被任意gunicorn worker使用。 73 | 2. `Application.router.add_subapp`已移除,使用`Application.add_subapp`代替。 74 | 3. `Application.finished`已移除,使用`Application.cleanup`代替。 75 | 76 | ## WebRequest和WebResponse 77 | 1. `GET`和`POST`属性将不再存在。使用`query`代替`GET`。 78 | 2. WebResponse.chunked不支持自定义分块大小 - 开发者有义务手动分块。 79 | 3. Payloads 的功能和body(响应体)差不多。所以在WebResponse中可以使用客户端响应的内容对象作为body参数的值。 80 | 4. `FileSender`api已移除,被更通用的`FileResponse`类所代替: 81 | ``` 82 | async def handle(request): 83 | return web.FileResponse('path-to-file.txt') 84 | ``` 85 | 5. `WebSocketResponse.protocol`重命名为`WebSocketResponse.ws_protocol`。`WebSocketResponse.protocol`现在是`RequestHandler`类的实例对象。 86 | 87 | ## RequestPayloadError 88 | 读取请求的payload时可能会抛出`RequestPayloadError`异常。与`ClientPayloadError`类似。 89 | 90 | ## WSGI 91 | WSGI的支持现已移除,现在支持gunicorn wsgi。我们仍然对`web.Application`提供默认功能和基于uvloop的gunicorn worker。 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /aiohttp文档/Essays/RouterRefactoring.md: -------------------------------------------------------------------------------- 1 | # 0.21版本的路由重构说明 2 | 3 | ## 合理性方面 4 | 第一代路由(v1)是基于映射对(method, path)的。映射则被称为路由。路由们都有一个唯一的名称。 5 | 第一代路由的主要的设计问题出构造路由(method, path)上,真实的URL构造工作是在资源基础上的。HTTP方法并不在URI中只是发送HTTP请求的途径。 6 | 同时多个不同路由都是指向同一个路径的也会造成一些混乱。再有,构造多个同路径路由起名字也是一件麻烦事。 7 | 另一方面,有时同一个web处理器会绑定多个不同的HTTP方法。对于v1版本的路由可以通过传递'\*'作为HTTP方法。以类为基础的视图也通常需要'\*'方法。 8 | 9 | ## 部署方面 10 | 更改之后,**资源**成为了首要因素: 11 | ``` 12 | resource = router.add_resource('/path/{to}', name='name') 13 | ``` 14 | 资源必须有一个路径(动态或静态),还可以有一个可选的名字。 15 | 在路由器相关的地方这个名字也必须是唯一的。 16 | 资源同时具有路由表。 17 | 路由则相对于HTTP方法及其对应web处理器: 18 | ``` 19 | route = resource.add_route('GET', handler) 20 | ``` 21 | 用户仍然可以用通配符来表示接受所有的HTTP方法(或许我们之后会添加个resource.add_wildcard(handler)的方法)。 22 | 现在,名字是相对于资源来说的了,`app.router['name']`会返回资源实例,而不是aiohttp.web.Route对象了。 23 | 资源具有.url()方法,所以`app.router['name'].url(parts={'a': 'b'}, query={'arg':'param'})`仍然可以使用。 24 | 改变之后允许重写静态文件处理以及部署嵌套应用。 25 | 拆散HTTP路径和方法真棒! 26 | 27 | 28 | ## 向后兼容方面 29 | 重构之后功能兼容之前99%已部署的内容。 30 | 99%的意思就是所有的栗子和绝大多数现存代码不需要修改就可以工作,不过我们仍然在遵守着向后不兼容政策,只不过感觉不到。 31 | `app.router['name']`现在返回`aiohttp.web.BaseResource`实例而不是`aiohttp.web.Route`实例, 但资源具有相同的 `resource.url(...)`这个最常用的方法,所以最终用户也感觉不出变化。 32 | `route.match(...)`现在不在支持,请使用`aiohttp.web.AbstractResource.resolve()`代替。 33 | `app.router.add_route(method, path, handler, name='name')`现在只是下面代码的快捷写法: 34 | ``` 35 | resource = app.router.add_resource(path, name=name) 36 | route = resource.add_route(method, handler) 37 | return route 38 | ``` 39 | `app.router.register_route(...)`仍然支持,每次调用都会创建aiohttp.web.ResourceAdapter对象(但已不赞成使用)。 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /aiohttp文档/Essays/What'sNewIn1.1.md: -------------------------------------------------------------------------------- 1 | # aiohttp 1.1的新内容 2 | 3 | ## YARL 和 URL编码 4 | 自aiohttp 1.1起,aiohttp使用yarl来进行URL的处理。 5 | 6 | ## 新API 7 | `yarl.URL`提供非常简便的方法来进行URL的相关操作。 8 | 客户端API仍然可以接受str的url形式,比如`session.get()`等价于`session.get(yarl.URL('http://example.com'))`。 9 | 内部API均已选用yarl.URL做处理。aiohttp.CookieJar将只接收URL实例对象。 10 | 服务端方面添加了`web.Request.url`和`web.Request.rel_url`来表示请求URL的相对和绝对路径。 11 | 使用URL是比较推荐的做法,现存的检索URL的方法已不赞成使用,并最终会被删除。 12 | web异常引起的重定向也支持 yarl.URL作为地址。str也会一直支持使用的。 13 | 由路由得到URL也已做了修改。 14 | 主API`aiohttp.web.Request.url_for(name, **kwargs)`现在会返回yarl.URL实例。已不支持查询参数,但也可以使用稍微繁琐的方式来添加查询参数:`request.url_for('name_resource', param='a').with_query(arg='val')` 。 15 | 返回的是相对URL,绝对URL需要使用`request.url.join(request.url_for(...))`来构建。 16 | 17 | ## URL编码 18 | 在yarl.URL创建时会编码所有非ASCII的字符。 19 | `URL('https://www.python.org/путь')`会被编码为`'https://www.python.org/%D0%BF%D1%83%D1%82%D1%8C'`。 20 | 填写路由表时可以使用非ASCII编码的字符和百分号字符两种路径: 21 | ``` 22 | app.router.add_get('/путь', handler) 23 | ``` 24 | 和 25 | ``` 26 | app.router.add_get('/%D0%BF%D1%83%D1%82%D1%8C', handler) 27 | ``` 28 | 是同样的效果。因为在内部`'/путь'`会被编码成百分号形式。 29 | 路由匹配也同样接受两种URL格式: 原始形式和在经过路由模式转换的形式。 30 | 31 | 32 | ## 子应用 33 | 子应用被用来解决复杂的代码结构问题。假设我们有一个项目,项目中既有业务逻辑也有管理区域和调试工具。 34 | 管理区是一个独立地应用,有其自己的逻辑,而且所有的URL都带有如'/admin'前缀的。 35 | 所以我们可以创建一个完全独立的应用,并将其命名为'admin',并把前缀附加到主app上: 36 | ``` 37 | admin = web.Application() 38 | # setup admin routes, signals and middlewares 39 | 40 | app.add_subapp('/admin/', admin) 41 | ``` 42 | 中间件和信号是链式的。 43 | 也就是说请求'/admin/something'app中的中间件会首先被调用,其次是admin。中间件是顺序调用得。 44 | 对于信号来说也一样——信号会被发送给app和admin。 45 | 常规信息如`on_startup`, `on_shutdown`和`on_cleanup`会被发送到所有已注册过此信号的子应用。发出的参数是这个子应用的实例对象,并不是顶级应用的实例对象。 46 | 二层应用后可以嵌套第三层,并无层级限制。 47 | 48 | ## Url 倒推 49 | 子应用中的Url倒推会给出带有特定前缀的url。 50 | 不过要得到URL,子应用的路由应该这么用: 51 | ``` 52 | admin = web.Application() 53 | admin.add_get('/resource', handler, name='name') 54 | 55 | app.add_subapp('/admin/', admin) 56 | 57 | url = admin.router['name'].url_for() 58 | ``` 59 | 得到的URL为`'/admin/resource'`。 60 | 61 | ## 应用冻结 62 | 应用可以作为主app使用(app.make_handler())也可以做为子应用使用,但不要同时使用。 63 | 在使用`.add_subapp()`连接应用后,或开启顶级应用后,应用就被冻结了。 64 | 这也就意味着不管是注册新路由,新信号还是新中间件都是不允许的。改变冻结应用的状态(app['name'] = 'value')是不赞成使用的,并最终会删除。 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /aiohttp文档/ExternalSources.md: -------------------------------------------------------------------------------- 1 | 有用的外部资源包汇集 2 | 3 | * 第三方扩展包 4 | * - 官方支持的包 5 | * - - aiohttp 扩展 6 | * - - 数据库相关 7 | * - - 其他工具 8 | * - 很赞的第三方包 9 | * - - 数据库相关 10 | * - 其他工具 11 | * 使用aiohttp创建的其他类型的包。 12 | * 基于aiohttp的网站。 -------------------------------------------------------------------------------- /aiohttp文档/ExternalSources/Third-PartyLibraries.md: -------------------------------------------------------------------------------- 1 | # 第三方扩展包 2 | 3 | aiohttp不仅仅是一个能发起HTTP请求和创建WEB服务器的库。同时还为广大包提供基础支持。 4 | 本页面列出了这些使用aiohttp为基础的工具。 5 | 你可以随时向我们提供你写的库,如果可以的话请写一个PR到 https://github.com/aio-libs/aiohttp/. 6 | * :smile:为什么你会想把你自己的牛X代码提供给我们呢? 7 | * 因为这样能提高曝光率啊~。 8 | 9 | # 官方支持的包 10 | 本列表列出了由aio-libs官方团队所创建维护的包。主页是:https://github.com/aio-libs 11 | 12 | ## aiohttp扩展 13 | * aiohttp-session 是一个为aiohttp.web提供会话功能的扩展包。 14 | * aiohttp-debugtoolbar 是一个为aiohttp.web提供调试工具栏的扩展包。 15 | * aiohttp-security 是一个为aiohttp.web提供许可和验证功能的扩展包。 16 | * aiohttp-devtools 是一个为aiohttp.web应用提供开发工具包的扩展包。 17 | * aiohttp-cors 是一个为aiohttp提供CORS(跨域资源共享)支持的扩展包。 18 | * aiohttp-sse 是一个为aiohttp提供服务器推送事件的扩展包。 19 | * pytest-aiohttp 是一个为aiohttp提供pytest插件的扩展包。 20 | * aiohttp-mako 是一个为aiohttp.web提供Mako模板支持的扩展包。 21 | * aiohttp-jinja2 是一个为aiohttp.web提供Jinja2模板支持的扩展包。 22 | 23 | ## 数据库相关 24 | * aiopg 可以以异步方式处理PostgreSQL的扩展包。 25 | * aiomysql 可以以异步方式处理MySql的扩展包。 26 | * aioredis 可以以异步方式处理Redis的扩展包。 27 | 28 | ## 其他工具 29 | * aiodocker 基于asyncio和aiohttp的Python Docker API客户端。 30 | * aiobotocore 使用aiohttp对botocore提供异步支持的库。 31 | 32 | # 很赞的第三方库 33 | 这些库不在aio-libs中,不过写的很赞,强烈推荐使用~。 34 | * uvloop 使用它可以让asyncio运行得飞快,基于`libuv`。 35 | 强烈推荐使用它来代替标准asyncio。 36 | 37 | ## 数据库相关 38 | * asyncpg 另一个可以以异步方式处理PostgreSQL的扩展包。它比aiopg快很多,不过它并不是开箱即用的——它的API有点怪。不管怎样请务必看一看,真滴非常快。 39 | 40 | # 其他工具 41 | 这些包是基于aiohttp的,但不在上述两种类别中。 42 | 至于好不好用,全凭君定——因为我们也不知道。 43 | 44 | 请现在这里添加你的库的介绍,之后在更改其状态。 45 | * aiohttp-cache 是一个为aiohttp服务器提供缓存系统的扩展包。 46 | * aiocache 是一个为多个后端提供异步缓存的扩展包。(未知框架) 47 | * gain 是一个基于异步的爬虫框架。 48 | * aiohttp-swagger 是关于aiohttp服务器使用Swagger的API文档。 49 | * aiohttp-swaggerify 是一个为aiohttp终端自动生成Swagger 2.0定义的扩展包。 50 | * aiohttp-validate 是一个帮助你确认你的API是否有效(JSON形式)的扩展包。 51 | * raven-aiohttp raven-python版aiohttp。 52 | * webargs 一个可以让解析HTTP请求参数变得容易的包。已被多个流行web框架内置。其中包括 Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon和aiohttp。 53 | * aioauth-client 为aiohttp提供OAuth客户端的扩展包。 54 | * aiohttpretty 使用aiohttp制作的httpretty(一个可以伪造HTTP请求的包)的包。 55 | * aioresponse 帮助你制造虚假web请求的包。 56 | * aiohttp-transmute 是一个为aiohttp提供transmute(未知含义)。 57 | * aiohttp_apiset 一个使用swagger规范创建路由的扩展包。 58 | * aiohttp-login 是一个为aiohttp应用提供注册和授权功能的扩展包。 59 | * aiohttp_utils 一个方便创建aiohttp.web应用的扩展包。 60 | * aiohttpproxy 一个简单aiohttp HTTP代理工具。 61 | * aiohttp_traversal 一个将基础路由转换给aiohttp.web的扩展包。 62 | * aiohttp_autoreload 一个让aiohttp服务器在源码更改时可以实时重载的扩展包。 63 | * gidgethub 异步Github API。 64 | * aiohttp_jrpc aiohttp版 JSON-RPC服务。 65 | * fbemissary 一个提供Facebook Messenger平台的功能的bot框架,基于asyncio和aiohttp。 66 | * aioslacker 一个asyncio懒包装后的扩展包。 67 | * aioreloader 一个可以将tornado重新加载到aiohttp上的扩展包。 68 | * aiohttp_babel 为aiohttp提供Babel本地化支持的扩展包。 69 | * python-mocket 一个套接字mock框架 - 支持所有种类的套接字应用,包括web客户端。 70 | * aioraft 异步RAFT算法(可逆加成-断裂链转移聚合算法),基于aiohttp。 71 | * home-assistant 开元中心自动化平台,基于Python 3。 72 | * discord.py Discord客户端。 73 | * async-v20 是一个为OANDA的v20API提供异步FOREX客户端扩展包。Python 3.6 +。 74 | 75 | -------------------------------------------------------------------------------- /aiohttp文档/FrequentlyAskedQuestions.md: -------------------------------------------------------------------------------- 1 | # 常见问题汇总 2 | 3 | * 有没有提供像Flask一样的`@app.route`装饰器的计划? 4 | * aiohttp有没有Flask中的蓝图或Django中的App的概念呢? 5 | * 如何创建一个可以缓存url的给定前缀的路由? 6 | * 我要把数据库连接放在哪才可以让处理器访问到它? 7 | * 为什么最低版本只支持到Python 3.4.2? 8 | * 如何让中间件可以存储数据以便让web-handler使用? 9 | * 如何并行地接收来自不同源的事件? 10 | * 如何以编程的方式在服务器端关闭websocket? 11 | * 如何从特定IP地址上发起请求? 12 | * 如果是隐式循环要怎么用aiohttp的测试功能呢? 13 | * API创建和废弃政策是什么? 14 | * 如何让整个应用程序都使用gzip压缩? 15 | * 在web服务器汇总如何管理ClientSession? 16 | 17 | # 有没有提供像Flask一样的`@app.route`装饰器的计划? 18 | 这种方法有几项问题: 19 | * 最大的问题是“导入时会有副作用”。 20 | * 路由匹配是有序的,这样做的话在导入时很难保证顺序。 21 | * 在大部分大型应用中,都表示在某文件中写路由表比这样好很多。 22 | 所以,基于以上原因,我们就没有提供这个功能。不过如果你真的很想用这个功能,继承下`web.Application`然后自己写一个吧~。 23 | 24 | # aiohttp有没有Flask中的蓝图或Django中的App的概念呢? 25 | 如果你计划写一个大型应用程序,你可以考虑使用嵌套应用。它的功能就像Flask的蓝图和Django的应用一样。 26 | 使用嵌套应用你可以为主应用程序添加子应用。 27 | 28 | 请看: 嵌套应用 29 | 30 | # 如何创建一个可以缓存url的给定前缀的路由? 31 | 尝试下这样做: 32 | ``` 33 | app.router.add_route('*', '/path/to/{tail:.+}', sink_handler) 34 | ``` 35 | 第一个参数星号(\*)表示可以是任意方法(GET, POST, OPTIONS等等),第二个参数则是指定的前缀,第三个就是处理器了。 36 | 37 | # 我要把数据库连接放在哪才可以让处理器访问到它? 38 | aiohttp.web.Application对象支持字典接口(dict),在这里面你可以存储数据库连接或其他任何你想在不同处理器间共享的资源。请看例子: 39 | ``` 40 | async def go(request): 41 | db = request.app['db'] 42 | cursor = await db.cursor() 43 | await cursor.execute('SELECT 42') 44 | # ... 45 | return web.Response(status=200, text='ok') 46 | 47 | 48 | async def init_app(loop): 49 | app = Application(loop=loop) 50 | db = await create_connection(user='user', password='123') 51 | app['db'] = db 52 | app.router.add_get('/', go) 53 | return app 54 | ``` 55 | 56 | # 为什么最低版本只支持到Python 3.4.2? 57 | 在aiohttp 还是v0.18.0时,我们是支持Python 3.3 - 3.4.1的。 58 | 最主要的原因是 `object.__del__()`方法了,自Python3.4.1时它才可以完整地工作,这也正是我们想要的——可以很方便的关闭资源。 59 | 当前适用于Python3.3, 3.4.0版本的是v0.17.4。 60 | 当然,这应该对于大多数适用aiohttp的用户来说不是个问题(例子中的Ubuntu 14.04.3 LTS的Python版本是3.4.3呢!),不过依赖于aiohttp的包应该考虑下这个问题了,是只使用低版本aiohttp呢还是一起抛弃Python3.3呢。 61 | 在aiohttp v1.0.0时我们就抛弃了Python 3.4.1转而要求3.4.2 +。原因是: loop.is_closed自3.4.2才出现。 62 | 最后,在如今的2016年夏这更不应该是个问题了,主流都已经是Python 3.5啦。 63 | 64 | # 如何让中间件可以存储数据以便让web-handler使用? 65 | `aiohttp.web.Request`与`aiohttp.web.Application`一样都支持字典接口(dict)。 66 | 只需要将数据放到里面即可: 67 | ``` 68 | async def handler(request): 69 | request['unique_key'] = data 70 | ``` 71 | 请看 https://github.com/aio-libs/aiohttp_session 的代码,`aiohttp_session.get_session(request)`方法使用`SESSION_KEY`来保存请求的特定会话信息。 72 | 73 | # 如何并行地接收来自不同源的事件? 74 | 比如我们现在有两个事件: 75 | 1. 某一终端用户的WebSocket事件。 76 | 77 | 2. Redis PubSub从应用的其他地方接受信息并要通过websocket发送给其他用户的事件。 78 | 并行地调用`aiohttp.web.WebSocketResponse.receive()`是不行的,同一时间只有一个任务可以执行websocket读操作。 79 | 不过其他任务可以使用相同的websocket对象发送数据: 80 | 81 | ``` 82 | async def handler(request): 83 | 84 | ws = web.WebSocketResponse() 85 | await ws.prepare(request) 86 | task = request.app.loop.create_task( 87 | read_subscription(ws, 88 | request.app['redis'])) 89 | try: 90 | async for msg in ws: 91 | # handle incoming messages 92 | # use ws.send_str() to send data back 93 | ... 94 | 95 | finally: 96 | task.cancel() 97 | 98 | async def read_subscription(ws, redis): 99 | channel, = await redis.subscribe('channel:1') 100 | 101 | try: 102 | async for msg in channel.iter(): 103 | answer = process message(msg) 104 | ws.send_str(answer) 105 | finally: 106 | await redis.unsubscribe('channel:1') 107 | ``` 108 | 109 | # 如何以编程的方式在服务器端关闭websocket? 110 | 比如我们现在有两个终端: 111 | 1. `/echo` 一个用于以某种方式验证用户真实性的回显websocket。 112 | 2. `/logout` 用于关闭某一用户的打开的所有websocket连接。 113 | 一种简单的解决方法是在`aiohttp.web.Application`实例中为某一用户持续存储websocket响应,并在`/logout_user`处理器中执行`aiohttp.web.WebSocketResponse.close()`。 114 | 115 | ``` 116 | async def echo_handler(request): 117 | 118 | ws = web.WebSocketResponse() 119 | user_id = authenticate_user(request) 120 | await ws.prepare(request) 121 | request.app['websockets'][user_id].add(ws) 122 | try: 123 | async for msg in ws: 124 | ws.send_str(msg.data) 125 | finally: 126 | request.app['websockets'][user_id].remove(ws) 127 | 128 | return ws 129 | 130 | 131 | async def logout_handler(request): 132 | 133 | user_id = authenticate_user(request) 134 | 135 | ws_closers = [ws.close() for ws in request.app['websockets'][user_id] if not ws.closed] 136 | 137 | # Watch out, this will keep us from returing the response until all are closed 138 | ws_closers and await asyncio.gather(*ws_closers) 139 | 140 | return web.Response(text='OK') 141 | 142 | 143 | def main(): 144 | loop = asyncio.get_event_loop() 145 | app = web.Application(loop=loop) 146 | app.router.add_route('GET', '/echo', echo_handler) 147 | app.router.add_route('POST', '/logout', logout_handler) 148 | app['websockets'] = defaultdict(set) 149 | web.run_app(app, host='localhost', port=8080) 150 | ``` 151 | 152 | # 如何从特定IP地址上发起请求? 153 | 如果你的系统上有多个IP接口,你可以选择其中一个来绑定到本地socket: 154 | 155 | ``` 156 | conn = aiohttp.TCPConnector(local_addr=('127.0.0.1', 0), loop=loop) 157 | async with aiohttp.ClientSession(connector=conn) as session: 158 | ... 159 | ``` 160 | 161 | ### 扩展 162 | 请看 `aiohttp.TCPConnector`及其`local_addr`参数。 163 | 164 | # 如果是隐式循环要怎么用aiohttp的测试功能呢? 165 | 传递显式的loop是推荐方式。但有时如果你有一个嵌套多层而且写的不好的服务时,这几乎是不可能的任务。 166 | 这里推荐一种基于**猴子补丁**的技术方式,要依赖于`aioes`,具体方式是注射一个loop进去。这样你只需要让`AioESService`在其自己的循环中就行(##非常不确定此句的正确性##)。例子如下: 167 | 168 | ``` 169 | import pytest 170 | 171 | from unittest.mock import patch, MagicMock 172 | 173 | from main import AioESService, create_app 174 | 175 | class TestAcceptance: 176 | 177 | async def test_get(self, test_client, loop): 178 | with patch("main.AioESService", MagicMock( 179 | side_effect=lambda *args, **kwargs: AioESService(*args, 180 | **kwargs, 181 | loop=loop))): 182 | client = await test_client(create_app) 183 | resp = await client.get("/") 184 | assert resp.status == 200 185 | ``` 186 | 187 | 注意我们为`AioESService`打了补丁,但是要额外传入一个显式`loop`(你需要自己加载loop fixture)。 188 | 最后需要测试的代码(你需要一个本地elasticsearch实例来运行): 189 | ``` 190 | import asyncio 191 | 192 | from aioes import Elasticsearch 193 | from aiohttp import web 194 | 195 | 196 | class AioESService: 197 | 198 | def __init__(self, loop=None): 199 | self.es = Elasticsearch(["127.0.0.1:9200"], loop=loop) 200 | 201 | async def get_info(self): 202 | return await self.es.info() 203 | 204 | 205 | class MyService: 206 | 207 | def __init__(self): 208 | self.aioes_service = AioESService() 209 | 210 | async def get_es_info(self): 211 | return await self.aioes_service.get_info() 212 | 213 | 214 | async def hello_aioes(request): 215 | my_service = MyService() 216 | cluster_info = await my_service.get_es_info() 217 | return web.Response(text="{}".format(cluster_info)) 218 | 219 | 220 | def create_app(loop=None): 221 | 222 | app = web.Application(loop=loop) 223 | app.router.add_route('GET', '/', hello_aioes) 224 | return app 225 | 226 | 227 | if __name__ == "__main__": 228 | web.run_app(create_app()) 229 | ``` 230 | 全部测试文件: 231 | 232 | ``` 233 | from unittest.mock import patch, MagicMock 234 | 235 | from main import AioESService, create_app 236 | 237 | 238 | class TestAioESService: 239 | 240 | async def test_get_info(self, loop): 241 | cluster_info = await AioESService("random_arg", loop=loop).get_info() 242 | assert isinstance(cluster_info, dict) 243 | 244 | 245 | class TestAcceptance: 246 | 247 | async def test_get(self, test_client, loop): 248 | with patch("main.AioESService", MagicMock( 249 | side_effect=lambda *args, **kwargs: AioESService(*args, 250 | **kwargs, 251 | loop=loop))): 252 | client = await test_client(create_app) 253 | resp = await client.get("/") 254 | assert resp.status == 200 255 | ``` 256 | 257 | 注意我们要怎么用side_effect功能来注射一个loop到`AioESService.__init__`中的。 \*args和\*\*kwargs是必须的,是为了将调用时传入的参数都传递出去。 258 | 259 | # API创建和废弃政策是什么? 260 | aiohttp尽量不会破坏现存的用户代码。 261 | 过时的属性和方法会在文档中被标记为已废弃,并且在使用时也会抛出`DeprecationWarning`警告。 262 | 废弃周期通常为一年半。 263 | 过了废弃周期后废弃的代码会被删除。 264 | 如果有新功能或bug修复强制我们打破规则我们也会打破的(比如合适地客户端cookies支持让我们打破了向后兼容政策两次!)。 265 | 所有向后不兼容的改变都会在CHANGES章节明确指出。 266 | 267 | # 如何让整个应用程序都使用gzip压缩? 268 | 这是不可能的。选择在哪不用压缩和用哪种压缩方式是一个非常棘手的问题。 269 | 如果你需要全局压缩——写一个自定义中间件吧。或在NGINX中开启(:smirk: 你正在部署反向代理是不是)。 270 | 271 | # 在web服务器中如何管理ClientSession? 272 | aiohttp.ClientSession在服务器的生命周期中只应该被创建一次,因为可以更好地利用连接池。 273 | Session在内部保存cookies。如果你不需要cookies使用aiohttp.DummyCookieJar就行。如果你需要在不同的http调用中使用不同的cookies,但在逻辑链的处理时使用的是同一个aiohttp.TCPConnector,那把own_connector设置为False。 274 | 275 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /aiohttp文档/Glossary.md: -------------------------------------------------------------------------------- 1 | # 相关名词释义 2 | ## aiodns 3 |     异步DNS解决方案 4 |     https://pypi.python.org/pypi/aiodns 5 | 6 | ## asyncio 7 |     写单线程多并发代码的协程库,通过套接字和其他资源进行多路复用的I/O访问,运行网络客户端和服务器 ,还有其他相关内容。 8 |     相关参考信息请看PEP 3156 9 |     https://pypi.python.org/pypi/asyncio/ 10 | 11 | ## callable 12 |     任何可调用的对象。可以使用callable()进行检测是不是一个callable对象。 13 | 14 | ## cchardet 15 |     `cChardet`是一个高速的通用编码发现引擎。 16 |     https://pypi.python.org/pypi/cchardet/ 17 | 18 | ## chardet 19 |     通用编码发现引擎。 20 |     https://pypi.python.org/pypi/chardet/ 21 | 22 | ## gunicorn 23 |     Gunicorn “绿色独角兽”是一个UNIX下的Python WSGI HTTP 服务器。 24 |     http://gunicorn.org/ 25 | 26 | ## IDNA 27 |     应用程序中的国际化域名(IDNA)是为了编码互联网域名的一个行业标准,基于特定的语言模板和字母,如阿拉伯语,汉语,西里尔文,泰米尔文,希伯来文或以拉丁字母为基础的变音字符还有像法语一样的连字字符。这些写作系统经由电脑编码成多字节Unicode字符。国际化的域名以ASCII的形式保存到域名系统中,使用Punycode编码互转。 28 | 29 | ## keep-alive 30 |     一个可以让HTTP客户端和服务器间保持通讯的技术。一旦进行连接并发送完一次响应后并不关闭连接,而是保持打开状态以使用同一个套接字发送下一次请求。 31 |     此技术可以让通讯变得飞快,因为无需为每次请求都建立一遍连接。 32 | 33 | ## nginx 34 |     Nginx [engine x] 是一个HTTP和反向代理服务器,邮件代理服务器和通用 TCP/UDP代理服务器。 35 |     https://nginx.org/en/ 36 | 37 | ## percent-encoding 38 |     是一个在URL中编码信息的机制,当URL中有不合适的字符时一般会进行编码。 39 | 40 | ## requoting 41 |     对不合理的字符进行百分号编码(percent-encoding)或编码后的字符进行解码。 42 | 43 | ## resource 44 |     一个映射HTTP路径的概念,每个资源(resource)相当于一个URI。 45 |     可以有一个独立的名称。 46 |     基于不同的HTTP方法,会包含不同的路由。 47 | 48 | ## route 49 |     资源(resource)的一部分,资源(resource)的路径和HTTP方法组成路由(route)。 50 | 51 | ## web-handler 52 |     用于返回HTTP响应的终端。 53 | 54 | ## websocket 55 |     是一个在同一个TCP连接上进行全双工通讯的协议。WebSocket协议由IETF标准化写入RFC 6455。 56 | 57 | ## yarl 58 |     一个操作URL对象的库。 59 | 60 |     https://pypi.python.org/pypi/yarl 61 | 62 | 63 | -------------------------------------------------------------------------------- /aiohttp文档/Helper.md: -------------------------------------------------------------------------------- 1 | 该文档介绍所有子模块——`客户端`,`multipart`,`协议`和`工具类`中要被加载到aiohttp命名空间的名称信息。 2 | 3 | 4 | # WebSocket 工具类 5 | *class aiohttp.WSCloseCode* 6 |    一个保留关闭消息码的整数枚举类。 7 |    **OK** 8 |        正常结束,表示目标连接已经成功建立。 9 | 10 |    **GOING_AWAY** 11 |        表示服务器正在关闭或浏览器已离开页面。 12 | 13 |    **PROTOCOL_ERROR** 14 |        表示由于协议错误引起的终止连接。 15 | 16 |    **UNSUPPORTED_DATA** 17 |        表示因接收到不能接受到的数据类型引起的终止连接。(比如只能接受文本的端口却接受了二进制消息) 18 | 19 |    **INVALIED_TEXT** 20 |        表示因接受到的数据中含有不能理解的消息类型引起的终止连接。(如在文本消息中出现非UTF-8编码的内容) 21 | 22 |    **POLICY_VIOLATION** 23 |        表示因接收到的信息违反规定引起的终止连接。如果没有合适的状态码会返回通用状态码(比如`unsupported_data`或`message_too_big`)。 24 | 25 |     **MESSAGE_TOO_BIG** 26 |        表示因接受的消息(数量)太大引起的终止连接。 27 | 28 |     **MANDATORY_EXTENSION** 29 |        表示因客户端期望与服务器协商更多的扩展类型但服务器没用在响应消息中回复此类内容引起的终止连接。扩展列表需要在Close帧中的/reason/部分显示。注意该状态码不会被服务器端使用,因为此状态码已代表WebSocket握手失败。 30 | 31 |     **INTERNAL_ERROR** 32 |        表示服务器端因遇到一个期望之外的错误无法完成请求而引起的终止连接。 33 | 34 |     **SERVICE_RESTART** 35 |        服务重启。客户端需要重新连接,如果确定重连需要等5-30S不等的时间。 36 | 37 |     **TRY_AGAIN_LATER** 38 |        服务过载。客户端需要连接到不同的IP地址(如果有的话)或尝试重新连接。 39 | 40 | *class aiohttp.WSMsgType* 41 |     描述`WSMessage`类型的`整数枚举(IntEnum)`类。 42 | 43 |     **CONTINUATION** 44 |        用于连接帧的标记,用户不会收到此消息类型。 45 | 46 |     **TEXT** 47 |        文本消息,值为字符串。 48 | 49 |     **BINARY** 50 |        二进制类型,值为字节码。 51 | 52 |     **PING** 53 |        代表Ping帧(由客户端发送)。 54 | 55 |     **PONG** 56 |        代表Pong帧,用于回复ping。由服务器发送。 57 | 58 |     **CLOSE** 59 |        代表Close帧。 60 | 61 |     **CLOSED FRAME** 62 |        不是一个帧,只是一个代表websocket已被关闭的标志。 63 | 64 |     **ERROR** 65 |        不是一个帧,只是一个代表websocket接受到一个错误的标志。 66 | 67 | *class aiohttp.WSMessage* 68 |     WebSocket信息,由 `.receive()`调用得到。 69 |    **type(类型)** 70 |        消息类型,是一个`WSMsgType`实例对象。 71 | 72 |    **data(数据)** 73 |        消息载体。 74 |        1. `WSMsgType.TEXT`消息的类型为`str`。 75 |        2. `WSMsgType.BINARY`消息的类型为`bytes`。 76 |        3. `WSMsgType.CLOSE`消息的类型为`WSCloseCode`。 77 |        4.`WSMsgType.PING`消息的类型为`bytes`。 78 |        5. `WSMsgType.PONG`消息的类型为`bytes`。 79 | 80 |     **extra** 81 |        额外信息,类型为字符串。 82 |        只对`WSMsgType.CLOSE`消息有效,内容包含可选的消息描述。 83 | 84 |     **json(\*, loads=json.loads)** 85 |        返回已解析的JSON数据。 86 |        新增于0.22版本。 87 |        **参数:** loads - 自定义JSON解码函数。 88 | 89 |     **tp** 90 |        不赞成使用的**type**别名函数。 91 |        1.0版本后不再建议使用。 92 | 93 | # 信号 94 | 信号是一个包含注册过的异步回调函数的列表。 95 | 信号的生命周期有两个阶段: 在信号的内容被标准列表操作所填充之后: `sig.append()`之类的。 96 | 第二个是`sig.freeze()`调用之后,在这之后信号会被冻结: 添加,删除和丢弃回调函数都是被禁止的。 97 | 唯一可做的就是调用之前已经注册过的回调函数: `await sig.send(data)` 。 98 | 99 | 更多实用例子请看`aiohttp.web`中的信号章节。 100 | 101 | *class aiohttp.Signal* 102 |     信号组件,具有`collections.abc.MutableSequence`接口。 103 | 104 |     *coroutine send(\*args, \*\*kwargs)* 105 |        从列表头部开始逐个调用已注册的回调函数。 106 | 107 |     *frozen* 108 |       如果 `freeze()`被调用过则为`True`。该属性只读。 109 | 110 |     *freeze()* 111 |        冻结列表。在这之后所有内容均不允许改动。 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /aiohttp文档/Introduce.md: -------------------------------------------------------------------------------- 1 | aiohttp是一个为Python提供异步HTTP 客户端/服务端编程,基于asyncio(Python用于支持异步编程的标准库)的异步库。 2 | 3 | ## 核心功能: 4 | 5 | * 同时支持客户端使用服务端使用。 6 | * 同时支持服务端WebSockets组件客户端WebSockets组件,开箱即用呦。 7 | * web服务器具有中间件信号组件和可插拔路由的功能。 8 | 9 | ## aiohttp库安装: 10 | `$ pip install aiohttp` 11 | 12 | 你可能还想安装更快的cchardet库来代替chardet进行解码: 13 | `$ pip install cchardet` 14 | 15 | 对于更快的客户端API DNS解析方案,aiodns是个很好的选择,极力推荐: 16 | `$ pip install aiodns` 17 | 18 | ## 快速开始: 19 | 客户端例子: 20 | ``` 21 | import aiohttp 22 | import asyncio 23 | import async_timeout 24 | 25 | async def fetch(session, url): 26 | with async_timeout.timeout(10): 27 | async with session.get(url) as response: 28 | return await response.text() 29 | 30 | async def main(): 31 | async with aiohttp.ClientSession() as session: 32 | html = await fetch(session, 'http://python.org') 33 | print(html) 34 | 35 | loop = asyncio.get_event_loop() 36 | loop.run_until_complete(main()) 37 | ``` 38 | 39 | 服务端例子: 40 | ``` 41 | from aiohttp import web 42 | 43 | async def handle(request): 44 | name = request.match_info.get('name', "Anonymous") 45 | text = "Hello, " + name 46 | return web.Response(text=text) 47 | 48 | app = web.Application() 49 | app.router.add_get('/', handle) 50 | app.router.add_get('/{name}', handle) 51 | 52 | web.run_app(app) 53 | ``` 54 | 55 | ### 注意: 56 | > 这篇文档的所有例子都是利用 *async/await* 语法来完成的,此语法介绍请看PEP 492,此语法仅Python 3.5+有效。 57 | > 如果你使用的是Python 3.4, 请将await替换成yield from,将async 替换成带有 @corotine装饰器的def. 比如: 58 | > ``` 59 | > async def coro(...): 60 | > ret = await f() 61 | > ``` 62 | > 应替换为: 63 | > ``` 64 | > @asyncio.coroutine 65 | > def coro(...): 66 | > ret = yield from f() 67 | > ``` 68 | 69 | ## 服务端指南: 70 | Polls tutorial 71 | 72 | ## 源码: 73 | 74 | 该项目托管在Github. 75 | 76 | 如果你发现了一个bug或有一些改善的建议请随时提交。 77 | 78 | 该库使用Travis进行持续集成。 79 | 80 | ## 程序依赖: 81 | * Python 3.4.2+ 82 | 83 | * *chardet* 84 | 85 | * *multidict* 86 | 87 | * *async_timeout* 88 | 89 | * *yarl* 90 | 91 | * 可选更快的cchardet代替chardet。 92 | 93 | 可通过下面命令的安装: 94 | 95 | `$ pip install cchardet` 96 | 97 | 可选aiodns进行DNS快速解析。极力推荐。 98 | `$ pip install aiodns` 99 | 100 | ## 交流渠道: 101 | *aio-libs* 谷歌交流群: https://groups.google.com/forum/#!forum/aio-libs 102 | 103 | 随时在这里交流你的问题和想法。 104 | 105 | *gitter 聊天* https://gitter.im/aio-libs/Lobby 106 | 107 | 我们还支持Stack Overflow. 在你的问题上添加aiohttp标签即可。 108 | 109 | ## 贡献 110 | 请在写一个PR前阅读下贡献须知。 111 | 112 | ## 作者和授权 113 | `aiohttp` 大部分由 Nikolay Kim 和 Andrew Svetlov编写. 114 | 115 | 使用 Apache 2 授权并可随意使用。 116 | 117 | 随时在GitHub上提交PR来改善此项目。 118 | 119 | ## 对后续不再兼容的更改所采用的策略 120 | 一般的更改aiohttp 保持向后兼容. 121 | 122 | 在废弃某些公开API(方法,类,函数参数等.)后仍保证可以使用这些被废弃的API至少一年半的时间直到某新版本完全弃用。 123 | 124 | 所有废弃的东西都会反映在文档中并给出**已废弃**提示。 125 | 126 | 有时我们会因一些必须要做的理由而打破某些我们定的规则。大多数原因是有只能通过修改主要API解决的BUG出现,但我们会尽可能不让这种事情发生。 127 | 128 | ## 目录: 129 | 打开此链接看完整的目录。 -------------------------------------------------------------------------------- /aiohttp文档/Logging.md: -------------------------------------------------------------------------------- 1 | # 日志 2 | aiohttp使用标准库logging追踪库活动。 3 | 4 | aiohttp中有以下日志记录器(以名字排序): 5 | 6 | * 'aiohttp.access' 7 | * 'aiohttp.client' 8 | * 'aiohttp.internal' 9 | * 'aiohttp.server' 10 | * 'aiohttp.web' 11 | * 'aiohttp.websocket' 12 | 13 | 你可以追踪这些记录器来查看日志信息。此文档中没有关于配置日志追踪的说明,logging.config.dictConfig()对于将记录器配置到应用程序中来说已经很好用了。 14 | 15 | # 访问日志 16 | 默认情况下访问日志是开启的使用的是'aiohttp.access'记录器。 17 | 可以调用aiohttp.web.Applicaiton.make_handler()来控制日志。 18 | 将logging.Logger实例通过access_log参数传递即可覆盖默认记录器。 19 | 20 | ### 注意 21 | 可以使用 web.run_app(app, access_log=None)来禁用记录访问日志。 22 | 其他参数如*access_log_format*可以指定日志格式(详情见下文) 23 | 24 | # 指定格式 25 | aiohttp提供自定义的微语言来指定请求和响应信息: 26 | 27 | 选项 |含义 28 | ----|---- 29 | %% | 百分号 30 | %a | 远程IP地址(如果使用了反向代理则为代理IP地址) 31 | %t | 请求被处理时的时间 32 | %P | 处理请求的ID。 33 | %r | 请求的首行。 34 | %s | 响应状态码。 35 | %b | 响应字节码的大小,包括HTTP头。 36 | %T | 处理请求的时间,单位为秒。 37 | %Tf | 处理请求的时间,秒的格式为 %.06f。 38 | %D | 处理请求的时间,单位为毫秒。 39 | %{FOO}i |request.headers['FOO'] 40 | %{FOO}o |response.headers['FOO'] 41 | 42 | 访问日志的默认格式为: 43 | ``` 44 | '%a %l %u %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i"' 45 | ``` 46 | 47 | **aiohttp.helper.AccessLogger**替换例子: 48 | ``` 49 | class AccessLogger(AbstractAccessLogger): 50 | 51 | def log(self, request, response, time): 52 | self.logger.info(f'{request.remote} ' 53 | f'"{request.method} {request.path} ' 54 | f'done in {time}s: {response.status}') 55 | ``` 56 | 57 | ### 注意 58 | 如果使用了Gunicorn来部署,默认的访问日式格式会自动被替换为aiohttp的默认访问日志格式。 59 | 如果要指定Gunicorn的访问日志格式,需要使用aiohttp的格式标识。 60 | 61 | # 错误日志 62 | aiohttpw.web使用`aiohttp.server`来存储处理web请求时的错误日志。 63 | 默认情况下是开启的。 64 | 使用不同的记录器名字请在调用`aiohttp.web.Application.make_handler()`时指定`logge`参数的值(需要`logging.Logger`实例对象)。 65 | 66 | -------------------------------------------------------------------------------- /aiohttp文档/LowLevelServer.md: -------------------------------------------------------------------------------- 1 | # 底层服务器 2 | 这一节介绍了aiohttp.web的基础底层服务器。 3 | 4 | # 抽象基础 5 | 有时候用户不需要更高级的封装,像是 application,routers和signals。 6 | 只是需要一个支持异步调用并且是接受请求返回响应对象的东西。 7 | 在aiohttp.web.Server类中有介绍过一个服务协议工厂——`asyncio.AbstractEventLoop.create_server()`,并可以将数据流桥接到web处理器以及反馈结果。 8 | 底层web处理器应该接收单个`BaseRequest`参数并且执行下列中的其中一个: 9 | 1. 返回一个包含HTTP响应体的响应对象。 10 | 2. 创建一个`StreamResponse`对象,然后可以调用`StreamResponse.prepare()`发送头信息,调用`StreamResponse.write() / StreamResponse.drain()`发送数据块,最后结束响应。 11 | 3. 抛出`HTTPException`派生的异常(看Exception部分)。 12 | 4. 使用`WebSocketResponse`发起/处理Web-Socket连接。 13 | 14 | # 运行基础底层服务器 15 | 16 | 请看下列代码: 17 | ``` 18 | import asyncio 19 | from aiohttp import web 20 | 21 | 22 | async def handler(request): 23 | return web.Response(text="OK") 24 | 25 | 26 | async def main(loop): 27 | server = web.Server(handler) 28 | await loop.create_server(server, "127.0.0.1", 8080) 29 | print("======= Serving on http://127.0.0.1:8080/ ======") 30 | 31 | # pause here for very long time by serving HTTP requests and 32 | # waiting for keyboard interruption 33 | await asyncio.sleep(100*3600) 34 | 35 | 36 | loop = asyncio.get_event_loop() 37 | 38 | try: 39 | loop.run_until_complete(main(loop)) 40 | except KeyboardInterrupt: 41 | pass 42 | loop.close() 43 | ``` 44 | 45 | 这样我们有了一个返回"OK"标准响应的处理器。 46 | 47 | 这个处理器经由服务器调用。调用`loop.create_server`创建的网络交流通道,随后可以访问http://127.0.0.1:8080/来查看。 48 | 这个处理器可以接受所有的请求: 不论`GET, POST, Web-Socket`都可以,无论哪一个路径的访问也都同样由其处理。 49 | 不过也同样很基础: 无论如何处理器都只返回`200 OK`。实际生活中所产生的状态要复杂的多。 50 | 51 | -------------------------------------------------------------------------------- /aiohttp文档/ServerDeployment.md: -------------------------------------------------------------------------------- 1 | # 服务器部署 2 | 关于aiohttp服务器部署,这里有以下几种选择: 3 | 1. 独立的服务器。 4 | 2. 使用nginx, HAProxy等反向代理服务器,之后是后端服务器。 5 | 3. 在反向代理之后在部署一层gunicorn,然后才是后端服务器。 6 | 7 | # 独立服务器 8 | 只需要调用`aiohttp.web.run_app()`,并传递`aiohttp.web.Application`实例即可。 9 | 该方法最简单,也是在比较小的程序中最好的解决方法。但该方法并不能完全利用CPU。 10 | 如果要运行多个aiohttp服务器实例请用反向代理。 11 | 12 | # Nginx + supervisord 13 | 将aiohttp服务器组运行在nginx之后有好多好处。 14 | 首先,nginx是个很好的前端服务器。它可以预防很多攻击如格式错误的http协议的攻击。 15 | 第二,部署nginx后可以同时运行多个aiohttp实例,这样可以有效利用CPU。 16 | 最后,nginx提供的静态文件服务器要比aiohttp内置的静态文件支持快很多。 17 | 18 | 但部署它也意味着更复杂的配置。 19 | 20 | 21 | ## 配置Nginx 22 | 23 | 下面是一份简短的配置Nginx参考,并没涉及到所有的Nginx选项。 24 | 25 | 你可以阅读Nginx指南官方文档来找到所有的参考。 26 | 27 | 好啦,首先我们要配置HTTP服务器本身: 28 | ``` 29 | http { 30 | server { 31 | listen 80; 32 | client_max_body_size 4G; 33 | 34 | server_name example.com; 35 | 36 | location / { 37 | proxy_set_header Host $http_host; 38 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 39 | proxy_redirect off; 40 | proxy_buffering off; 41 | proxy_pass http://aiohttp; 42 | } 43 | 44 | location /static { 45 | # path for static files 46 | root /path/to/app/static; 47 | } 48 | 49 | } 50 | } 51 | ``` 52 | 53 | 这样配置之后会监听80端口,服务器名为`example.com`,所有的请求都会被重定向到aiohttp后端处理组。 54 | 静态文件也同样适用,/path/to/app/static路径的静态文件访问时就变成了 example.com/static。 55 | 56 | 接下来我们配置aiohttp上游组件: 57 | ``` 58 | http { 59 | upstream aiohttp { 60 | # fail_timeout=0 means we always retry an upstream even if it failed 61 | # to return a good HTTP response 62 | 63 | # Unix domain servers 64 | server unix:/tmp/example_1.sock fail_timeout=0; 65 | server unix:/tmp/example_2.sock fail_timeout=0; 66 | server unix:/tmp/example_3.sock fail_timeout=0; 67 | server unix:/tmp/example_4.sock fail_timeout=0; 68 | 69 | # Unix domain sockets are used in this example due to their high performance, 70 | # but TCP/IP sockets could be used instead: 71 | # server 127.0.0.1:8081 fail_timeout=0; 72 | # server 127.0.0.1:8082 fail_timeout=0; 73 | # server 127.0.0.1:8083 fail_timeout=0; 74 | # server 127.0.0.1:8084 fail_timeout=0; 75 | } 76 | } 77 | ``` 78 | 79 | 所有来自`http://example.com`的HTTP请求(除了`http://example.com/static`)都将重定向到example1.sock, example2.sock, example3.sock 或 example4.sock后端服务器。默认情况下,Nginx使用轮询调度算法(round-robin)来选择后端服务器。 80 | 81 | ### 注意 82 | Nginx 并不是反向代理的唯一选择,不过却是最流行的。你也可以选择HAProxy。 83 | 84 | ## Supervisord 85 | 86 | 配置完Nginx,我们要开始配置aiohttp后端服务器了。使用些工具可以在系统重启或后端出现错误时更快地自动启动。 87 | 我们有很多工具可以选择: Supervisord, Upstart, Systemd, Gaffer, Runit等等。 88 | 我们用Supervisord当做例子: 89 | ``` 90 | [program:aiohttp] 91 | numprocs = 4 92 | numprocs_start = 1 93 | process_name = example_%(process_num)s 94 | 95 | ; Unix socket paths are specified by command line. 96 | command=/path/to/aiohttp_example.py --path=/tmp/example_%(process_num)s.sock 97 | 98 | ; We can just as easily pass TCP port numbers: 99 | ; command=/path/to/aiohttp_example.py --port=808%(process_num)s 100 | 101 | user=nobody 102 | autostart=true 103 | autorestart=true 104 | ``` 105 | 106 | ## aiohtto服务器 107 | 最后我们要让aiohttp服务器在supervisord上工作。 108 | 假设我们已经正确配置`aiohttp.web.Application`,端口也被正确指定,这些工作挺烦的: 109 | ``` 110 | # aiohttp_example.py 111 | import argparse 112 | from aiohttp import web 113 | 114 | parser = argparse.ArgumentParser(description="aiohttp server example") 115 | parser.add_argument('--path') 116 | parser.add_argument('--port') 117 | 118 | 119 | if __name__ == '__main__': 120 | app = web.Application() 121 | # configure app 122 | 123 | args = parser.parse_args() 124 | web.run_app(app, path=args.path, port=args.port) 125 | ``` 126 | 当然在真实环境中我们还要做些其他事情,比如配置日志等等,但这些事情不在本章讨论范围内。 127 | 128 | # Nginx + Gunicorn 129 | 我们还可以使用Gunicorn来部署aiohttp,Gunicorn基于pre-fork worker模式。Gunicorn将你的app当做worker进程来处理即将到来的请求。 130 | 与部署Ngnix相反,使用Gunicorn不需要我们手动启动aiohttp进程,也不需要使用如supervisord之类的工具进行监控。但这并不是没有代价:在Gunicorn下运行aiohttp应用会有些许缓慢。 131 | 132 | ## 准备环境 133 | 在做以上操作之前,我们首先要做的就是配置我们的部署环境。本章例子基于*Ubuntu 14.04*。 134 | 135 | 首先为应用程序创建个目录: 136 | ``` 137 | >> mkdir myapp 138 | >> cd myapp 139 | ``` 140 | 在Ubuntu中使用pyenv有一个bug,所以我们需要做些额外的操作才能配置虚拟环境: 141 | ``` 142 | >> pyvenv-3.4 --without-pip venv 143 | >> source venv/bin/activate 144 | >> curl https://bootstrap.pypa.io/get-pip.py | python 145 | >> deactivate 146 | >> source venv/bin/activate 147 | ``` 148 | 好啦,虚拟环境我们已经配置完了,之后我们要安装aiohttp和gunicorn: 149 | ``` 150 | >> pip install gunicorn 151 | >> pip install -e git+https://github.com/aio-libs/aiohttp.git#egg=aiohttp 152 | ``` 153 | 154 | ## 应用程序 155 | 我们写一个简单的应用程序,将其命名为 my_app_module.py: 156 | ``` 157 | from aiohttp import web 158 | 159 | def index(request): 160 | return web.Response(text="Welcome home!") 161 | 162 | 163 | my_web_app = web.Application() 164 | my_web_app.router.add_get('/', index) 165 | ``` 166 | 167 | ## 启动Gunicorn 168 | 启动Gunicorn时我们要将模块名字(如*my_app_module*)和应用程序的名字(如*my_web_app*)传入,可以一起在配置Gunicorn其他选项时写入也可以写在配置文件中。 169 | 本章例子所使用到的选项: 170 | * -bind 用于设置服务器套接字地址。 171 | * -worker-class 表示使用我们自定义的worker代替Gunicorn的默认worker。 172 | * 你可能还想用 -workers 让Gunicorn知道应该用多少个worker来处理请求。(建议的worker数量请看 设置多少个worker合适?) 173 | 174 | ``` 175 | >> gunicorn my_app_module:my_web_app --bind localhost:8080 --worker-class aiohttp.GunicornWebWorker 176 | [2015-03-11 18:27:21 +0000] [1249] [INFO] Starting gunicorn 19.3.0 177 | [2015-03-11 18:27:21 +0000] [1249] [INFO] Listening at: http://127.0.0.1:8080 (1249) 178 | [2015-03-11 18:27:21 +0000] [1249] [INFO] Using worker: aiohttp.worker.GunicornWebWorker 179 | [2015-03-11 18:27:21 +0000] [1253] [INFO] Booting worker with pid: 1253 180 | ``` 181 | 现在,Gunicorn已成功运行,随时可以将请求交由应用程序的worker处理。 182 | 183 | ### 注意 184 | 如果你使用的是另一个asyncio事件循环uvloop, 你需要用`aiohttp.GunicornUVLoopWebWorker` worker类。 185 | 186 | ## 其他内容 187 | Gunicorn 文档建议将Gunicorn部署在Nginx代理服务器之后。可看下官方文档的建议。 188 | 189 | ## 配置日志 190 | `aiohttp`和`Gunicorn`使用不同的日志格式。 191 | 默认aiohttp使用自己的日志格式: 192 | ``` 193 | '%a %l %u %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i"' 194 | ``` 195 | 查看访问日志的格式来获取更多信息。 196 | -------------------------------------------------------------------------------- /aiohttp文档/ServerTutorial.md: -------------------------------------------------------------------------------- 1 | # 服务端指南 2 | 3 | 准备使用aiohttp但不知道如何开始?这里有一些小例子来快速熟悉下。接下来我们一起来试着开发一个小投票系统。 4 | 5 | 如果你想改进或与之对比学习,可以查看demo source 来获取全部源码。 6 | 7 | 准备好我们的开发环境 8 | 首先检查下python版本: 9 | ``` 10 | $ python -V 11 | Python 3.5.0 12 | ``` 13 | 我们需要python 3.5.0及以上版本。 14 | 15 | 16 | 假设你已经安装好aiohttp库了。你可以用以下命令来查询当前aiohttp库的版本。 17 | ``` 18 | $ python3 -c 'import aiohttp; print(aiohttp.__version__)' 19 | 2.0.5 20 | ``` 21 | 22 | 项目结构与其他以python为基础的web项目大同小异: 23 | ``` 24 | . 25 | ├── README.rst 26 | └── polls 27 | ├── Makefile 28 | ├── README.rst 29 | ├── aiohttpdemo_polls 30 | │ ├── __init__.py 31 | │ ├── __main__.py 32 | │ ├── db.py 33 | │ ├── main.py 34 | │ ├── routes.py 35 | │ ├── templates 36 | │ ├── utils.py 37 | │ └── views.py 38 | ├── config 39 | │ └── polls.yaml 40 | ├── images 41 | │ └── example.png 42 | ├── setup.py 43 | ├── sql 44 | │ ├── create_tables.sql 45 | │ ├── install.sh 46 | │ └── sample_data.sql 47 | └── static 48 | └── style.css 49 | ``` 50 | # 开始用aiohttp构建我们的第一个应用程序 51 | 52 | 该指南借鉴了Django投票系统指南。 53 | 54 | ## 创建应用程序 55 | aiohttp的服务端程序都是 `aiohttp.web.Application`实例对象。用于创建信号,连接路由等。 56 | 57 | 使用下列代码可以创建一个应用程序: 58 | ``` 59 | 60 | from aiohttp import web 61 | 62 | 63 | app = web.Application() 64 | web.run_app(app, host='127.0.0.1', port=8080) 65 | ``` 66 | 67 | 将其保存在`aiohttpdemo_polls/main.py`然后开启服务器: 68 | `$ python3 main.py` 69 | 70 | 你会在命令行中看到如下输出: 71 | 72 | ``` 73 | ======== Running on http://127.0.0.1:8080 ======== 74 | (Press CTRL+C to quit) 75 | ``` 76 | 在浏览器中打开 http://127.0.0.1:8080 或在命令行中使用`curl`: 77 | `$ curl -X GET localhost:8080` 78 | 79 | 啊咧,出现了404: Not Found. 呃...因为我们并没有创建路由和和展示页面。 80 | 81 | ## 创建视图 82 | 我们来一起创建第一个展示页面(视图)。我们先创建个文件`aiohttpdemo_polls/views.py`然后写入: 83 | ``` 84 | from aiohttp import web 85 | 86 | async def index(request): 87 | return web.Response(text='Hello Aiohttp!') 88 | ``` 89 | 90 | `index`就是我们创建的展示页,然后我们创建个路由连接到这个展示页上。我们来把路由放在`aiohttpdemo_polls/routes.py`文件中(将路由表和模型分开写是很好的实践。创建实际项目时可能会有多个同类文件,这样分开放可以让自己很清楚。): 91 | 92 | ``` 93 | from views import index 94 | 95 | def setup_routes(app): 96 | app.router.add_get('/', index) 97 | ``` 98 | 99 | 我们还要在`main.py`中调用`setup_routes`。 100 | ``` 101 | from aiohttp import web 102 | from routes import setup_routes 103 | 104 | 105 | app = web.Application() 106 | setup_routes(app) 107 | web.run_app(app, host='127.0.0.1', port=8080) 108 | ``` 109 | 然后我们重新开启服务器,现在我们从浏览器中访问: 110 | 111 | ``` 112 | $ curl -X GET localhost:8080 113 | Hello Aiohttp! 114 | ``` 115 | 116 | 啊哈!成功了!我们现在应该有了一个如下所示的目录结构: 117 | ``` 118 | . 119 | ├── .. 120 | └── polls 121 | ├── aiohttpdemo_polls 122 | │ ├── main.py 123 | │ ├── routes.py 124 | │ └── views.py 125 | ``` 126 | 127 | ## 使用配置文件 128 | aiohttp不需要任何配置文件,也没有内置支持任何配置架构。 129 | 但考虑到这些事实: 130 | 1. 99%的服务器都有配置文件。 131 | 2. 其他的同类程序(除了以Python为基础的像Django和Flask的)都不会将配置文件作为源码的一部分。 132 | 比如Nginx将配置文件保存在 /etc/nginx文件夹里。 133 | mongo则保存在 /etc/mongodb.conf里。 134 | 3. 使用配置文件是公认的好方法,在部署产品时可以预防一些小错误。 135 | 136 | 所以我们建议用以下途径(进行配置文件): 137 | 1. 将配置信息写在yaml文件中。(json或ini都可以,但yaml最好用。) 138 | 2. 在一个预先设定好的目录中加载yaml。 139 | 3. 拥有能通过命令行来设置配置文件的功能。如: ./run_app --config=/opt/config/app_cfg.yaml 140 | 4. 对要加载的字典执行严格检测,以确保其数据类型符合预期。可以使用: trafaret, colander 或 JSON schema等库。 141 | 142 | 以下代码会加载配置文件并设置到应用程序中: 143 | ``` 144 | # load config from yaml file in current dir 145 | conf = load_config(str(pathlib.Path('.') / 'config' / 'polls.yaml')) 146 | app['config'] = conf 147 | ``` 148 | 149 | ## 构建数据库 150 | ### 准备工作 151 | 这份指南中我们使用最新版的`PostgreSQL`数据库。 你可访问以下链接下载: http://www.postgresql.org/download/ 152 | 153 | ### 数据库架构 154 | 我们使用`SQLAlchemy`来写数据库架构。我们只要创建两个简单的模块——`question`和`choice`: 155 | ``` 156 | import sqlalchemy as sa 157 | 158 | meta = sa.MetaData() 159 | 160 | question - sq.Table( 161 | 'question', meta, 162 | sa.Column('id', sa.Integer, nullable=False), 163 | sa.Column('question_text', sa.String(200), nullable=False), 164 | sa.Column('pub_date', sa.Date, nullable=False), 165 | # Indexes # 166 | sa.PrimaryKeyConstraint('id', name='question_id_pkey') 167 | ) 168 | 169 | choice = sa.Table( 170 | 'choice', meta, 171 | sa.Column('id', sa.Integer, nullable=False), 172 | sa.Column('question_id', sa.Integer, nullable=False), 173 | sa.Column('choice_text', sa.String(200), nullable=False), 174 | sa.Column('votes', server_default="0", nullable=False), 175 | # Indexes # 176 | sa.PrimayKeyConstraint('id', name='choice_id_pkey'), 177 | sa.ForeignKeyContraint(['question_id'], [question.c.id], 178 | name='choice_question_id_fkey', 179 | ondelete='CASCADE'), 180 | ) 181 | ``` 182 | 183 | 你会看到如下数据库结构: 184 | 185 | 第一张表 question: 186 | |question| 187 | |id| 188 | |question_text| 189 | |pub_date| 190 | 191 | 第二张表 choice: 192 | |choice| 193 | |id| 194 | |choice_text| 195 | |votes| 196 | |question_id| 197 | 198 | ### 创建连接引擎 199 | 为了从数据库中查询数据我们需要一个引擎实例对象。假设`conf`变量是一个带有连接信息的配置字典,`Postgre`s会使用异步的方式完成该操作: 200 | ``` 201 | async def init_pg(app): 202 | conf = app['config'] 203 | engine = await aiopg.sa.create_engine( 204 | database=conf['database'], 205 | user=conf['user'], 206 | password=conf['password'], 207 | host=conf['host'], 208 | port=conf['host'], 209 | minsize=conf['minsize'], 210 | maxsize=conf['maxsize']) 211 | 212 | app['db'] = engine 213 | ``` 214 | 最好将连接数据库的函数放在`on_startup`信号中: 215 | ``` 216 | app.on_startup.append(init_pg) 217 | ``` 218 | 219 | ### 关闭数据库 220 | 程序退出时一块关闭所有的资源接口是一个很好的做法。 221 | 使用on_cleanup信号来关闭数据库接口: 222 | ``` 223 | async def close_pg(app): 224 | app['db'].close() 225 | await app['db'].wait_closed() 226 | 227 | app.on_cleanup.append(close_pg) 228 | ``` 229 | 230 | ## 使用模板 231 | 232 | 我们来添加些更有用的页面: 233 | ``` 234 | @aiohttp_jinja2.template('detail.html') 235 | async def poll(request): 236 | async with request['db'].acquire() as conn: 237 | question_id = request.match_info['question_id'] 238 | try: 239 | question, choices = await db.get_question(conn, 240 | question_id) 241 | except db.RecordNotFound as e: 242 | raise web.HTTPNotFound(text=str(e)) 243 | return { 244 | 'question': question, 245 | 'choices': choices 246 | } 247 | ``` 248 | 249 | 编写页面时使用模板是很方便的。我们返回带有页面内容的字典,`aiohttp_jinja2.template`装饰器会用`jinja2`模板加载它。 250 | 251 | 当然我们要先安装下`aiohttp_jinja2`: 252 | ``` 253 | $ pip install aiohttp_jinja2 254 | 255 | ``` 256 | 安装完成后我们使用时要适配下: 257 | ``` 258 | import aiohttp_jinja2 259 | import jinja2 260 | 261 | aiohttp_jinja2.setup( 262 | app, loader=jinja2.PackageLoader('aiohttpdemo_polls', 'templates')) 263 | ``` 264 | 265 | 我们将其放在`polls/aiohttpdemo_polls/templates`文件夹中。 266 | 267 | ## 静态文件 268 | 每个web站点都有一些静态文件: 图片啦,JavaScript,CSS文件啦等等。 269 | 在生产环境中处理这些静态文件最好的方法是使用NGINX或CDN服务做反向代理。 270 | 但在开发环境中使用aiohttp服务器处理静态文件是很方便的。 271 | 272 | 只需要简单的调用一个信号即可: 273 | ``` 274 | app.router.add_static('/static/', 275 | path=str(project_root / 'static'), 276 | name='static') 277 | ``` 278 | project_root表示根目录。 279 | 280 | ## 使用中间件 281 | 中间件是每个web处理器必不可少的组件。它的作用是在处理器处理请求前预处理请求以及在得到响应后发送出去。 282 | 283 | 我们下面来实现一个用于显示漂亮的404和500页面的简单中间件。 284 | ``` 285 | def setup_middlewares(app): 286 | error_middleware = error_pages({404: handle_404, 287 | 500: handle_500}) 288 | app.middlewares.append(error_middleware) 289 | ``` 290 | 291 | 中间件(middleware)本身是一个接受*应用程序(application)*和*后续处理器(next handler)*的加工厂。 292 | 293 | 中间件工厂返回一个与web处理器一样接受请求并返回响应的中间件处理器。 294 | 295 | 下面实现一个用于处理HTTP异常的中间件: 296 | ``` 297 | def error_pages(overrides): 298 | async def middleware(app, handler): 299 | async def middleware_handler(request): 300 | try: 301 | response = await handler(request) 302 | override = overrides.get(response.status) 303 | if override is None: 304 | return response 305 | else: 306 | return await override(request, response) 307 | except web.HTTPException as ex: 308 | override = overrides.get(ex.status) 309 | if override is None: 310 | raise 311 | else: 312 | return await override(request, ex) 313 | return middleware_handler 314 | return middleware 315 | ``` 316 | 317 | 318 | 这些`overrides(handle_404和handle_500)`只是简单的用`Jinja2`模板渲染: 319 | ``` 320 | async def handle_404(request, response): 321 | response = aiohttp_jinja2.render_template('404.html', 322 | request, 323 | {}) 324 | return response 325 | 326 | 327 | async def handle_500(request, response): 328 | response = aiohttp_jinja2.render_template('500.html', 329 | request, 330 | {}) 331 | return response 332 | ``` 333 | 334 | ### 详情看 Middlewares. 335 | 336 | -------------------------------------------------------------------------------- /aiohttp文档/ServerUsage.md: -------------------------------------------------------------------------------- 1 | # 服务端使用 2 | 3 | # 启动一个简单地Web服务器 4 | 5 | 部署web服务器首先要创建一个 请求处理器(request handler)。 6 | 请求处理器可以是协程方法也可以是普通方法,它只有一个用于接受`Request`实例对象的参数,之后会返回`Response`实例对象: 7 | 8 | ``` 9 | from aiohttp import web 10 | 11 | async def hello(request): 12 | return web.Response(text="Hello, world") 13 | ``` 14 | 15 | 再之后我们需要创建应用(Appliaction)并将请求处理器配置到应用的路由,注意选择合适的HTTP方法和请求路径: 16 | 17 | ``` 18 | app = web.Application() 19 | app.router.add_get('/', hello) 20 | ``` 21 | 22 | 我们调用`run_app()`来启动应用: 23 | ``` 24 | web.run_app(app) 25 | ``` 26 | 27 | 最后,我们访问 http://localhost:8080/ 来查看内容。 28 | 29 | ## 扩展 30 | 可查看 优雅地关闭(Graceful shutdown)一节了解`run_app()`做了什么以及如何从头开始实现复杂服务器的初始化/关闭。 31 | 32 | # 使用命令行接口(CLI) 33 | `aiohttp.web`带有一个基于TCP/IP的基本命令行接口,用于快速启动一个应用(Application)。 34 | 35 | ``` 36 | $ python -m aiohttp.web -H localhost -P 8080 package.module:init_func 37 | ``` 38 | 39 | `package.module:init_func`应是一个可导入,调用的对象,有一个接受包含命令行参数列表的参数,配置好之后返回`Application`对象: 40 | ``` 41 | def init_func(argv): 42 | app = web.Application() 43 | app.router.add_get("/", index_handler) 44 | return app 45 | ``` 46 | 47 | # 使用处理器 48 | 处理器对象可以被任意调用,它只接受`Request`实例对象,并且返回`StreamResponse`的派生对象实例(如`Response`): 49 | ``` 50 | def handler(request): 51 | return web.Response() 52 | ``` 53 | 它还可以是协程方法,这样`aiohttp.web`会等待处理: 54 | ``` 55 | async def handler(request): 56 | return web.Response() 57 | ``` 58 | 处理器必须被注册在路由上才能使用: 59 | ``` 60 | app.router.add_get('/', handler) 61 | app.router.add_post('/post', post_handler) 62 | app.router.add_put('/put', put_handler) 63 | ``` 64 | `add_route()`同样支持使用通配符方法: 65 | ``` 66 | app.router.add_route('*', '/path', all_handler) 67 | ``` 68 | 之后可以使用`Request.method`来获知请求所使用的HTTP方法。 69 | 70 | 默认情况下,`add_get()`也会接受HEAD请求,并像使用GET请求一样返回响应头。你也可以禁用它: 71 | ``` 72 | app.router.add_get('/', handler, allow_head=False) 73 | ``` 74 | 如果处理器不能被调用,服务器将会返回405。 75 | ### 注意 76 | 根据`RFC 7231` aiohttp 2.0版本后做了接受HEAD请求的调整,使用之前版本并且用`add_get()`添加的请求,如果使用HEAD方法访问会返回405。 77 | 78 | 如果处理器会写入很多响应体内容,你可以在执行HEAD方法时跳过处理响应体内容以提高执行效率。 79 | 80 | # 使用资源和路由 81 | 82 | 内部路由是一个资源列表。 83 | 资源是路由表的入口,相当于所请求的URL。 84 | 每个资源至少有一个路由。 85 | 路由负责调用web处理器进行处理。 86 | 87 | `UrlDispatcher.add_get() / UrlDispatcher.add_post()` 及其同类方法是 `UrlDispatcher.add_route(`)的一种简便写法。 88 | 同样 `UrlDispatcher.add_route()` 是 `UrlDispatcher.add_resource()` 和`Resource.add_route()` 的一种简便写法。 89 | 90 | ``` 91 | resource = app.router.add_resource(path, name=name) 92 | route = resource.add_route(method, handler) 93 | return route 94 | ``` 95 | 96 | ### 扩展: 97 | 查看 Router refactoring in 0.21 获取更多信息。 98 | 99 | ## 使用可变形资源 100 | 资源可以是可变的路径。比如,如果某一路径是`'/a/{name}/c'`,那`'/a/b/c', '/a/1/c','/a/etc/c'`,这样的路径就都可以匹配到。 101 | 可变部分需要用 `{标识字符}` 这样的形式,标识字符的作用是让处理器可以匹配到这个值。使用`Request.match_info`来完成这一操作: 102 | ``` 103 | async def variable_handler(request): 104 | return web.Response( 105 | text="Hello, {}".format(request.match_info['name'])) 106 | 107 | resource = app.router.add_resource('/{name}') 108 | resource.add_route('GET', variable_handler) 109 | ``` 110 | 111 | 默认情况下,每个可变部分所使用的正则表达式是`[^{}/]+`。 112 | 113 | 当然你也可以自定义正则表达式`{标识符: 正则}`: 114 | ``` 115 | resource = app.router.add_resource(r'/{name:\d+}') 116 | ``` 117 | 118 | ### 注意: 119 | 正则表达式匹配的是非百分号编码的URL(request.raw_path)字符。比如 空格编码后是%20。 120 | RFC 3986规定可在路径中的字符是: 121 | 122 | ``` 123 | allowed = unreserved / pct-encoded / sub-delims 124 | / ":" / "@" / "/" 125 | 126 | pct-encoded = "%" HEXDIG HEXDIG 127 | 128 | unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 129 | 130 | sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 131 | / "*" / "+" / "," / ";" / "=" 132 | ``` 133 | 134 | ## 使用命名资源进行反向引用URL 135 | 136 | 路由中可以指定*name*: 137 | 138 | ``` 139 | resource = app.router.add_resource('/root', name='root') 140 | ``` 141 | 之后可以访问和构建此资源的URL: 142 | ``` 143 | >>> request.app.router['root'].url_for().with_query({"a": "b", "c": "d"}) 144 | URL('/root?a=b&c=d') 145 | ``` 146 | 147 | 带有可变形部分的资源: 148 | ``` 149 | app.router.add_resource(r'/{user}/info', name='user-info') 150 | ``` 151 | 这时传入那部分即可: 152 | ``` 153 | >>> request.app.router['user-info'].url_for(user='john_doe')\ 154 | ... .with_query("a=b") 155 | '/john_doe/info?a=b' 156 | ``` 157 | 158 | ## 将处理器放到类中使用 159 | 160 | 如上所述,处理器可以是全局函数或协同程序: 161 | ``` 162 | async def hello(request): 163 | return web.Response(text="Hello, world") 164 | app.router.add_get('/', hello) 165 | ``` 166 | 但有时将具有同样逻辑的一组路由放到类中是很方便很有用的。 167 | 不过`aiohttp.web`并未有任何该方面的规范,开发者可任意使用: 168 | ``` 169 | class Handler: 170 | 171 | def __init__(self): 172 | pass 173 | 174 | def handle_intro(self, request): 175 | return web.Response(text="Hello, world") 176 | 177 | async def handle_greeting(self, request): 178 | name = request.match_info.get('name', "Anonymous") 179 | txt = "Hello, {}".format(name) 180 | return web.Response(text=txt) 181 | 182 | handler = Handler() 183 | app.router.add_get('/intro', handler.handle_intro) 184 | app.router.add_get('/greet/{name}', handler.handle_greeting) 185 | ``` 186 | 187 | ## 基础视图类 188 | 189 | `aiohttp.web`提供django风格的基础视图类。 190 | 191 | 你可以从 `View` 类中继承,并自定义http请求的处理方法: 192 | 193 | ``` 194 | class MyView(web.View): 195 | async def get(self): 196 | return await get_resp(self.request) 197 | 198 | async def post(self): 199 | return await post_resp(self.request) 200 | ``` 201 | 202 | 处理器应是一个协程方法,且只接受self参数,并且返回标准web处理器的响应对象。请求对象可使用`View.request`中取出。 203 | 204 | 当然还需要在路由中注册: 205 | ``` 206 | app.router.add_route('*', '/path/to', MyView) 207 | ``` 208 | 这样`/path/to`既可使用GET也可使用POST进行请求,不过其他未指定的HTTP方法会抛出405错误。 209 | 210 | 211 | ## 资源视图 212 | 所有在路由中注册的资源都可使用`UrlDispatcher.resources()`查看: 213 | ``` 214 | for resource in app.router.resources(): 215 | print(resource) 216 | ``` 217 | 218 | 同样,有`name`的资源可以用`UrlDispatcher.named_resources()`来查看: 219 | ``` 220 | for name, resource in app.router.named_resources().items(): 221 | print(name, resource) 222 | ``` 223 | `UrlDispatcher.routes()` 新增于 0.18版本. 224 | `UrlDispatcher.named_routes()` 新增于 0.19版本. 225 | 226 | 0.21版本后请使用`UrlDispatcher.named_routes() / UrlDispatcher.routes()` 来代替 `UrlDispatcher.named_resources() / UrlDispatcher.resources()` 。 227 | 228 | # 其他注册路由的方式 229 | 上述例子使用常规方式添加新路由: `app.router.add_get(...)` 之类. 230 | 231 | `aiohttp`还提供两种其他方法: 路由表和路由装饰器。 232 | 233 | 路由表和Django中一样: 234 | ``` 235 | async def handle_get(request): 236 | ... 237 | 238 | 239 | async def handle_post(request): 240 | ... 241 | 242 | app.router.add_routes([web.get('/get', handle_get), 243 | web.post('/post', handle_post), 244 | ``` 245 | 使用 `aiohttp.web.get()` 或 `aiohttp.web.post()`定义处理器,并放到一个列表中然后传给`add_routes`。 246 | ### 扩展: 247 | RouteDef reference. 248 | 249 | 路由装饰器有点像Flask风格: 250 | ``` 251 | routes = web.RouteTableDef() 252 | 253 | @routes.get('/get') 254 | async def handle_get(request): 255 | ... 256 | 257 | 258 | @routes.post('/post') 259 | async def handle_post(request): 260 | ... 261 | 262 | app.router.add_routes(routes) 263 | ``` 264 | 首先是要创建一个 `aiohttp.web.RouteTableDef` 对象。 265 | 266 | 该对象是一个类列表对象,额外提供`aiohttp.web.RouteTableDef.get()`,`aiohttp.web.RouteTableDef.post()`这些装饰器来注册路由。 267 | 268 | 最后调用`add_routes()`添加到应用的路由里。 269 | 270 | ### 扩展: 271 | RouteTableDef reference. 272 | 273 | 这三种方法都是一样的,你可以自行选择喜欢的方式或者混着用也行。 274 | 275 | 新增于2.3版本。 276 | 277 | # Web 处理器中的取消操作 278 | ### 警告: 279 | web处理器中每一条await语句都可被取消,这种情况一般发生在客户端还没有完全读取响应体然后中断了连接。 280 | 与著名WSGI架构 如Flask和Django在这点上有所不同。 281 | 282 | 在处理GET请求时,代码可能会从数据库或其他web资源中获取数据,这些查询可能很慢。 283 | 这时候取消查询是最好的: 该连接已经被抛弃了,没有理由再浪费时间和资源(内存等)进行查询,已经没有机会响应了。 284 | 285 | 不过在POST请求时可能会有些不好,POST请求常常需要将信息存进数据库,不管该连接是否被抛弃了。 286 | 287 | 预防被取消掉可以用下列几种方法: 288 | 289 | * 使用 `asyncio.shield()` 来进行存进数据库的处理。 290 | * 开启一个存入数据库的协程任务。 291 | * 使用`aiojobs`或其他第三方库。 292 | 293 | `asyncio.shield()` 挺不错的,唯一的缺点是你需要分清哪些是需要得到保护的代码哪些不是。 294 | 295 | 比如这样的代码就不靠谱: 296 | ``` 297 | async def handler(request): 298 | await asyncio.shield(write_to_redis(request)) 299 | await asyncio.shield(write_to_postgres(request)) 300 | return web.Response('OK') 301 | ``` 302 | 如果保存到redis时触发了取消,那之后的`write_to_postgres`将不会被执行。 303 | 304 | 生成一个新的任务更不靠谱,这样不能等待: 305 | ``` 306 | async def handler(request): 307 | request.loop.create_task(write_to_redis(request)) 308 | return web.Response('OK') 309 | ``` 310 | `write_to_redis`所产生的错误并不能被捕获,这样会导致有很多asyncio的Future异常的日志消息并且是不可恢复的,因为Task被销毁而不是挂起了。 311 | 312 | 此外,在按照优雅地关闭(Graceful shutdown)中的进行关闭操作时,`aiohttp`并不会等待这些任务完成,所以你还要找个机会来释放这些重要的数据。 313 | 314 | 另一方面,`aiojobs`提供一个生成新任务并且能等待结果(等此类操作)的API。`aiojobs`会将所有待完成的操作存入内部数据结构中,并且可以优雅地终止这些操作: 315 | ``` 316 | from aiojobs.aiohttp import setup, spawn 317 | 318 | async def coro(timeout): 319 | await asyncio.sleep(timeout) # do something in background 320 | 321 | async def handler(request): 322 | await spawn(request, coro()) 323 | return web.Response() 324 | 325 | app = web.Application() 326 | setup(app) 327 | app.router.add_get('/', handler) 328 | ``` 329 | 330 | 所有未完成的任务会被终止并由`aiohttp.web.Application.on_cleanup`信号发送出去。 331 | 332 | 使用`@atomic`装饰器可以使整个处理器都防止有取消操作: 333 | 334 | ``` 335 | from aiojobs.aiohttp import atomic 336 | 337 | @atomic 338 | async def handler(request): 339 | await write_to_db() 340 | return web.Response() 341 | 342 | app = web.Application() 343 | setup(app) 344 | app.router.add_post('/', handler) 345 | ``` 346 | 这样避免了整个async处理器函数执行时被取消,`write_to_db`也不会被中断。 347 | 348 | # 自定义路由准则 349 | 350 | 有时,比起简单的使用HTTP方法和路径注册路由,你可能会有其他更复杂些的规则。 351 | 352 | 尽管`UrlDispatcher`不直接提供额外的准则,但你可以这样做: 353 | ``` 354 | class AcceptChooser: 355 | 356 | def __init__(self): 357 | self._accepts = {} 358 | 359 | async def do_route(self, request): 360 | for accept in request.headers.getall('ACCEPT', []): 361 | acceptor = self._accepts.get(accept) 362 | if acceptor is not None: 363 | return (await acceptor(request)) 364 | raise HTTPNotAcceptable() 365 | 366 | def reg_acceptor(self, accept, handler): 367 | self._accepts[accept] = handler 368 | 369 | 370 | async def handle_json(request): 371 | # do json handling 372 | 373 | async def handle_xml(request): 374 | # do xml handling 375 | 376 | chooser = AcceptChooser() 377 | app.router.add_get('/', chooser.do_route) 378 | 379 | chooser.reg_acceptor('application/json', handle_json) 380 | chooser.reg_acceptor('application/xml', handle_xml) 381 | ``` 382 | 383 | # 静态文件的处理 384 | 处理静态文件( 图片,JavaScripts, CSS文件等)最好的方法是使用反向代理,像是nginx或CDN服务。 385 | 386 | 但就开发来说,`aiohttp`服务器本身可以很方便的处理静态文件。 387 | 388 | 只需要通过 `UrlDispatcher.add_static()`注册个新的静态路由即可: 389 | 390 | ``` 391 | app.router.add_static('/prefix', path_to_static_folder) 392 | ``` 393 | 394 | 当访问静态文件的目录时,默认服务器会返回 HTTP/403 Forbidden(禁止访问)。 使用`show_index`并将其设置为`True`可以显示出索引: 395 | ``` 396 | app.router.add_static('/prefix', path_to_static_folder, show_index=True) 397 | ``` 398 | 399 | 当从静态文件目录访问一个符号链接(软链接)时,默认服务器会响应 HTTP/404 Not Found(未找到)。使用`follow_symlinks`并将其设置为`True`可以让服务器使用符号链接: 400 | ``` 401 | app.router.add_static('/prefix', path_to_static_folder, follow_symlinks=True) 402 | ``` 403 | 404 | 如果你想允许缓存清除,使用`append_version`并设为`True`。 405 | 406 | 缓存清除会对资源文件像JavaScript 和 CSS文件等的文件名上添加一个hash后的版本。这样的好处是我们可以让浏览器无限期缓存这些文件而不用担心这些文件是否发布了新版本。 407 | ``` 408 | app.router.add_static('/prefix', path_to_static_folder, append_version=True) 409 | ``` 410 | 411 | 412 | # 模板 413 | 414 | `aiohttp.web`并不直接提供模板读取,不过可以使用第三方库 `aiohttp_jinja2`,该库是由`aiohttp`作者维护的。 415 | 使用起来也很简单。首先我们用`aiohttp_jinja2.setup()`来设置下`jinja2`环境: 416 | ``` 417 | app = web.Application() 418 | aiohttp_jinja2.setup(app, 419 | loader=jinja2.FileSystemLoader('/path/to/templates/folder')) 420 | ``` 421 | 422 | 然后将模板引擎应用到处理器中。最简单的方式是使用`aiohttp_jinja2.templates()`装饰器: 423 | ``` 424 | @aiohttp_jinja2.template('tmpl.jinja2') 425 | def handler(request): 426 | return {'name': 'Andrew', 'surname': 'Svetlov'} 427 | ``` 428 | 如果你更喜欢Mako模板引擎,可以看看 `aiohttp_mako`库。 429 | 430 | # 返回JSON 响应 431 | 432 | 使用 `aiohttp.web.json_response()`可以返回JSON响应: 433 | ``` 434 | def handler(request): 435 | data = {'some': 'data'} 436 | return web.json_response(data) 437 | ``` 438 | 439 | 这个方法返回的是`aiohttp.web.Response`实例对象,所以你可以做些其他的事,比如设置*cookies*。 440 | 441 | # 处理用户会话 442 | 你经常想要一个可以通过请求存储用户数据的仓库。一般简称为会话。 443 | 444 | `aiohttp.web`没有内置会话,不过你可以使用第三方库`aiohttp_session`来提供会话支持: 445 | ``` 446 | import asyncio 447 | import time 448 | import base64 449 | from cryptography import fernet 450 | from aiohttp import web 451 | from aiohttp_session import setup, get_session, session_middleware 452 | from aiohttp_session.cookie_storage import EncryptedCookieStorage 453 | 454 | async def handler(request): 455 | session = await get_session(request) 456 | last_visit = session['last_visit'] if 'last_visit' in session else None 457 | text = 'Last visited: {}'.format(last_visit) 458 | return web.Response(text=text) 459 | 460 | def make_app(): 461 | app = web.Application() 462 | # secret_key must be 32 url-safe base64-encoded bytes 463 | fernet_key = fernet.Fernet.generate_key() 464 | secret_key = base64.urlsafe_b64decode(fernet_key) 465 | setup(app, EncryptedCookieStorage(secret_key)) 466 | app.router.add_route('GET', '/', handler) 467 | return app 468 | 469 | web.run_app(make_app()) 470 | ``` 471 | 472 | 473 | # 处理HTTP表单 474 | `aiohttp`直接提供HTTP表单支持。 475 | 476 | 如果表单的方法是 “GET”(
),则要使用`Request.query`获取数据。 477 | 如果是“POST”则用`Request.post()`或 `Request.multipart()` 478 | `Request.post()`接受标明为`'application/x-www-form-urlencoded'`和`'multipart/form-data'` 的数据()。它会将数据存进一个临时字典中。如果指定了`client_max_size`,超出了的话会抛出`ValueError`异常,这时使用`Request.multipart()`是更好的选择,尤其是在上传大文件时。 479 | 480 | 小例子: 481 | 由以下表单发送的数据: 482 | ``` 483 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 |
493 | ``` 494 | 495 | 可以用以下方法获取: 496 | ``` 497 | async def do_login(request): 498 | data = await request.post() 499 | login = data['login'] 500 | password = data['password'] 501 | ``` 502 | 503 | # 文件上传 504 | `aiohttp.web`内置处理文件上传的功能。 505 | 506 | 首先呢我们要确保
标签的有`enctype`元素并且设置为了`"multipart/form-data"`。 507 | 我们写一个可以上传*MP3*文件的表单(作为例子): 508 | ``` 509 | 511 | 512 | 513 | 514 | 515 | 516 |
517 | ``` 518 | 519 | 之后你可以在请求处理器中接受这个文件,它变成了一个`FileField`实例对象。`FileFiled`包含了该文件的原信息。 520 | 521 | ``` 522 | async def store_mp3_handler(request): 523 | 524 | # 注意,如果是个很大的文件不要用这种方法。 525 | data = await request.post() 526 | 527 | mp3 = data['mp3'] 528 | 529 | # .filename 包含该文件的名称,是个字符串。 530 | filename = mp3.filename 531 | 532 | # .file 包含该文件的内容。 533 | mp3_file = data['mp3'].file 534 | 535 | content = mp3_file.read() 536 | 537 | return web.Response(body=content, 538 | headers=MultiDict( 539 | {'CONTENT-DISPOSITION': mp3_file})) 540 | ``` 541 | 542 | 注意例子中的警告。通常`Reuest.post()`会把所有数据读到内容,可能会引起*OOM(out of memory 内存炸了)*错误。你可以用`Request.multipart()`来避免这种情况,它返回的是`multipart`读取器。 543 | 544 | ``` 545 | async def store_mp3_handler(request): 546 | 547 | reader = await request.multipart() 548 | 549 | # /!\ 不要忘了这步。(至于为什么请搜索 Python 生成器/异步)/!\ 550 | 551 | mp3 = await reader.next() 552 | 553 | filename = mp3.filename 554 | 555 | # 如果是分块传输的,别用Content-Length做判断。 556 | size = 0 557 | with open(os.path.join('/spool/yarrr-media/mp3/', filename), 'wb') as f: 558 | while True: 559 | chunk = await mp3.read_chunk() # 默认是8192个字节。 560 | if not chunk: 561 | break 562 | size += len(chunk) 563 | f.write(chunk) 564 | 565 | return web.Response(text='{} sized of {} successfully stored' 566 | ''.format(filename, size)) 567 | ``` 568 | 569 | # 使用WebSockets 570 | 571 | `aiohttp.web`直接提供*WebSockets*支持。 572 | 573 | 在处理器中创建一个`WebSocketResponse`对象即可设置`WebSocket`,之后即可进行通信: 574 | ``` 575 | async def websocket_handler(request): 576 | 577 | ws = web.WebSocketResponse() 578 | await ws.prepare(request) 579 | 580 | async for msg in ws: 581 | if msg.type == aiohttp.WSMsgType.TEXT: 582 | if msg.data == 'close': 583 | await ws.close() 584 | else: 585 | await ws.send_str(msg.data + '/answer') 586 | elif msg.type == aiohttp.WSMsgType.ERROR: 587 | print('ws connection closed with exception %s' % 588 | ws.exception()) 589 | 590 | print('websocket connection closed') 591 | 592 | return ws 593 | ``` 594 | 595 | *websockets*处理器需要用HTTP GET方法注册: 596 | ``` 597 | app.router.add_get('/ws', websocket_handler) 598 | 599 | ``` 600 | 601 | 从`WebSocket`中读取数据(`await ws.receive()`)必须在请求处理器内部完成,不过写数据(`ws.send_str(...)`),关闭(`await ws.close()`)和取消操作可以在其他任务中完成。 详情看 FAQ 部分。 602 | 603 | `aiohttp.web` 隐式地使用`asyncio.Task`处理每个请求。 604 | 605 | ### 注意: 606 | 虽然aiohttp仅支持不带诸如长轮询的WebSocket不过如果我们有维护一个基于aiohttp的SockJS包,用于部署兼容SockJS服务器端的代码。 607 | 608 | ### 警告 609 | 不要试图从websocket中并行地读取数据,aiohttp.web.WebSocketResponse.receive()不能在分布在两个任务中同时调用。 610 | 611 | 请看 FAQ 部分了解解决方法。 612 | 613 | # 异常 614 | `aiohttp.web`定义了所有HTTP状态码的异常。 615 | 616 | 每个异常都是`HTTPException`的子类和某个HTTP状态码。 617 | 同样还都是`Response`的子类,所以就允许你在请求处理器中返回或抛出它们。 618 | 请看下面这些代码: 619 | 620 | ``` 621 | async def handler(request): 622 | return aiohttp.web.HTTPFound('/redirect') 623 | ``` 624 | ``` 625 | async def handler(request): 626 | raise aiohttp.web.HTTPFound('/redirect') 627 | ``` 628 | 629 | 每个异常的状态码是根据RFC 2068规定来确定的: 100-300不是由错误引起的; 400之后是客户端错误,500之后是服务器端错误。 630 | 631 | 异常等级图: 632 | ``` 633 | HTTPException 634 | HTTPSuccessful 635 | * 200 - HTTPOk 636 | * 201 - HTTPCreated 637 | * 202 - HTTPAccepted 638 | * 203 - HTTPNonAuthoritativeInformation 639 | * 204 - HTTPNoContent 640 | * 205 - HTTPResetContent 641 | * 206 - HTTPPartialContent 642 | HTTPRedirection 643 | * 300 - HTTPMultipleChoices 644 | * 301 - HTTPMovedPermanently 645 | * 302 - HTTPFound 646 | * 303 - HTTPSeeOther 647 | * 304 - HTTPNotModified 648 | * 305 - HTTPUseProxy 649 | * 307 - HTTPTemporaryRedirect 650 | * 308 - HTTPPermanentRedirect 651 | HTTPError 652 | HTTPClientError 653 | * 400 - HTTPBadRequest 654 | * 401 - HTTPUnauthorized 655 | * 402 - HTTPPaymentRequired 656 | * 403 - HTTPForbidden 657 | * 404 - HTTPNotFound 658 | * 405 - HTTPMethodNotAllowed 659 | * 406 - HTTPNotAcceptable 660 | * 407 - HTTPProxyAuthenticationRequired 661 | * 408 - HTTPRequestTimeout 662 | * 409 - HTTPConflict 663 | * 410 - HTTPGone 664 | * 411 - HTTPLengthRequired 665 | * 412 - HTTPPreconditionFailed 666 | * 413 - HTTPRequestEntityTooLarge 667 | * 414 - HTTPRequestURITooLong 668 | * 415 - HTTPUnsupportedMediaType 669 | * 416 - HTTPRequestRangeNotSatisfiable 670 | * 417 - HTTPExpectationFailed 671 | * 421 - HTTPMisdirectedRequest 672 | * 422 - HTTPUnprocessableEntity 673 | * 424 - HTTPFailedDependency 674 | * 426 - HTTPUpgradeRequired 675 | * 428 - HTTPPreconditionRequired 676 | * 429 - HTTPTooManyRequests 677 | * 431 - HTTPRequestHeaderFieldsTooLarge 678 | * 451 - HTTPUnavailableForLegalReasons 679 | HTTPServerError 680 | * 500 - HTTPInternalServerError 681 | * 501 - HTTPNotImplemented 682 | * 502 - HTTPBadGateway 683 | * 503 - HTTPServiceUnavailable 684 | * 504 - HTTPGatewayTimeout 685 | * 505 - HTTPVersionNotSupported 686 | * 506 - HTTPVariantAlsoNegotiates 687 | * 507 - HTTPInsufficientStorage 688 | * 510 - HTTPNotExtended 689 | * 511 - HTTPNetworkAuthenticationRequired 690 | ``` 691 | 692 | 所有的异常都拥有相同的结构: 693 | ``` 694 | HTTPNotFound(*, headers=None, reason=None, 695 | body=None, text=None, content_type=None) 696 | ``` 697 | 如果没有指定*headers*,默认是响应中的*headers*。 698 | 其中`HTTPMultipleChoices, HTTPMovedPermanently, HTTPFound, HTTPSeeOther, HTTPUseProxy, HTTPTemporaryRedirect`的结构是下面这样的: 699 | ``` 700 | HTTPFound(location, *, headers=None, reason=None, 701 | body=None, text=None, content_type=None) 702 | ``` 703 | *location*参数的值会写入到HTTP头部的Location中。 704 | 705 | `HTTPMethodNotAllowed`的结构是这样的: 706 | ``` 707 | HTTPMethodNotAllowed(method, allowed_methods, *, 708 | headers=None, reason=None, 709 | body=None, text=None, content_type=None) 710 | ``` 711 | *method*是不支持的那个方法,*allowed_methods*是所支持的方法。 712 | 713 | # 数据共享 714 | 715 | `aiohttp.web`不推荐使用全局变量进行数据共享。每个变量应在自己的上下文中而不是全局可用的。 716 | 因此,`aiohttp.web.Application`和`aiohttp.web.Request`提供`collections.abc.MutableMapping(类字典对象)`来存储数据。 717 | 718 | 将类全局变量存储到Application实例对象中: 719 | ``` 720 | app['my_private_key'] = data 721 | ``` 722 | 723 | 之后就可以在web处理器中获取出来: 724 | ``` 725 | async def handler(request): 726 | data = request.app['my_private_key'] 727 | ``` 728 | 如果变量的生命周期是一次请求,可以在请求中存储。 729 | ``` 730 | async def handler(request): 731 | request['my_private_key'] = "data" 732 | ... 733 | ``` 734 | 对于中间件和信号处理器存储需要进一步被处理的数据特别有用。 735 | 为了避免与其他`aiohttp`所写的程序和其他第三方库中的命名出现冲突,请务必写一个独一无二的键名。 736 | 如果你要发布到PyPI上,使用你公司的名称或url或许是个不错的选择。(如 org.company.app) 737 | 738 | # 中间件 739 | 740 | `aiohttp.web`提供一个强有力的中间件组件来编写自定义请求处理器。 741 | 742 | 中间件是一个协程程序帮助修正请求和响应。下面这个例子是在响应中添加'wink'字符串: 743 | ``` 744 | from aiohttp.web import middleware 745 | 746 | @middleware 747 | async def middleware(request, handler): 748 | resp = await handler(request) 749 | resp.text = resp.text + ' wink' 750 | return resp 751 | ``` 752 | (对于流式响应和*websockets*该例子不起作用) 753 | 754 | 每个中间件需要接受两个参数,一个是请求实例另一个是处理器,中间件需要返回响应内容。 755 | 756 | 创建`Application`时,可以通过`middlewares`参数传递中间件过去: 757 | ``` 758 | app = web.Application(middlewares=[middleware_1, 759 | middleware_2]) 760 | ``` 761 | 762 | 内部会将单个请求处理器处理的结果经由所传入的中间件处理器倒序的处理一遍。 763 | 因为`middlewares(中间件)`都是协程程序,所以它们可以执行`await`语句以进行如从数据库中查询等操作。 764 | `middlewares(中间件)`常常会调用处理器,不过也可以不调用,比如用户访问了没有权限访问的资源则显示403Forbidden页面或者抛出`HTTPForbidden`异常。 765 | 处理器中也可能抛出异常,比如执行一些预处理或后续处理(HTTP访问资源控制等)。 766 | 767 | 下列代码演示中间件的执行顺序: 768 | ``` 769 | from aiohttp import web 770 | 771 | def test(request): 772 | print('Handler function called') 773 | return web.Response(text="Hello") 774 | 775 | @web.middleware 776 | async def middleware1(request, handler): 777 | print('Middleware 1 called') 778 | response = await handler(request) 779 | print('Middleware 1 finished') 780 | return response 781 | 782 | @web.middleware 783 | async def middleware2(request, handler): 784 | print('Middleware 2 called') 785 | response = await handler(request) 786 | print('Middleware 2 finished') 787 | return response 788 | 789 | 790 | app = web.Application(middlewares=[middleware1, middleware2]) 791 | app.router.add_get('/', test) 792 | web.run_app(app) 793 | Produced output: 794 | 795 | Middleware 1 called 796 | Middleware 2 called 797 | Handler function called 798 | Middleware 2 finished 799 | Middleware 1 finished 800 | ``` 801 | 802 | ## 例子 803 | 通常中间件用于部署自定义错误页面。下面的例子将会使用JSON响应(JSON REST)显示404错误页面。 804 | ``` 805 | import json 806 | from aiohttp import web 807 | 808 | def json_error(message): 809 | return web.Response( 810 | body=json.dumps({'error': message}).encode('utf-8'), 811 | content_type='application/json') 812 | 813 | @web.middleware 814 | async def error_middleware(request, handler): 815 | try: 816 | response = await handler(request) 817 | if response.status == 404: 818 | return json_error(response.message) 819 | return response 820 | except web.HTTPException as ex: 821 | if ex.status == 404: 822 | return json_error(ex.reason) 823 | raise 824 | 825 | app = web.Application(middlewares=[error_middleware]) 826 | ``` 827 | 828 | ## 旧式中间件 829 | 2.3之前的版本中间件需要一个返回中间件协同程序的外部中间件处理工厂。2.3之后就不在需要这样做了,使用`@middleware`装饰器即可。 830 | 旧式中间件(使用外部处理工厂不使用`@middleware`装饰器)仍然支持。此外,新旧两个版本可以混用。 831 | 中间件工厂是一个用于在调用中间件之前做些其他操作的协同程序,下面例子是一个简单中间件工厂: 832 | ``` 833 | async def middleware_factory(app, handler): 834 | async def middleware_handler(request): 835 | resp = await handler(request) 836 | resp.text = resp.text + ' wink' 837 | return resp 838 | return middleware_handler 839 | ``` 840 | 中间件工厂要接受两个参数: app实例对象和请求处理器最后返回一个新的处理器。 841 | 842 | ### 注意: 843 | 外部中间件工厂和内部中间件处理器会处理每一个请求。 844 | 中间件工厂要返回一个与请求处理器有同样功能的新处理器——接受Request实例对象并返回响应或抛出异常。 845 | 846 | # 信号 847 | 信号组件新增于0.18版本。 848 | 849 | 尽管中间件可以自定义之前和之后的处理行为,但并不能自定义响应中的行为。所以信号量由此而生。 850 | 851 | 比如,中间件只能改变没有预定义HTTP头的响应的HTTP 头(看`prepare()`),但有时我们需要一个可以改变流式响应和*WebSockets HTTP头*的钩子。所以我们可以用`on_response_prepare`信号来充当这个钩子: 852 | ``` 853 | async def on_prepare(request, response): 854 | response.headers['My-Header'] = 'value' 855 | 856 | app.on_response_prepare.append(on_prepare) 857 | ``` 858 | 859 | 此外,你也可以用`on_startup`和`on_cleanup`信号来捕获应用开启和释放时的状态。 860 | 861 | 请看以下代码: 862 | ``` 863 | from aiopg.sa import create_engine 864 | 865 | async def create_aiopg(app): 866 | app['pg_engine'] = await create_engine( 867 | user='postgre', 868 | database='postgre', 869 | host='localhost', 870 | port=5432, 871 | password='' 872 | ) 873 | 874 | async def dispose_aiopg(app): 875 | app['pg_engine'].close() 876 | await app['pg_engine'].wait_closed() 877 | 878 | app.on_startup.append(create_aiopg) 879 | app.on_cleanup.append(dispose_aiopg) 880 | ``` 881 | 882 | 信号处理器不要返回内容,一般用于修改传入的可变对象。 883 | 信号处理器会循环执行,它们会一直累积。如果处理器是异步方式,在调用下个之前会一直等待。 884 | 885 | ### 警告: 886 | 信号API目前是临时状态,在未来可能会改变。 887 | 888 | 信号注册和发送方式基本不会被,不过信号对象的创建可能会变。只要你不创建新信号只用已经存在的信号量那基本不受影响。 889 | 890 | # 嵌套应用 891 | 子应用用来完成某些特定的功能。比如我们有一个项目,有独立的业务逻辑和其他功能如管理功能和调试工具。 892 | 893 | 管理功能是一个独立于业务逻辑的功能,但使用的时候要在URL中加上如 `/admi`这样的前缀。 894 | 因此我们可以创建名为 admin的子应用,我们可以用`add_subapp()`来完成: 895 | ``` 896 | admin = web.Application() 897 | # setup admin routes, signals and middlewares 898 | 899 | app.add_subapp('/admin/', admin) 900 | ``` 901 | 902 | 主应用和子应用间的中间件和信号是一个环一样的结构。 903 | 也就是说如果请求`'/admin/something'`会先调用主应用的中间件然后在调用子应用(`admin.middlewares`)的中间件。 904 | 信号也同样。 905 | 所有注册的基础信号如`on_startup,on_shutdown,on_cleanup`都会给子应用也注册一份。只不过传递的参数是子应用。 906 | 子应用也可以嵌套子应用。 907 | 子应用也可以使用Url反向引用,只不过会带上前缀: 908 | ``` 909 | admin = web.Application() 910 | admin.router.add_get('/resource', handler, name='name') 911 | 912 | app.add_subapp('/admin/', admin) 913 | 914 | url = admin.router['name'].url_for() 915 | ``` 916 | 这样我们得到的url是`'/admin/resource'`。 917 | 如果主应用想得到子应用的Url反向引用,可以这样: 918 | ``` 919 | admin = web.Application() 920 | admin.router.add_get('/resource', handler, name='name') 921 | 922 | app.add_subapp('/admin/', admin) 923 | app['admin'] = admin 924 | 925 | async def handler(request): # main application's handler 926 | admin = request.app['admin'] 927 | url = admin.router['name'].url_for() 928 | ``` 929 | 930 | # 流控制 931 | `aiohttp.web`有复杂的流控制机制来应对底层TCP套接字写入缓存。 932 | 问题是: 默认情况下TCP套接字使用Nagle算法来输出缓存,但这种算法对于流式数据协议如HTTP不是很理想。 933 | 934 | Web服务器的响应是以下几种状态其中一个: 935 | 936 | 1. CORK(tcp_cork设置为True)。这个选项不会发送一部分TCP/IP帧。当这个选项被(再次)清除时会发送所有已经在队列中的片段帧。因为会把各种小帧聚合起来发送,所以这种方式对发送大量片段数据非常理想。 如果操作系统不支持CORK模式(不管是socket.TCP_CORK还是socket.TCP_NOPUSH)那该模式与Nagle模式一样。一般来说是windows系统不支持此模式。 937 | 938 | 2. NODELAY(tcp_nodelay设置为True)。这个选项会禁用Nagle算法。选用这个那么无论数据多小都会尽快发出去,即使是很小的数据。该模式对发送少量数据非常理想。 939 | 940 | 3. Nagle算法(tcp_cork和tcp_nodelay都为False)。该模式会先缓存数据,直到达到预定的数据大小后再一起发送。如果要发送HTTP数据应该避免使用这个模式除非你确定要使用它。 941 | 942 | 默认情况下,*流数据*(`StreamResponse`),*标准响应*(`Response`和http异常及其派生类)和*websockets*(`WebSocketResponse`)使用NODELAY模式,静态文件处理器使用CORK模式。 943 | 944 | 可以使用`set_tcp_cork(`)方法和`set_tcp_nodelay()`方法手动切换。 945 | 946 | # 使用Expect头 947 | 948 | aiohttp.web支持使用Expect头。默认是HTTP/1.1 100 Continue,如果Expect头不是`"100-continue"`则抛出`HTTPExpectationFailed`异常。你可以自定义`Expect头`处理器。如果`Expect头`存在的话则会调用`Expect处理器`,`Expect处理器`会先于中间件和路由处理器被调用。`Expect处理器`可以返回`None`,返回`None`则会继续执行(调用中间件和路由处理器)。如果返回的是`StreamResponse`实例对象,之后请求处理器则使用该返回对象作为响应内容。也可以抛出`HTTPException`的子类对象。抛出错误的时候之后的处理将不会进行,客户端将会接受一个适当的http响应。 949 | ### 注意: 950 | 如果服务器不能理解或不支持的Expect域中的任何期望值,则服务器必须返回417或其他适当的错误码(4xx状态码)。 951 | http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20 952 | 953 | 如果所有的检查通过,在返回前自定义处理器需要会将`HTTP/1.1 100 Continue`写入。 954 | 955 | 下面是一个自定义`Expect处理器`的例子: 956 | 957 | ``` 958 | async def check_auth(request): 959 | if request.version != aiohttp.HttpVersion11: 960 | return 961 | 962 | if request.headers.get('EXPECT') != '100-continue': 963 | raise HTTPExpectationFailed(text="Unknown Expect: %s" % expect) 964 | 965 | if request.headers.get('AUTHORIZATION') is None: 966 | raise HTTPForbidden() 967 | 968 | request.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n") 969 | 970 | async def hello(request): 971 | return web.Response(body=b"Hello, world") 972 | 973 | app = web.Application() 974 | app.router.add_get('/', hello, expect_handler=check_auth) 975 | ``` 976 | 977 | # 部署自定义资源 978 | 使用 `UrlDispatcher.register_resource()`注册自定义的资源。资源实例必须有`AbstractResource`接口。 979 | 980 | 新增于1.2.1版本。 981 | 982 | # 优雅地关闭 983 | 停止`aiohttp web`服务器时只关闭打开的连接时不够的。 984 | 因为可能会有一些`websockets`或流,在服务器关闭时这些连接还是打开状态。 985 | aiohttp没有内置如何进行关闭,但开发者可以使用`Applicaiton.on_shutdown`信号来完善这一功能。 986 | 下面的代码是关闭`websocket`处理器的例子: 987 | ``` 988 | app = web.Application() 989 | app['websockets'] = [] 990 | 991 | async def websocket_handler(request): 992 | ws = web.WebSocketResponse() 993 | await ws.prepare(request) 994 | 995 | request.app['websockets'].append(ws) 996 | try: 997 | async for msg in ws: 998 | ... 999 | finally: 1000 | request.app['websockets'].remove(ws) 1001 | 1002 | return ws 1003 | ``` 1004 | 信号处理器差不多这样: 1005 | ``` 1006 | async def on_shutdown(app): 1007 | for ws in app['websockets']: 1008 | await ws.close(code=WSCloseCode.GOING_AWAY, 1009 | message='Server shutdown') 1010 | 1011 | app.on_shutdown.append(on_shutdown) 1012 | ``` 1013 | 1014 | 合适的关闭程序要注意以下死点: 1015 | 1. 不在接受新的连接。注意调用`asyncio.Server.close()`和`asyncio.Server.wait_closed()`来关闭。 1016 | 2. 解除`Application.shutdown()`事件。 1017 | 3. 在一小段延迟后调用`Server.shutdown()`关闭已经开启的连接。 1018 | 4. 发出`Application.cleanup()`信号。 1019 | 1020 | 下列代码演示从开始到结束: 1021 | ``` 1022 | loop = asyncio.get_event_loop() 1023 | handler = app.make_handler() 1024 | f = loop.create_server(handler, '0.0.0.0', 8080) 1025 | srv = loop.run_until_complete(f) 1026 | print('serving on', srv.sockets[0].getsockname()) 1027 | try: 1028 | loop.run_forever() 1029 | except KeyboardInterrupt: 1030 | pass 1031 | finally: 1032 | srv.close() 1033 | loop.run_until_complete(srv.wait_closed()) 1034 | loop.run_until_complete(app.shutdown()) 1035 | loop.run_until_complete(handler.shutdown(60.0)) 1036 | loop.run_until_complete(app.cleanup()) 1037 | loop.close() 1038 | ``` 1039 | 1040 | # 后台任务 1041 | 有些时候我们需要执行些异步操作。 1042 | 甚至需要在请求处理器处理问题时进行一些后台操作。比如监听消息队列或者其他网络消息/事件资源(ZeroMQ,Redis Pub/Sub,AMQP等)然后接受消息并作出反应。 1043 | 例如创建一个后台任务,用于在zmq.SUB套接字上监听ZeroMQ,然后通过`WebSocket(app['websockets'])`处理并转发接收到的消息给客户端。 1044 | 使用`Application.on_startup`信号注册的后台任务可以让这些任务在应用的请求处理器执行时一并执行。 1045 | 比如我们需要一个一次性的任务和两个常驻任务。最好的方法是通过信号注册: 1046 | ``` 1047 | async def listen_to_redis(app): 1048 | try: 1049 | sub = await aioredis.create_redis(('localhost', 6379), loop=app.loop) 1050 | ch, *_ = await sub.subscribe('news') 1051 | async for msg in ch.iter(encoding='utf-8'): 1052 | # Forward message to all connected websockets: 1053 | for ws in app['websockets']: 1054 | ws.send_str('{}: {}'.format(ch.name, msg)) 1055 | except asyncio.CancelledError: 1056 | pass 1057 | finally: 1058 | await sub.unsubscribe(ch.name) 1059 | await sub.quit() 1060 | 1061 | 1062 | async def start_background_tasks(app): 1063 | app['redis_listener'] = app.loop.create_task(listen_to_redis(app)) 1064 | 1065 | 1066 | async def cleanup_background_tasks(app): 1067 | app['redis_listener'].cancel() 1068 | await app['redis_listener'] 1069 | 1070 | 1071 | app = web.Application() 1072 | app.on_startup.append(start_background_tasks) 1073 | app.on_cleanup.append(cleanup_background_tasks) 1074 | web.run_app(app) 1075 | ``` 1076 | `listen_to_redis()`将会一直运行下去。当关闭时发出的`on_cleanup`信号会调用关闭处理器以关闭它。 1077 | 1078 | # 处理错误页面 1079 | 404 NotFound和500 Internal Error之类的错误页面可以看Middlewares章节了解详情,这些都可以通过自定义中间件完成。 1080 | 1081 | # 基于代理部署服务器 1082 | Server Deployment中讨论了基于反向代理服务(像nginx)部署`aiohttp`来用于生产使用。 1083 | 如果使用这种方法,就不要在使用scheme, host和remote了。 1084 | 将正确的值配置在代理服务器中,之后不管是使用`Forwarded`还是使用旧式的 `X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto` HTTP头都是可以的。 1085 | `aiohttp`默认不会获取`forwarded`值,因为可能会引起一些安全问题: HTTP客户端也可以自己添加这个值,非常不值得信任。 1086 | 这就是为什么`aiohttp`应该在使用反向代理的自定义中间件中设置forwarded头的原因。 1087 | 在中间件中改变scheme,host和remote可以用`clone()`。 1088 | 1089 | 待更新: 添加一个可以很好的配置中间件的第三方项目。 1090 | 1091 | # Swagger 支持 1092 | `aiohttp-swagge`r是一个允许在`aiohttp.web`项目中使用*Swagger-UI*的库。 1093 | 1094 | # CORS 支持 1095 | `aiohttp.web`本身不支持跨域资源共享(`Cross-Origin Resource Sharing`),但可以用`aiohttp_cors`。 1096 | 1097 | # 调试工具箱 1098 | 开发`aiohttp.web`应用项目时,`aiohttp_debugtoolbar`是非常好用的一个调试工具。 1099 | 1100 | 可使用pip进行安装: 1101 | ``` 1102 | $ pip install aiohttp_debugtoolbar 1103 | ``` 1104 | 1105 | 之后将`aiohttp_debugtoolba`r中间件添加到`aiohttp.web.Applicaiton`中并调用`aiohttp_debugtoolbar.setup()`来部署: 1106 | ``` 1107 | import aiohttp_debugtoolbar 1108 | from aiohttp_debugtoolbar import toolbar_middleware_factory 1109 | 1110 | app = web.Application(middlewares=[toolbar_middleware_factory]) 1111 | aiohttp_debugtoolbar.setup(app) 1112 | ``` 1113 | 1114 | 愉快的调试起来吧~。 1115 | 1116 | # 开发工具 1117 | `aiohttp-devtools`提供几个简化开发的小工具。 1118 | 1119 | 可以使用pip安装: 1120 | ``` 1121 | $ pip install aiohttp-devtools 1122 | * ``runserver`` 提供自动重载,实时重载,静态文件服务和aiohttp_debugtoolbar_integration。 1123 | * ``start`` 是一个帮助做繁杂且必须的创建'aiohttp.web'应用的命令。 1124 | ``` 1125 | 创建和运行本地应用的文档和指南请看`aiohttp-devtools`。 1126 | -------------------------------------------------------------------------------- /aiohttp文档/StreamingAPI.md: -------------------------------------------------------------------------------- 1 | # 流API 2 | `aiohttp.web.Request.content`和`aiohttp.ClientResponse.content`都使用流来接受数据,同时也是流形式的API属性。 3 | 4 | *class aiohttp.StreamReader* 5 |    流内容读取器 6 |    除了已经存在的`aiohttp.web.Request.content`和`aiohttp.ClientResponse.content`用于读取原始内容的`StreamReader`实例,用户不要手动创建`StreamReader`实例对象。 7 | 8 | ## 读取方法 9 | *coroutine StreamReader.read(n=-1)* 10 |    读取n字节内容,如果n没有提供或为-1,会一直读到EOF(无更多内容可读)并返回全部所读字节。 11 |    如果接受的是EOF并且内部缓存器没有内容,则返回空字节对象。 12 |    **参数**: 13 |       n(int) - 指定读取的字节数,-1则为全部读取。 14 |    **返回所读字节。** 15 | *coroutine StreamReader.readany()* 16 |    用于读取下一部分流内容。 17 |    如果内部缓存器有数据则立即返回。 18 |    **返回所读字节。** 19 | *coroutine StreamReader.readexactly(n)* 20 |    返回n字节数据。 21 |     如果超过了读取上限则会抛出`asyncio.IncompleteReadError`异常,`asyncio.IncompleteReadError.partial`属性包含了已经读取到的那部分字节。 22 |     **参数:** n (int) - 要读的字节数。 23 |     **返回所读字节。** 24 | *coroutine StreamReader.readline()* 25 |     读取其中的一行数据,行是以"\n"所区分开的数据。 26 |     如果发现EOF而不是\n则会返回所读到的字节数据。 27 |     如果发现EOF但内部缓存器是空的,则返回空字节对象。 28 |     **返回所读行。** 29 | *coroutine StreamReader.readchunk()* 30 |     读取从服务器接受到的块数据。 31 |     返回包含信息的元组((data, end_of_HTTP_chunk))。 32 |     如果使用了分块传输编码,`end_of_HTTP_chunk`是一个指示末尾数据是否对应HTTP块末尾的布尔值,其他情况下都是`False`。 33 |     返回元组[bytes, bool]: 34 |       bytes指代数据块,bool指代是否对应了HTTP块的最后一部分。 35 | 36 | 37 | ## 异步迭代 38 | 流读取器支持异步迭代。 39 | 默认是以行读取的: 40 | ``` 41 | async for line in response.content: 42 | print(line) 43 | 44 | ``` 45 | 当然我们还提供几个如可以指定最大限度的数据块迭代和任何可读内容的迭代器等。 46 | 47 | *async-for StreamReader.iter_chunked(n)* 48 |     指定了最大可读字节的数据块迭代器: 49 | ``` 50 | async for data in response.content.iter_chunked(1024): 51 | print(data) 52 | ``` 53 | 54 | *async-for StreamReader.iter_any()* 55 |     按顺序读取在流中的数据块: 56 | ``` 57 | async for data in response.content.iter_any(): 58 | print(data) 59 | ``` 60 | 61 | *async-for StreamReader.iter_chunks()* 62 |     读取从服务器接收到的数据块: 63 | ``` 64 | async for data, _ in response.content.iter_chunks(): 65 | print(data) 66 | ``` 67 | 如果使用了分块传输编码,使用返回的元组中的第二个元素即可检索原始http块的格式信息。 68 | ``` 69 | buffer = b"" 70 | 71 | async for data, end_of_http_chunk in response.content.iter_chunks(): 72 | buffer += data 73 | if end_of_http_chunk: 74 | print(buffer) 75 | buffer = b"" 76 | ``` 77 | 78 | 79 | ## 其他帮助信息 80 | StreamReader.exception() 81 |     返回数据读取时发生的异常。 82 | 83 | aiohttp.is_eof() 84 |     如果检索到EOF则返回`Ture`。 85 | 86 |     这个函数检索到EOF的场合内部缓存器可能不是空的。 87 | 88 | aiohttp.at_eof() 89 |    如果检索到EOF同时缓存器是空的则返回`True`。 90 | 91 | StreamReader.read_nowait(n=None) 92 |    返回内部缓存器的任何数据,没有则返回空对象。 93 |    如果其他协同程序正在等待这个流则抛出`RuntimeError`异常。 94 |    **参数**: n (int) - 要读的字节数。-1则是缓存器中的所有数据。 95 |    返回所读字节。 96 | 97 | StreamReader.unread_data(data) 98 |    将所读的一些内容回滚到数据流中,数据会插入到缓存器的头部。 99 | 100 |    **参数**: data (bytes) - 需要压入流中的数据。 101 | 102 | ### 警告 103 | 该方法不会唤醒正在等待的协同程序。 104 | 如对read()方法就不会奏效。 105 | 106 | coroutine aiohttp.wait_eof() 107 |    等待直到发现EOF。所给出的数据可以被接下来的读方法获取到。 108 | 109 | -------------------------------------------------------------------------------- /aiohttp文档/Testing.md: -------------------------------------------------------------------------------- 1 | # 测试 2 | # aiohttp web服务器测试 3 | aiohttp有一个pytest插件可以轻松构建web服务器测试程序,同时该插件还有一个用于测试其他框架(单元测试等)的测试框架包。 4 | 在写测试之前,我想你可能会想读一读如何写一个可测试的服务器程序感兴趣,因为它们之间的作用的相互的。 5 | 在使用之前,我们还需要安装下才行: 6 | ``` 7 | $ pip install pytest-aiohttp 8 | ``` 9 | 如果你不想安装它,你可以在conftest.py中插入一行 `pytest_plugins='aiohttp.pytest_plugin'`来代替这个包。 10 | 11 | # 临时状态说明 12 | 该模块是临时的。 13 | 14 | 对于已经废弃的API,基于向后不兼容政策,aiohttp允许仍可以继续使用一年半的时间。 15 | 16 | 不过这对aiohttp.test_tools则不适用。 17 | 18 | 同时,如有一些必要的原因,我们也会不管向后兼容期而做出更改。 19 | 20 | # 客户端与服务器端测试程序 21 | 22 | aiohttp中的test_utils有一个基于aiohttp的web服务器测试模板。 23 | 其中包含两个部分: 一个是启动测试服务器,然后是向这个服务器发起HTTP请求。 24 | TestServer使用以aiohttp.web.Application为基础的服务器。RawTestServer则使用基于aiohttp.web.WebServer的低级服务器。 25 | 发起HTTP请求到服务器你可以创建一个TestClient实例对象。 26 | 测试客户端实例使用aiohttp.ClientSession来对常规操作如ws_connect,get,post等进行支持。 27 | 28 | #Pytest 29 | pytest-aiohttp插件允许你创建客户端并向你的应用程序发起请求来进行测试。 30 | 31 | 简易程序如下: 32 | ``` 33 | from aiohttp import web 34 | 35 | async def hello(request): 36 | return web.Response(text='Hello, world') 37 | 38 | async def test_hello(test_client, loop): 39 | app = web.Application() 40 | app.router.add_get('/', hello) 41 | client = await test_client(app) 42 | resp = await client.get('/') 43 | assert resp.status == 200 44 | text = await resp.text() 45 | assert 'Hello, world' in text 46 | ``` 47 | 同样,它也提供访问app实例的方法,允许测试组件查看app的状态。使用fixture可以创建非常便捷的app测试客户端: 48 | ``` 49 | import pytest 50 | from aiohttp import web 51 | 52 | 53 | async def previous(request): 54 | if request.method == 'POST': 55 | request.app['value'] = (await request.post())['value'] 56 | return web.Response(body=b'thanks for the data') 57 | return web.Response( 58 | body='value: {}'.format(request.app['value']).encode('utf-8')) 59 | 60 | @pytest.fixture 61 | def cli(loop, test_client): 62 | app = web.Application() 63 | app.router.add_get('/', previous) 64 | app.router.add_post('/', previous) 65 | return loop.run_until_complete(test_client(app)) 66 | 67 | async def test_set_value(cli): 68 | resp = await cli.post('/', data={'value': 'foo'}) 69 | assert resp.status == 200 70 | assert await resp.text() == 'thanks for the data' 71 | assert cli.server.app['value'] == 'foo' 72 | 73 | async def test_get_value(cli): 74 | cli.server.app['value'] = 'bar' 75 | resp = await cli.get('/') 76 | assert resp.status == 200 77 | assert await resp.text() == 'value: bar' 78 | ``` 79 | Pytest工具箱里有以下fixture: 80 | aiohttp.test_utils.**test_server**(*app, \*\*kwargs*) 81 |     一个创建TestServer的fixture。 82 | ``` 83 | async def test_f(test_server): 84 | app = web.Application() 85 | # 这里填写路由表 86 | 87 | server = await test_server(app) 88 | ``` 89 |    服务器会在测试功能结束后销毁。 90 |    *app*是aiohttp.web.Application组件,用于启动服务器。 91 |    *kwargs*是其他需要传递的参数。 92 | 93 | aiohttp.test_utils.**test_client**(*app, \*\*kwargs*) 94 | aiohttp.test_utils.**test_client**(*server, \*\*kwargs*) 95 | aiohttp.test_utils.**test_client**(*raw_server, \*\*kwargs*) 96 |     一个用户创建访问测试服务的TestClient fixture。 97 | ``` 98 | async def test_f(test_client): 99 | app = web.Application() 100 | # 这里填写路由表。 101 | 102 | client = await test_client(app) 103 | resp = await client.get('/') 104 | ``` 105 |     客户端和响应在测试功能完成后会自动清除。 106 |     这个fixture可以接收aiohttp.webApplication, aiohttp.test_utils.TestServer或aiohttp.test_utils.RawTestServer实例对象。 107 |     *kwargs*用于接收传递给aiohttp.test_utils.TestClient的参数。 108 | 109 | aiohttp.test_utils.**raw_test_server**(*handler, \*\*kwargs*) 110 |     一个从给定web处理器实例创建RawTestServer的fixture。 111 |     处理器应是一个可以接受请求并且返回响应的协同程序: 112 | ``` 113 | async def test_f(raw_test_server, test_client): 114 | 115 | async def handler(request): 116 | return web.Response(text="OK") 117 | 118 | raw_server = await raw_test_server(handler) 119 | client = await test_client(raw_server) 120 | resp = await client.get('/') 121 | ``` 122 | 123 | # 单元测试 124 | 使用标准库里的单元测试/基础单元测试的功能来测试应用程序,提供AioHTTPTestCase类: 125 | ``` 126 | from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop 127 | from aiohttp import web 128 | 129 | class MyAppTestCase(AioHTTPTestCase): 130 | 131 | async def get_application(self): 132 | """ 133 | Override the get_app method to return your application. 134 | """ 135 | return web.Application() 136 | 137 | # the unittest_run_loop decorator can be used in tandem with 138 | # the AioHTTPTestCase to simplify running 139 | # tests that are asynchronous 140 | @unittest_run_loop 141 | async def test_example(self): 142 | request = await self.client.request("GET", "/") 143 | assert request.status == 200 144 | text = await request.text() 145 | assert "Hello, world" in text 146 | 147 | # a vanilla example 148 | def test_example(self): 149 | async def test_get_route(): 150 | url = root + "/" 151 | resp = await self.client.request("GET", url, loop=loop) 152 | assert resp.status == 200 153 | text = await resp.text() 154 | assert "Hello, world" in text 155 | 156 | self.loop.run_until_complete(test_get_route()) 157 | ``` 158 | *class aiohttp.test_utils.**AioHTTPTestCase*** 159 |     一个允许使用aiohttp对web应用程序进行单元测试的基础类。 160 |     该类派生于unittest.TestCase. 161 |     提供下列功能: 162 |     **client** 163 |         aiohttp测试客户端(TestClient实例)。 164 |     **server** 165 |         aiohttp测试服务器(TestServer实例)。 新增于2.3.0版本。 166 |     **loop** 167 |         应用程序和服务器运行的事件循环。 168 |     **app** 169 |         应用程序(aiohttp.web.Application实例),由get_app()返回。 170 |     **coroutine get_client()** 171 |        该方法可以被覆盖。返回测试中的TestClient对象。 172 |        **返回TestClient实例对象。** 新增于2.3.0版本。 173 |     **coroutine get_server()** 174 |         该方法可以备覆盖。返回测试中的TestServer对象。 175 |         **返回TestServer实例对象。** 新增于2.3.0版本。 176 |     **coroutine get_application()** 177 |         该方法可以被覆盖。返回用于测试的aiohttp.web.Application对象。 178 |         **返回aiohttp.web.Application实例对象。** 179 |     **coroutine setUpAsync()** 180 |         默认该方法什么也不做,不过可以被覆盖用于在TestCase的setUp阶段执行异步代码。 新增于2.3.0版本。 181 |     **coroutine tearDownAsync()** 182 |         默认该方法什么也不做,不过可以被覆盖用于在TestCase的tearDown阶段执行异步代码。 新增于2.3.0版本。 183 |     **setUp()** 184 |         标准测试初始化方法。 185 |     **tearDown()** 186 |         标准测试析构方法。 187 | 188 | ### 注意 189 | TestClient的方法都是异步方法,你必须使用异步方法来执行它的函数。 190 | 使用unittest_run_loop()装饰器可以包装任何一个基础类中的测试方法。 191 | ``` 192 | class TestA(AioHTTPTestCase): 193 | 194 | @unittest_run_loop 195 | async def test_f(self): 196 | resp = await self.client.get('/') 197 | ``` 198 | **unittest_run_loop** 199 |     专门用在AioHTTPTestCase的异步方法上的装饰器。 200 |     使用AioHTTPTestCase中的AioHTTPTestCase.loop来执行异步函数。 201 | # 虚假请求对象 202 | aiohttp提供创建虚假aiohttp.web.Request对象的测试工具: `aiohttp.test_utils.make_mocked_request()`,在一些简单的单元测试中特别好用,比如处理器测试,或者很难在真实服务器上重现的错误之类的。 203 | ``` 204 | from aiohttp import web 205 | from aiohttp.test_utils import make_mocked_request 206 | 207 | def handler(request): 208 | assert request.headers.get('token') == 'x' 209 | return web.Response(body=b'data') 210 | 211 | def test_handler(): 212 | req = make_mocked_request('GET', '/', headers={'token': 'x'}) 213 | resp = handler(req) 214 | assert resp.body == b'data' 215 | ``` 216 | 217 | ### 警告 218 | 我们不建议在任何地方都用make_mocked_request()来测试,最好使用真实的操作。 219 | make_mocked_request()的存在只是为了测试那些很难或根本不能通过简便方法测试的复杂案例(比如模仿网络错误)。 220 | 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=...*) 221 |     创建一个用于测试的仿真web.Request。 222 |     对于那些在特殊环境难以触发的错误在单元测试中非常有用。 223 |     **参数:** 224 | * method (str) - str, 代表HTTP方法,如GET, POST。 225 | * path(str) - str, 带有URL的路径信息但没有主机名的字符串。 226 | * headers(dict, multidict.CIMultiDict, 成对列表) - 一个包含头信息的映射对象。可传入任何能被multidict.CIMultiDict接受的对象。 227 | * match_info(dict) - 一个包含url参数信息的映射对象。 228 | * version(aiohttp.protocol.HttpVersion) - 带有HTTP版本的namedtuple。 229 | * closing(bool) - 一个用于决定是否在响应后保持连接的标识。 230 | * app(aiohttp.web.Application) - 带有虚假请求的aiohttp.web.application。 231 | * writer - 管理如何输出数据的对象。 232 | * transport (asyncio.transports.Transport) - asyncio transport 实例。 233 | * payload (aiohttp.streams.FlowControlStreamReader) - 原始载体读取器对象。 234 | * sslcontext(ssl.SSLContext) - ssl.SSLContext对象,用于HTTPS连接。 235 | * loop (asyncio.AbstractEventLoop) - 事件循环对象,默认是仿真(mocked)循环。 236 |     **返回** 237 |       返回aiohttp.web.Request对象。 238 |       2.3版本新增: match_info参数。 239 | 240 | 241 | # 未知框架工具箱 242 | 创建高等级测试: 243 | ``` 244 | from aiohttp.test_utils import TestClient, loop_context 245 | from aiohttp import request 246 | 247 | # loop_context is provided as a utility. You can use any 248 | # asyncio.BaseEventLoop class in it's place. 249 | with loop_context() as loop: 250 | app = _create_example_app() 251 | with TestClient(app, loop=loop) as client: 252 | 253 | async def test_get_route(): 254 | nonlocal client 255 | resp = await client.get("/") 256 | assert resp.status == 200 257 | text = await resp.text() 258 | assert "Hello, world" in text 259 | 260 | loop.run_until_complete(test_get_route()) 261 | ``` 262 | 263 | 如果需要更细粒度的创建/拆除,可以直接用TestClient对象: 264 | ``` 265 | from aiohttp.test_utils import TestClient 266 | 267 | with loop_context() as loop: 268 | app = _create_example_app() 269 | client = TestClient(app, loop=loop) 270 | loop.run_until_complete(client.start_server()) 271 | root = "http://127.0.0.1:{}".format(port) 272 | 273 | async def test_get_route(): 274 | resp = await client.get("/") 275 | assert resp.status == 200 276 | text = await resp.text() 277 | assert "Hello, world" in text 278 | 279 | loop.run_until_complete(test_get_route()) 280 | loop.run_until_complete(client.close()) 281 | ``` 282 | 283 | 你可以在api参考中找到所有工具包清单。 284 | 285 | # 编写可测试服务 286 | 一些如motor, aioes等依赖asyncio循环来执行代码的库,当它们运行正常程序时,都会选一个主事件循环给asyncio.get_event_loop。问题在于,当处在测试环境中时,我们没有主事件循环,因为每个测试都有一个独立的循环。 287 | 这样当其他库尝试找这个主事件循环时就会发生出错。不过幸运的是,这问题很好解决,我们可以显式的传入循环。我们来看aioes客户端中的代码: 288 | ``` 289 | def __init__(self, endpoints, *, loop=None, **kwargs) 290 | ``` 291 | 如你所见,有一个可选的loop参数。当然,我们并不打算直接测试aioes客户端只是我们的服务建立在它之上。所以如果我们想让我们的AioESService容易测试,我们可以这样写: 292 | ``` 293 | import asyncio 294 | 295 | from aioes import Elasticsearch 296 | 297 | 298 | class AioESService: 299 | 300 | def __init__(self, loop=None): 301 | self.es = Elasticsearch(["127.0.0.1:9200"], loop=loop) 302 | 303 | async def get_info(self): 304 | cluster_info = await self.es.info() 305 | print(cluster_info) 306 | 307 | 308 | if __name__ == "__main__": 309 | client = AioESService() 310 | loop = asyncio.get_event_loop() 311 | loop.run_until_complete(client.get_info()) 312 | ``` 313 | 注意它接受的loop参数。正常情况下没有什么影响因为我们不用显示地传递loop就能让服务有一个主事件循环。问题出在我们测试时: 314 | ``` 315 | import pytest 316 | 317 | from main import AioESService 318 | 319 | 320 | class TestAioESService: 321 | 322 | async def test_get_info(self): 323 | cluster_info = await AioESService().get_info() 324 | assert isinstance(cluster_info, dict) 325 | ``` 326 | 327 | 如果尝试运行测试,一般会失败并给出类似下面的信息: 328 | ``` 329 | ... 330 | RuntimeError: There is no current event loop in thread 'MainThread'. 331 | ``` 332 | 因为aioes在主线程中找不到当前的事件循环,所以就报错咯。显式地传递事件循环可以解决这个问题。 333 | 如果你的代码依靠隐式循环工作,你可以需要点小技巧。请看FAQ。 334 | 335 | # 测试API参考 336 | ## 测试服务器 337 | 在随机TCP端口上运行给定的aiohttp.web.Application。 338 | 创建完成后服务器并没开始,请用start_server()确保服务器开启和使用close()来确保关闭。 339 | 测试服务器通常与aiohttp.test_utils.TestClient连用,后者可以提供便利的客户端方法来访问服务器。 340 | class aiohttp.test_utils.**BaseTestServer**(*\*, scheme='http', host='127.0.0.1'*) 341 |     测试服务器的基础类。 342 |     **参数:** 343 | * scheme(str) - HTTP协议,默认是无保护的“http”。 344 | * host(str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。 345 |     **scheme** 346 |        被测试应用使用的协议,'http'是无保护的,'https'是有TLS加密的。 347 |     **host** 348 |        用于启动测试服务器的主机名。 349 |     **port** 350 |        用于启动测试服务器的端口(随机的)。 351 |     **handler** 352 |        用于处理HTTP请求的aiohttp.web.WebServer对象。 353 |     **server** 354 |        用于管理已接受连接的asyncio.AbstractServer对象。 355 |    *coroutine start_server(loop=None, \*\*kwargs)* 356 |        **参数:** loop(asyncio.AbstractEventLoop) - 用于开启测试服务器的事件循环。 357 |     *coroutine close()* 358 |       停止和结束开启的测试服务器。 359 |     *make_url(path)* 360 |        返回给定path的绝对URL。 361 | 362 | class aiohttp.test_utils.**RawTestServer**(*handler, \*, scheme="http", host="127.0.0.1"*) 363 |     低级测试服务器(派生于BaseTestServer) 364 |     **参数:** 365 | * handler - 用于处理web请求的协同程序。处理器需要接受aiohttp.web.BaseRequest实例并且返回响应实例(StreamResponse或Response之类的)。对于非200的HTTP响应,处理器可以抛出HTTPException异常。 366 | * scheme(str) - HTTP协议,默认是无保护的“http”。 367 | * host(str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。 368 | class aiohttp.test_utils.**TestServer**(*app, \*, scheme="http", host="127.0.0.1"*) 369 |     用于启动应用程序的测试服务器(派生于BaseTestServer)。 370 |     **参数:** 371 | * app - 要启动的aiohttp.web.Application实例对象。 372 | * scheme(str) - HTTP协议,默认是无保护的“http”。 373 | * host(str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。 374 |     app 375 |        要启动的aiohttp.web.Application实例对象。 376 | 377 | # 测试客户端。 378 | class aiohttp.test_utils.**TestClient**(*app_or_server, \*, loop=None, scheme='http', host='127.0.0.1'*) 379 |     一个用于制造请求来测试服务器的测试客户端。 380 |     **参数:** 381 | * app_or_server - BaseTestServer实例对象,用于向其发起请求。如果是aiohttp.web.Application对象,会为应用程序自动创建一个TestServer。 382 | * cookie_jar - 可选的aiohttp.CookieJar实例对象,搭配CookieJar(unsafe=True)更佳。 383 | * scheme (str) - HTTP协议,默认是无保护的“http”。 384 | * loop (asyncio.AbstractEventLoop) - 需要使用的事件循环。 385 | * host (str) - TCP套接字主机,默认是IPv4本地主机(127.0.0.1)。 386 |     scheme 387 |        被测试应用的使用的协议,'http'是无保护的,'https'是有TLS加密的。 388 |     host 389 |        用于启动测试服务器的主机名。 390 |     port 391 |        用于启动测试服务器的端口(随机的)。 392 |     server 393 |        BaseTestServer测试服务器实例,一般与客户端连用。 394 |     session 395 |        内部aiohttp.ClientSession对象. 396 |        不同于TestClient中的那样,客户端会话的请求不会自动将url查询放入主机名中,需要传入一个绝对路径。 397 |     coroutine start_server(\*\*kwargs) 398 |        开启测试服务器。 399 |     coroutine close() 400 |        关闭在运行的服务器。 401 |     make_url(path) 402 |        返回给定path的绝对URL。 403 |    coroutine request(method, path, \*args, \*\*kwargs) 404 |        将请求发送给测试服务器。 405 |        除了loop参数被测试服务器所使用的循环覆盖外,该接口与asyncio.ClientSession.request()相同。 406 |     coroutine get(path, \*args, \*\*kwargs) 407 |        执行HTTP GET请求。 408 |     coroutine post(path, \*args, \*\*kwargs) 409 |        执行HTTP POST请求。 410 |     coroutine options(path, \*args, \*\*kwargs) 411 |        执行HTTP OPTIONS请求。 412 |     coroutine head(path, \*args, \*\*kwargs) 413 |        执行HTTP HEAD请求。 414 |     coroutine put(path, \*args, \*\*kwargs) 415 |        执行HTTP PUT请求。 416 |     coroutine patch(path, \*args, \*\*kwargs) 417 |        执行HTTP PATCH请求。 418 |     coroutine delete(path, \*args, \*\*kwargs) 419 |        执行HTTP DELETE请求。 420 |     coroutine ws_connect(path, \*args, \*\*kwargs) 421 |        初始化websocket连接。 422 |        该api与aiohttp.ClientSession.ws_connect()相同。 423 | 424 | # 其他工具包 425 | aiohttp.test_utils.make_mocked_coro(return_value) 426 |     创建一个协程mock。 427 |     其表现形式像一个协程一般,作用是返回要返回的值(return_value)。而同时又是一个mock对象,你可以使用一般的Mock来测试它: 428 | ``` 429 | mocked = make_mocked_coro(1) 430 | assert 1 == await mocked(1, 2) 431 | mocked.assert_called_with(1, 2) 432 | ``` 433 |     **参数:** return_value - 当mock对象被调用时返回的值。 434 |     **像协程一样返回return_value的值。** 435 | 436 | aiohttp.test_utils.unused_port() 437 |     返回一个可以用在IPv4 TCP协议上的还没有被使用的端口。 438 |     返回一个可以使用端口值(类型为整数int)。 439 | 440 | aiohttp.test_utils.loop_context(loop_factory=) 441 |     一个上下文管理器,可以创建一个用于测试目的事件循环。 442 |     用于进行测试循环的创建和清理工作。 443 | 444 | aiohttp.test_utils.setup_test_loop(loop_factory=) 445 |     创建并返回asyncio.AbstractEventLoop实例对象。 446 |     如果要调用它,需要在结束循环时调用下teardown_test_loop. 447 | 448 | aiohttp.test_utils.teardown_test_loop(loop) 449 |     销毁并清除setup_test_loop所创建的event_loop。 450 |     **参数:** loop(asyncio.AbstractEventLoop) - 需要拆除的循环。 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | -------------------------------------------------------------------------------- /aiohttp文档/WorkWithMultipart.md: -------------------------------------------------------------------------------- 1 | # 使用Multipart 2 | 3 | `aiohttp`支持功能完备的*Multipart*读取器和写入器。这俩都使用流式设计,以避免不必要的占用,尤其是处理的载体较大时,但这也意味着大多数I/O操作只能被执行一次。 4 | 5 | # 读取Multipart响应 6 | 假设你发起了一次请求,然后想读取*Multipart*响应数据: 7 | 8 | ``` 9 | async with aiohttp.request(...) as resp: 10 | pass 11 | ``` 12 | 13 | 首先,你需要使用`MultipartReader.from_response()`来处理下响应内容。这样可以让数据从响应和连接中分离出来并保持`MultipartReader`状态,使其更便捷的使用: 14 | ``` 15 | reader = aiohttp.MultipartReader.from_response(resp) 16 | ``` 17 | 假设我们需要接受**JSON**数据和*Multipart*文件,但并不需要所有数据,只是其中的一个。 18 | 19 | 那我们首先需要进入一段循环中,在里面处理*Multipart*: 20 | ``` 21 | metadata = None 22 | filedata = None 23 | while True: 24 | part = await reader.next() 25 | ``` 26 | 所返回的类型取决于下一次循环时的值: 如果是一个正常响应内容那会得到`BodyPartReader`实例对象,否则将会是一个嵌套*Multipart*的*MultipartReader*实例对象。记住,*Multipart*的格式就是递归并且支持嵌套多层。如果接下来没有内容可以获取了,则返回`None` - 然后就可以跳出这个循环了: 27 | 28 | ``` 29 | if part is None: 30 | break 31 | ``` 32 | 33 | `BodyPartReader`和`MultipartReader`都可访问内容的`headers`: 这样就可以使用他们的属性来进行过滤: 34 | ``` 35 | if part.headers[aiohttp.hdrs.CONTENT_TYPE] == 'application/json': 36 | metadata = await part.json() 37 | continue 38 | ``` 39 | 不明确说明的话,不管是`BodyPartReader`还是`MultipartReader`都不会读取出全部的内容。`BodyPartReader`提供一些易用的方法来帮助获取比较常见的内容类型: 40 | * `BodyPartReader.text()` 普通文本内容。 41 | * `BodyPartReader.json()` JSON内容。 42 | * `BodyPartReader.form()` `application/www-urlform-encode`内容。 43 | 如果传输内容使用了*gzip*和*deflate*进行过编码则会自动识别,或者如果是*base64*或*quoted-printable*这种情况也会自动解码。不过如果你需要读取原始数据,使用`BodyPartReader.read()`和`BodyPartReader.read_chunk()`协程方法都可以读取原始数据,只不过一个是一次性读取全部一个是分块读取。 44 | `BodyPartReader.filename`属性对于处理*Multipart*文件时可能会有些用处: 45 | ``` 46 | if part.filename != 'secret.txt': 47 | continue 48 | ``` 49 | 当前的内容不符合你的期待然后要跳过的话只需要使用`continue`来继续这个循环。在获取下一个内容之前使用`await reader.next()`确保之前那个已经完全被读取出来了。如果没有的话,所有的内容将会被抛弃然后来获取下一个内容。所以你不用关心如何清理这些无用的数据。 50 | 一旦发现你搜寻的那个文件,直接读就行。我们可以先不使用解码读取: 51 | ``` 52 | filedata = await part.read(decode=False) 53 | ``` 54 | 之后如果要解码的话也很简单: 55 | ``` 56 | filedata = part.decode(filedata) 57 | ``` 58 | 一旦完成了关于*Multipart*的处理,只需要跳出循环就好了: 59 | ``` 60 | break 61 | ``` 62 | 63 | 64 | # 发送Multipart请求 65 | `MultipartWriter`提供将Python数据转换到*Multipart*载体(以二进制流的形式)的接口。因为*Multipart*格式是递归的而且支持深层嵌套,所以你可以使用`with`语句设计*Multipart*数据的关闭流程: 66 | 67 | ``` 68 | with aiohttp.MultipartWriter('mixed') as mpwriter: 69 | ... 70 | with aiohttp.MultipartWriter('related') as subwriter: 71 | ... 72 | mpwriter.append(subwriter) 73 | 74 | with aiohttp.MultipartWriter('related') as subwriter: 75 | ... 76 | with aiohttp.MultipartWriter('related') as subsubwriter: 77 | ... 78 | subwriter.append(subsubwriter) 79 | mpwriter.append(subwriter) 80 | 81 | with aiohttp.MultipartWriter('related') as subwriter: 82 | ... 83 | mpwriter.append(subwriter) 84 | ``` 85 | 86 | `MultipartWriter.append()`用于将新的内容压入同一个流中。它可以接受各种输入,并且决定给这些输入用什么`headers`。 87 | 对于文本数据默认的`Content-Type`是`text/plain; charset=utf-8`: 88 | ``` 89 | mpwriter.append('hello') 90 | ``` 91 | 二进制则是`application/octet-stream`: 92 | ``` 93 | mpwriter.append(b'aiohttp') 94 | ``` 95 | 你也可以使用第二参数来覆盖默认值: 96 | ``` 97 | mpwriter.append(io.BytesIO(b'GIF89a...'), 98 | {'CONTENT-TYPE': 'image/gif'}) 99 | ``` 100 | 对于文件对象`Content-Type`会使用Python的`mimetypes`模块来做判断,此外,`Content-Disposition`头会把文件的基本名包含进去。 101 | ``` 102 | part = root.append(open(__file__, 'rb')) 103 | ``` 104 | 105 | 如果你想给文件设置个其他的名字,只需要操作`BodyPartWriter`实例即可,使用`BodyPartWriter.set_content_disposiition()`后`MultipartWriter.append()`方法总会显式的返回和设置`Content-Disposition`: 106 | ``` 107 | part.set_content_disposition('attachment', filename='secret.txt') 108 | ``` 109 | 此外,你还可以设置些其他的头信息: 110 | ``` 111 | part.headers[aiohttp.hdrs.CONTENT_ID] = 'X-12345' 112 | ``` 113 | 如果你设置了`Content-Encoding`,后续的数据都会自动编码: 114 | ``` 115 | part.headers[aiohttp.hdrs.CONTENT_ENCODING] = 'gzip' 116 | ``` 117 | 常用的方法还有`MultipartWriter.append_json()`和`MultipartWriter.append_form()`对JSON和表单数据非常好用,这样你就不需要每次都手动编码成需要的格式: 118 | ``` 119 | mpwriter.append_json({'test': 'passed'}) 120 | mpwriter.append_form([('key', 'value')]) 121 | ``` 122 | 最后,只需要将根`MultipartWriter`实例通过`aiohttp.client.request()`的`data`参数传递出去即可: 123 | ``` 124 | await aiohttp.post('http://example.com', data=mpwriter) 125 | ``` 126 | 后台的`MultipartWriter.serialize()`对每个部分都生成一个块,如果拥有`Content-Encoding`或者`Content-Transfer-Encoding`头信息会被自动应用到流数据上。 127 | 128 | 注意,在被MultipartWriter.serialize()处理时,所有的文件对象都会被读至末尾,不将文件指针重置到开始时是不能重复读取的。 129 | 130 | # Multipart使用技巧 131 | 132 | 互联网上充满陷阱,有时你可能会发现一个支持*Multipart*的服务器出现些奇怪的情况。 133 | 比如,如果服务器使用了`cgi.FieldStorage`,你就必须确认是否包含`Content-Length`头信息: 134 | ``` 135 | for part in mpwriter: 136 | part.headers.pop(aiohttp.hdrs.CONTENT_LENGTH, None) 137 | ``` 138 | 另一方面,有些服务器可能需要你为所有的`Multipart`请求指定`Content-Length`头信息。但`aiohttp`并不会指定因为默认是用块传输来发送*Multipart*的。要实现的话你必须连接`MultipartWriter`来计算大小: 139 | ``` 140 | body = b''.join(mpwriter.serialize()) 141 | await aiohttp.post('http://example.com', 142 | data=body, headers=mpwriter.headers) 143 | ``` 144 | 有时服务器的响应并没有一个很好的格式: 可能不包含嵌套部分。比如,我们请求的资源返回JSON和文件的混合体。如果响应中有任何附加信息,他们应该使用嵌套*Multipart*的形式。如果没有则是普通形式: 145 | ``` 146 | CONTENT-TYPE: multipart/mixed; boundary=--: 147 | 148 | --: 149 | CONTENT-TYPE: application/json 150 | 151 | {"_id": "foo"} 152 | --: 153 | CONTENT-TYPE: multipart/related; boundary=----: 154 | 155 | ----: 156 | CONTENT-TYPE: application/json 157 | 158 | {"_id": "bar"} 159 | ----: 160 | CONTENT-TYPE: text/plain 161 | CONTENT-DISPOSITION: attachment; filename=bar.txt 162 | 163 | bar! bar! bar! 164 | ----:-- 165 | --: 166 | CONTENT-TYPE: application/json 167 | 168 | {"_id": "boo"} 169 | --: 170 | CONTENT-TYPE: multipart/related; boundary=----: 171 | 172 | ----: 173 | CONTENT-TYPE: application/json 174 | 175 | {"_id": "baz"} 176 | ----: 177 | CONTENT-TYPE: text/plain 178 | CONTENT-DISPOSITION: attachment; filename=baz.txt 179 | 180 | baz! baz! baz! 181 | ----:-- 182 | --:-- 183 | ``` 184 | 在单个流内读取这样的数据是可以的,不过并不清晰: 185 | ``` 186 | result = [] 187 | while True: 188 | part = await reader.next() 189 | 190 | if part is None: 191 | break 192 | 193 | if isinstance(part, aiohttp.MultipartReader): 194 | # Fetching files 195 | while True: 196 | filepart = await part.next() 197 | if filepart is None: 198 | break 199 | result[-1].append((await filepart.read())) 200 | 201 | else: 202 | # Fetching document 203 | result.append([(await part.json())]) 204 | ``` 205 | 我们换一种方式来处理,让普通文档和与文件相关的读取器成对附到每个迭代器上: 206 | ``` 207 | class PairsMultipartReader(aiohttp.MultipartReader): 208 | 209 | # keep reference on the original reader 210 | multipart_reader_cls = aiohttp.MultipartReader 211 | 212 | async def next(self): 213 | """Emits a tuple of document object (:class:`dict`) and multipart 214 | reader of the followed attachments (if any). 215 | 216 | :rtype: tuple 217 | """ 218 | reader = await super().next() 219 | 220 | if self._at_eof: 221 | return None, None 222 | 223 | if isinstance(reader, self.multipart_reader_cls): 224 | part = await reader.next() 225 | doc = await part.json() 226 | else: 227 | doc = await reader.json() 228 | 229 | return doc, reader 230 | ``` 231 | 这样我们就可以更轻快的解决: 232 | ``` 233 | reader = PairsMultipartReader.from_response(resp) 234 | result = [] 235 | while True: 236 | doc, files_reader = await reader.next() 237 | 238 | if doc is None: 239 | break 240 | 241 | files = [] 242 | while True: 243 | filepart = await files_reader.next() 244 | if file.part is None: 245 | break 246 | files.append((await filepart.read())) 247 | 248 | result.append((doc, files)) 249 | ``` 250 | 251 | # 扩展 252 | Multipart API in Helpers API section. 253 | --------------------------------------------------------------------------------