Python 的异步 IO:Aiohttp Client 代码分析

字节追梦者
• 阅读 19468

Python 的异步 IO:Aiohttp Client 代码分析

Aiohttp 是 Python 的一个 HTTP 框架,基于 asyncio,所以叫 Aiohttp。

我主要是看源码,想理解它的设计,所以附上了类图与时序图。不可避免的,可读性会比较差。
想找教程的话,请移步 官方教程,写得还是挺不错的。

一个例子

下面这个例子,通过 HTTP GET 列出 GitHub 的 public events

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.github.com/events') as resp:
            print(resp.status)
            print(await resp.text())

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Response 是一个 JSON 格式的文本:

[
  {
    "id": "6888907432",
    "type": "PushEvent",
    "actor": {
      "id": 3956266,
      "login": "sekineh",
      "display_login": "sekineh",
      "gravatar_id": "",
      "url": "https://api.github.com/users/sekineh",
      "avatar_url": "https://avatars.githubusercontent.com/u/3956266?"
    },
    ...
]

ClientSession 是一个 Asynchronous Context Manager,所以搭配 async with 语句一起使用。像下面这样应该也是可以的:

async def main():
    session = aiohttp.ClientSession()
    ...
    await session.close()

不过肯定是不推荐的,就当是帮助理解吧。

ClientSession.get() 返回一个 ClientResponse 对象,通过 text() 方法,可以拿到 response 的文本:

print(await resp.text())

当然,text() 是一个协程:

    @asyncio.coroutine
    def text(self, encoding=None, errors='strict'):
        """Read response payload and decode."""
        ...

Connector

ClientSession 依赖 Connector 来创建连接,缺省为 TCPConnector,它继承自 BaseConnector,此外还有 UnixConnector(应该是 Unix Domain Socket)。

Connector 的接口比较简单,主要提供了 connect() 方法(也是协程):

    @asyncio.coroutine
    def connect(self, req):
        """Get from pool or create new connection."""
        ...

以及 close() 方法:

    def close(self):
        """Close all opened transports."""
        ...

ConnectionKey

ClientRequest 有个属性 connection_key

class ClientRequest:
    @property
    def connection_key(self):
        return ConnectionKey(self.host, self.port, self.ssl)

它是一个 namedtuple

ConnectionKey = namedtuple('ConnectionKey', ['host', 'port', 'ssl'])

hostportssl 三个元素组成,这三个元素可以唯一定义一个连接,所以叫 ConnectionKey
文章开头的那个例子中,ConnectionKey 为:

ConnectionKey(host='api.github.com', port=443, ssl=True)

全局函数 request()

Aiohttp 为 Client 程序提供了一个全局函数 request(),用法如下:

async def main():
    resp = await aiohttp.request('GET', 'http://python.org/')
    print(resp)
    
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

可见 request() 只是 ClientSession 的一个简单封装,其步骤大致为:

  • 创建 TCPConnector
  • 创建 ClientSession
  • 调用 ClientSession._request()

建议不要直接使用 request(),而只把它当成 ClientSession 的一个样例。因为 Aiohttp 官方文档是 这样说的

Don’t create a session per request. Most likely you need a session per application which performs all requests altogether.

A session contains a connection pool inside, connection reusage and keep-alives (both are on by default) may speed up total performance.

即,一个 request 用一个 session,太浪费;通常是一个 application 用一个 session。

一些小问题

我经常发现一个变量,明明可以是局部变量,却被当成了成员变量。

Request 里放了一个 response?

class ClientRequest:
    def send(self, conn):
        ...
        self.response = self.response_class(
            self.method, self.original_url,
            writer=self._writer, ...
        )

        self.response._post_init(self.loop, self._session)
        return self.response

self.responseClientRequest 其他地方并没有用到,是否可以改成局部变量?

ClientResponse.start() 里的 _protocol 应该用局部变量吧?

class ClientResponse:
   @asyncio.coroutine
    def start(self, connection, read_until_eof=False):
        """Start response processing."""
        self._closed = False
        self._protocol = connection.protocol

类图

Python 的异步 IO:Aiohttp Client 代码分析

时序图

Python 的异步 IO:Aiohttp Client 代码分析

The End

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
字节追梦者
字节追梦者
Lv1
扁舟坐泊,危亭孤啸,目断闲云千里。
文章
4
粉丝
0
获赞
0