aiohttp

官方文档

https://docs.aiohttp.org/en/stable/client_quickstart.html

 

客户端快速入门

渴望开始?这个页面很好地介绍了如何开始使用 aiohttp 客户端 API。

首先,确保 aiohttp 已安装并且是最新的

让我们从一些简单的例子开始。

提出请求

首先导入 aiohttp 模块和 asyncio:

import aiohttp
import asyncio

现在,让我们尝试获取一个网页。例如让我们查询 http://httpbin.org/get

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://httpbin.org/get') as resp:
            print(resp.status)
            print(await resp.text())

asyncio.run(main)

现在,我们有一个ClientSession被调用对象session和一个 被调用ClientResponse对象resp我们可以从响应中获取我们需要的所有信息。coroutine的强制参数 ClientSession.get()是一个 HTTP urlstr或类:yarl.URL实例)。

为了使 HTTP POST 请求使用ClientSession.post()协程:

session.post('http://httpbin.org/post', data=b'data')

其他 HTTP 方法也可用:

session.put('http://httpbin.org/put', data=b'data')
session.delete('http://httpbin.org/delete')
session.head('http://httpbin.org/get')
session.options('http://httpbin.org/get')
session.patch('http://httpbin.org/patch', data=b'data')

为了使对同一个站点的多个请求更简单,可以使用构造函数的参数base_url 。ClientSession例如,请求不同的端点http://httpbin.org可以使用以下代码:

async with aiohttp.ClientSession('http://httpbin.org') as session:
    async with session.get('/get'):
        pass
    async with session.post('/post', data=b'data'):
        pass
    async with session.put('/put', data=b'data'):
        pass

笔记

不要为每个请求创建会话。您很可能需要每个应用程序一个会话来完全执行所有请求。

更复杂的情况可能需要每个站点一个会话,例如一个用于 Github,另一个用于 Facebook API。无论如何,为每个请求创建一个会话是一个非常糟糕的主意。

会话内部包含一个连接池。连接重用和保持活动(默认情况下都打开)可以提高整体性能。

会话上下文管理器的使用不是强制性的,但在这种情况下应该调用方法,例如:await session.close()

session = aiohttp.ClientSession()
async with session.get('...'):
    # ...
await session.close()

在 URL 中传递参数

您经常希望在 URL 的查询字符串中发送某种数据。如果您手动构建 URL,则此数据将作为 URL 中问号后的键/值对给出,例如httpbin.org/get?key=valdictRequests 允许您使用 params关键字参数将这些参数作为 提供。举个例子,如果你想传递 key1=value1and key2=value2httpbin.org/get你可以使用下面的代码:

params = {'key1': 'value1', 'key2': 'value2'}
async with session.get('http://httpbin.org/get',
                       params=params) as resp:
    expect = 'http://httpbin.org/get?key1=value1&key2=value2'
    assert str(resp.url) == expect

通过打印 URL,您可以看到 URL 已正确编码。

MultiDict可以使用为同一键发送具有多个值的数据 ;该库也支持嵌套列表 ( ) 替代方案。{'key': ['value1', 'value2']}

也可以将 2 项元组的列表作为参数传递,在这种情况下,您可以为每个键指定多个值:

params = [('key', 'value1'), ('key', 'value2')]
async with session.get('http://httpbin.org/get',
                       params=params) as r:
    expect = 'http://httpbin.org/get?key=value2&key=value1'
    assert str(r.url) == expect

您也可以将str内容作为参数传递,但要注意 - 内容不是由库编码的。请注意,+未编码:

async with session.get('http://httpbin.org/get',
                       params='key=value+1') as r:
        assert str(r.url) == 'http://httpbin.org/get?key=value+1'

笔记

aiohttp在发送请求之前在内部执行 URL 规范化。

规范化通过IDNA编解码器 对主机部分进行编码,并将重新引用应用于路径查询部分。

例如URL('http://example.com/путь/%30?a=%31')转换为 URL('http://example.com/%D0%BF%D1%83%D1%82%D1%8C/0?a=1').

如果服务器接受精确的表示并且不重新引用 URL 本身,则有时规范化是不可取的。

encoded=True要禁用URL 构造的规范化使用参数:

await session.get(
    URL('http://example.com/%30', encoded=True))

警告

传递params overrides encoded=True,永远不要同时使用这两个选项。

响应内容和状态码

我们可以读取服务器响应的内容及其状态码。再次考虑 GitHub 时间线:

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

打印出类似的东西:

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

aiohttp自动解码来自服务器的内容。您可以为该text()方法指定自定义编码:

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

二进制响应内容

对于非文本请求,您还可以将响应正文作为字节访问:

print(await resp.read())
b'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...

传输编码会自动为您解码gzipdeflate

您可以启用brotli传输编码支持,只需安装 brotli

JSON 请求

任何会话的请求方法,如request()ClientSession.get()等都ClientSession.post()接受 json参数:

async with aiohttp.ClientSession() as session:
    async with session.post(url, json={'test': 'object'})

默认情况下,会话使用 python 的标准json模块进行序列化。但可以使用不同的 serializerClientSession接受json_serialize 参数:

import ujson

async with aiohttp.ClientSession(
        json_serialize=ujson.dumps) as session:
    await session.post(url, json={'test': 'object'})

笔记

ujson库比标准库快,json但略有不兼容。

JSON 响应内容

还有一个内置的 JSON 解码器,以防您处理 JSON 数据:

async with session.get('https://api.github.com/events') as resp:
    print(await resp.json())

如果 JSON 解码失败,json()将引发异常。可以为json()调用指定自定义编码和解码器函数。

笔记

上述方法将整个响应体读入内存。如果您计划读取大量数据,请考虑使用下面记录的流式响应方法。

流式响应内容

While 方法read(), json()并且text()非常方便,您应该谨慎使用它们。所有这些方法都将整个响应加载到内存中。例如,如果您要下载几个千兆字节大小的文件,这些方法将加载内存中的所有数据。相反,您可以使用该content 属性。它是aiohttp.StreamReader 类的一个实例。和transfer-encodingsgzipdeflate自动为您解码:

async with session.get('https://api.github.com/events') as resp:
    await resp.content.read(10)

但是,一般来说,您应该使用这样的模式来保存正在流式传输到文件的内容:

with open(filename, 'wb') as fd:
    async for chunk in resp.content.iter_chunked(chunk_size):
        fd.write(chunk)

无法使用read()json()并且text()在显式读取content.

更复杂的 POST 请求

通常,您希望发送一些表单编码的数据——很像 HTML 表单。为此,只需将字典传递给data参数。发出请求时,您的数据字典将自动进行表单编码:

payload = {'key1': 'value1', 'key2': 'value2'}
async with session.post('http://httpbin.org/post',
                        data=payload) as resp:
    print(await resp.text())
{
  ...
  "form": {
    "key2": "value2",
    "key1": "value1"
  },
  ...
}

如果要发送未进行表单编码的数据,可以通过传递 abytes而不是a 来实现dict此数据将直接发布,默认情况下内容类型设置为 'application/octet-stream':

async with session.post(url, data=b'\x00Binary-data\x00') as resp:
    ...

如果要发送 JSON 数据:

async with session.post(url, json={'example': 'test'}) as resp:
    ...

要发送具有适当内容类型的文本,只需使用data参数:

async with session.post(url, data='Тест') as resp:
    ...

发布一个多部分编码的文件

上传多部分编码的文件:

url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}

await session.post(url, data=files)

您可以明确设置filenameand :content_type

url = 'http://httpbin.org/post'
data = FormData()
data.add_field('file',
               open('report.xls', 'rb'),
               filename='report.xls',
               content_type='application/vnd.ms-excel')

await session.post(url, data=data)

如果您将文件对象作为数据参数传递,aiohttp 将自动将其流式传输到服务器。检查StreamReader 支持的格式信息。

也可以看看

使用多部分

流式上传

aiohttp支持多种类型的流式上传,这使您可以发送大文件而无需将它们读入内存。

作为一个简单的案例,只需为您的 body 提供一个类似文件的对象:

with open('massive-body', 'rb') as f:
   await session.post('http://httpbin.org/post', data=f)

或者您可以使用异步生成器

async def file_sender(file_name=None):
    async with aiofiles.open(file_name, 'rb') as f:
        chunk = await f.read(64*1024)
        while chunk:
            yield chunk
            chunk = await f.read(64*1024)

# Then you can use file_sender as a data provider:

async with session.post('http://httpbin.org/post',
                        data=file_sender(file_name='huge_file')) as resp:
    print(await resp.text())

因为该content属性是一个 StreamReader(提供异步迭代器协议),所以您可以将 get 和 post 请求链接在一起:

resp = await session.get('http://python.org')
await session.post('http://httpbin.org/post',
                   data=resp.content)

笔记

Python 3.5 没有对异步生成器的原生支持,使用 async_generator库作为解决方法。

3.1 版后已弃用:aiohttp仍支持aiohttp.streamer装饰器,但不推荐使用此方法,而支持异步生成器,如上所示。

WebSockets

aiohttp可与开箱即用的客户端 websocket 一起使用。

您必须使用aiohttp.ClientSession.ws_connect()协程进行客户端 websocket 连接。它接受一个url作为第一个参数并返回ClientWebSocketResponse,使用该对象,您可以使用响应的方法与 websocket 服务器进行通信:

async with session.ws_connect('http://example.org/ws') as ws:
    async for msg in ws:
        if msg.type == aiohttp.WSMsgType.TEXT:
            if msg.data == 'close cmd':
                await ws.close()
                break
            else:
                await ws.send_str(msg.data + '/answer')
        elif msg.type == aiohttp.WSMsgType.ERROR:
            break

必须使用唯一的 websocket 任务来读取(例如or )和写入,但可能有多个只能异步发送数据的写入器任务( 例如)。await ws.receive()async for msg in ws:await ws.send_str('data')

Timeouts

超时设置存储在ClientTimeout数据结构中。

默认情况下,aiohttp使用总共300 秒(5 分钟)超时,这意味着整个操作应该在 5 分钟内完成。

该值可以被会话的超时参数覆盖(以秒为单位):

timeout = aiohttp.ClientTimeout(total=60)
async with aiohttp.ClientSession(timeout=timeout) as session:
    ...

对于如下请求,超时可能会被覆盖ClientSession.get()

async with session.get(url, timeout=timeout) as resp:
    ...

支持ClientTimeout的字段是:

total

整个操作的最大秒数,包括建立连接、发送请求和读取响应。

connect

如果超出池连接限制,则建立新连接或等待池中的空闲连接的最大秒数。

sock_connect

为新连接连接到对等点的最大秒数,不是从池中给出的。

sock_read

从对等点读取新数据部分之间允许的最大秒数。

所有字段都是浮动的,None或者0禁用特定的超时检查,请参阅 ClientTimeout参考以获取默认值和其他详细信息。

因此,默认超时为:

aiohttp.ClientTimeout(total=5*60, connect=None,
                      sock_connect=None, sock_read=None)

笔记

如果值等于或大于 5 秒,则aiohttp ceils超时。超时在大于 的下一个整数秒到期 current_time timeout

设置上限是为了优化,当许多并发任务被安排在几乎相同但不同的绝对时间唤醒时。它会导致非常多的事件循环唤醒,从而降低性能。

优化通过将绝对唤醒时间安排到与其他邻居完全相同的时间来改变绝对唤醒时间,循环每秒唤醒一次以超时到期。

较小的超时不会四舍五入以帮助测试;在现实生活中,网络超时通常大于几十秒。

 

 

高级用法

高级客户端使用

客户端会话

ClientSession是所有客户端 API 操作的核心和主要入口点。

首先创建会话,使用实例执行 HTTP 请求并启动 WebSocket 连接。

会话包含 cookie 存储和连接池,因此 cookie 和连接在同一会话发送的 HTTP 请求之间共享。

自定义请求标头

如果您需要将 HTTP 标头添加到请求中,请将它们传递给 dictheaders参数

例如,如果您想直接指定内容类型:

url = 'http://example.com/image'
payload = b'GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00'
          b'\x00\x00\x01\x00\x01\x00\x00\x02\x00;'
headers = {'content-type': 'image/gif'}

await session.post(url,
                   data=payload,
                   headers=headers)

您还可以为所有会话请求设置默认标头:

headers={"Authorization": "Basic bG9naW46cGFzcw=="}
async with aiohttp.ClientSession(headers=headers) as session:
    async with session.get("http://httpbin.org/headers") as r:
        json_body = await r.json()
        assert json_body['headers']['Authorization'] == \
            'Basic bG9naW46cGFzcw=='

典型用例是发送 JSON 正文。可以如上图直接指定内容类型,但使用特殊关键字更方便 json

await session.post(url, json={'example': 'text'})

对于文本/纯文本

await session.post(url, data='Привет, Мир!')

笔记

Authorization如果您被重定向到不同的主机或协议,标头将被删除。

自定义 Cookie 

要将自己的 cookie 发送到服务器,可以使用构造函数的cookies 参数ClientSession

url = 'http://httpbin.org/cookies'
cookies = {'cookies_are': 'working'}
async with ClientSession(cookies=cookies) as session:
    async with session.get(url) as resp:
        assert await resp.json() == {
           "cookies": {"cookies_are": "working"}}

笔记

httpbin.org/cookies端点以 JSON 编码的正文返回请求 cookie。要访问会话 cookie,请参阅ClientSession.cookie_jar

ClientSession可用于在多个请求之间共享 cookie:

async with aiohttp.ClientSession() as session:
    await session.get(
        'http://httpbin.org/cookies/set?my_cookie=my_value')
    filtered = session.cookie_jar.filter_cookies(
        'http://httpbin.org')
    assert filtered['my_cookie'].value == 'my_value'
    async with session.get('http://httpbin.org/cookies') as r:
        json_body = await r.json()
        assert json_body['cookies']['my_cookie'] == 'my_value'

响应头和 Cookie 

我们可以ClientResponse.headers使用以下命令查看服务器的响应CIMultiDictProxy

assert resp.headers == {
    'ACCESS-CONTROL-ALLOW-ORIGIN': '*',
    'CONTENT-TYPE': 'application/json',
    'DATE': 'Tue, 15 Jul 2014 16:49:51 GMT',
    'SERVER': 'gunicorn/18.0',
    'CONTENT-LENGTH': '331',
    'CONNECTION': 'keep-alive'}

不过,该字典很特别:它仅用于 HTTP 标头。根据RFC 7230,HTTP 标头名称不区分大小写。它还支持与 HTTP 协议一样的同一键的多个值。

因此,我们可以使用我们想要的任何大小写来访问标题:

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

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

surrogateescape所有标头都使用带有选项的 UTF-8 从二进制数据转换而来 。这在大多数情况下都可以正常工作,但如果服务器使用非标准编码,有时需要未转换的数据。虽然这些标头格式不正确从RFC 7230 的角度来看,它们可以通过使用 ClientResponse.raw_headers属性来检索:

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

如果响应包含一些HTTP Cookies,您可以快速访问它们:

url = 'http://example.com/some/cookie/setting/url'
async with session.get(url) as resp:
    print(resp.cookies['example_cookie_name'])

笔记

响应 cookie 仅包含重定向链中最后一个Set-Cookie请求的标头中的值。要在所有重定向请求之间收集 cookie,请使用aiohttp.ClientSession对象。

重定向历史

如果请求被重定向,则可以使用history属性查看以前的响应:

resp = await session.get('http://example.com/some/redirect/')
assert resp.status == 200
assert resp.url = URL('http://example.com/some/other/url/')
assert len(resp.history) == 1
assert resp.history[0].status == 301
assert resp.history[0].url = URL(
    'http://example.com/some/redirect/')

如果没有发生重定向或allow_redirects设置为False,则历史记录将是一个空序列。

饼干罐

Cookie 安全

默认情况下ClientSession使用严格版本的 aiohttp.CookieJar.RFC 2109明确禁止 cookie 从带有 IP 地址而不是 DNS 名称的 URL 接受(例如http://127.0.0.1:80/cookie)。

这很好,但有时为了测试,我们需要启用对此类 cookie 的支持。它应该通过传递unsafe=True给 aiohttp.CookieJar构造函数来完成:

jar = aiohttp.CookieJar(unsafe=True)
session = aiohttp.ClientSession(cookie_jar=jar)

Cookie 引用例程

客户端使用的SimpleCookie报价例程符合RFC 2109,它又引用了来自的字符定义RFC 2068它们提供了一种双向引用算法,其中任何非文本字符都被转换为 4 个字符序列:正斜杠后跟该字符的三位八进制等效值。任何\"用前面的\斜杠引用。由于浏览器真正处理 cookie 的方式(与 RFC 所说的相反),我们还编码,;.

一些后端系统不支持引用的 cookie。quote_cookie=False您可以通过传递给 CookieJar构造函数来跳过此引用例程:

jar = aiohttp.CookieJar(quote_cookie=False)
session = aiohttp.ClientSession(cookie_jar=jar)

虚拟饼干罐

有时 cookie 处理是不可取的。为此,可以将aiohttp.DummyCookieJar实例传递给客户端会话:

jar = aiohttp.DummyCookieJar()
session = aiohttp.ClientSession(cookie_jar=jar)

上传预压缩数据

要在将数据传递给 aiohttp 之前上传已经压缩的数据,请使用使用的压缩算法名称(通常为deflategzip)作为 Content-Encoding标头的值调用请求函数:

async def my_coroutine(session, headers, my_data):
    data = zlib.compress(my_data)
    headers = {'Content-Encoding': 'deflate'}
    async with session.post('http://httpbin.org/post',
                            data=data,
                            headers=headers)
        pass

禁用 JSON 响应的内容类型验证

该标准明确将 JSON Content-TypeHTTP 标头限制为 application/json或任何扩展形式,例如application/vnd.custom-type+json不幸的是,有些服务器发送了错误的类型,例如text/html.

这可以通过两种方式解决:

  1. 显式传递预期类型(在这种情况下,检查将是严格的,没有扩展表单支持,因此custom/xxx+type不会被接受):

    await resp.json(content_type='custom/type').

  2. 完全禁用检查:

    await resp.json(content_type=None).

客户追踪

可以遵循特定请求的执行流程,将侦听器协程附加到实例提供的信号 TraceConfig,该实例将用作ClientSession构造函数的参数,因此客户端会触发 TraceConfig默认情况下,任何 ClientSession类实例都禁用了信号能力。以下代码段显示了如何遵循请求流的开始和结束信号:

async def on_request_start(
        session, trace_config_ctx, params):
    print("Starting request")

async def on_request_end(session, trace_config_ctx, params):
    print("Ending request")

trace_config = aiohttp.TraceConfig()
trace_config.on_request_start.append(on_request_start)
trace_config.on_request_end.append(on_request_end)
async with aiohttp.ClientSession(
        trace_configs=[trace_config]) as client:
    client.get('http://example.com/some/redirect/')

trace_configs是一个列表,可以包含 TraceConfig允许运行来自不同实例的信号处理程序的类TraceConfig实例。以下示例显示了如何TraceConfig安装两个具有不同性质的不同组件以在每个信号句柄中执行其工作:

from mylib.traceconfig import AuditRequest
from mylib.traceconfig import XRay

async with aiohttp.ClientSession(
        trace_configs=[AuditRequest(), XRay()]) as client:
    client.get('http://example.com/some/redirect/')

所有信号首先作为参数,即ClientSession 与该信号相关的特定请求使用的实例,其次是一个SimpleNamespace名为 trace_config_ctxtrace_config_ctx对象可用于通过属于同一请求和同一TraceConfig类的不同信号共享状态,也许:

async def on_request_start(
        session, trace_config_ctx, params):
    trace_config_ctx.start = asyncio.get_event_loop().time()

async def on_request_end(session, trace_config_ctx, params):
    elapsed = asyncio.get_event_loop().time() - trace_config_ctx.start
    print("Request took {}".format(elapsed))

trace_config_ctx默认情况下,参数是 SimpleNamespace请求流开始时初始化的 a。trace_config_ctx_factory但是,可以使用类的构造函数参数覆盖用于创建此对象的工厂TraceConfig

参数可以在trace_request_ctx请求执行开始时给出,被所有 HTTP 动词接受,并将作为trace_config_ctx_factory 工厂的关键字参数传递。此参数对于传递仅在请求时可用的数据很有用,可能是:

async def on_request_start(
        session, trace_config_ctx, params):
    print(trace_config_ctx.trace_request_ctx)


session.get('http://example.com/some/redirect/',
            trace_request_ctx={'foo': 'bar'})

也可以看看

跟踪参考部分以获取有关支持的不同信号的更多信息。

Connectors

要调整或更改请求的传输层,您可以将自定义 连接器传递给ClientSession和系列。例如:

conn = aiohttp.TCPConnector()
session = aiohttp.ClientSession(connector=conn)

笔记

默认情况下,会话对象拥有连接器的所有权,其中包括在会话关闭后关闭连接。如果您热衷于通过不同的会话实例共享同一个连接器, 则必须将 每个会话实例的connector_owner参数设置为False

也可以看看

连接器部分,了解有关不同连接器类型和配置选项的更多信息。

限制连接池大小

要限制同时打开的连接数量,您可以将限制 参数传递给连接器

conn = aiohttp.TCPConnector(limit=30)

该示例将并行连接的总数限制为30

默认值为100

如果您明确不希望有限制,请传递0例如:

conn = aiohttp.TCPConnector(limit=0)

要限制同时打开的连接到同一端点(三重)的数量,您可以将limit_per_host 参数传递给连接器(host, port, is_ssl)

conn = aiohttp.TCPConnector(limit_per_host=30)

该示例将并行连接的数量限制为30

默认值为0(对每个主机基础没有限制)。

调整 DNS 缓存

默认TCPConnector开启DNS缓存表,解析默认缓存10秒。可以将此行为更改为更改分辨率的 TTL,如以下示例所示:

conn = aiohttp.TCPConnector(ttl_dns_cache=300)

或禁用 DNS 缓存表,这意味着所有请求最终都会进行 DNS 解析,如以下示例所示:

conn = aiohttp.TCPConnector(use_dns_cache=False)

使用自定义名称服务器解析

为了在解析主机名时指定名称服务器, 需要aiodns

from aiohttp.resolver import AsyncResolver

resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"])
conn = aiohttp.TCPConnector(resolver=resolver)

Unix 域套接字

如果您的 HTTP 服务器使用 UNIX 域套接字,您可以使用 UnixConnector

conn = aiohttp.UnixConnector(path='/path/to/socket')
session = aiohttp.ClientSession(connector=conn)

Windows 中的命名管道

如果您的 HTTP 服务器使用命名管道,您可以使用 NamedPipeConnector

conn = aiohttp.NamedPipeConnector(path=r'\\.\pipe\<name-of-pipe>')
session = aiohttp.ClientSession(connector=conn)

它只适用于 ProactorEventLoop

TCP 套接字的 SSL 控制

默认情况下, aiohttp对 HTTPS 协议使用严格检查。可以通过将ssl设置为来放宽认证检查False

r = await session.get('https://example.com', ssl=False)

如果您需要设置自定义 ssl 参数(例如使用自己的认证文件),您可以创建一个ssl.SSLContext实例并将其传递给正确的ClientSession方法:

sslcontext = ssl.create_default_context(
   cafile='/path/to/ca-bundle.crt')
r = await session.get('https://example.com', ssl=sslcontext)

如果您需要验证自签名ssl.SSLContext.load_cert_chain()证书,您可以执行与上一个示例相同的操作,但使用密钥对添加另一个调用 :

sslcontext = ssl.create_default_context(
   cafile='/path/to/ca-bundle.crt')
sslcontext.load_cert_chain('/path/to/client/public/device.pem',
                           '/path/to/client/private/device.key')
r = await session.get('https://example.com', ssl=sslcontext)

ssl 验证失败时出现显式错误

aiohttp.ClientConnectorSSLError:

try:
    await session.get('https://expired.badssl.com/')
except aiohttp.ClientConnectorSSLError as e:
    assert isinstance(e, ssl.SSLError)

aiohttp.ClientConnectorCertificateError:

try:
    await session.get('https://wrong.host.badssl.com/')
except aiohttp.ClientConnectorCertificateError as e:
    assert isinstance(e, ssl.CertificateError)

如果您需要跳过与 ssl 相关的两个错误

aiohttp.ClientSSLError:

try:
    await session.get('https://expired.badssl.com/')
except aiohttp.ClientSSLError as e:
    assert isinstance(e, ssl.SSLError)

try:
    await session.get('https://wrong.host.badssl.com/')
except aiohttp.ClientSSLError as e:
    assert isinstance(e, ssl.CertificateError)

您还可以通过SHA256指纹验证证书:

# Attempt to connect to https://www.python.org
# with a pin to a bogus certificate:
bad_fp = b'0'*64
exc = None
try:
    r = await session.get('https://www.python.org',
                          ssl=aiohttp.Fingerprint(bad_fp))
except aiohttp.FingerprintMismatch as e:
    exc = e
assert exc is not None
assert exc.expected == bad_fp

# www.python.org cert's actual fingerprint
assert exc.got == b'...'

请注意,这是 DER 编码证书的指纹。如果您拥有 PEM 格式的证书,则可以将其转换为 DER,例如:

openssl x509 -in crt.pem -inform PEM -outform DER > crt.der

笔记

提示:要将十六进制摘要转换为二进制字节串,您可以使用binascii.unhexlify().

ssl参数可以TCPConnector作为默认值传递给,值 from ClientSession.get()和其他覆盖默认值。

代理支持

aiohttp 支持普通的 HTTP 代理和可以通过 HTTP CONNECT 方法升级到 HTTPS 的 HTTP 代理。aiohttp 对必须通过 via 连接的代理的支持有限https://- 请参阅下面的信息框了解更多详细信息。要连接,请使用代理参数:

async with aiohttp.ClientSession() as session:
    async with session.get("http://python.org",
                           proxy="http://proxy.com") as resp:
        print(resp.status)

它还支持代理授权:

async with aiohttp.ClientSession() as session:
    proxy_auth = aiohttp.BasicAuth('user', 'pass')
    async with session.get("http://python.org",
                           proxy="http://proxy.com",
                           proxy_auth=proxy_auth) as resp:
        print(resp.status)

身份验证凭据可以在代理 URL 中传递:

session.get("http://python.org",
            proxy="http://user:pass@some.proxy.com")

requests库相反,默认情况下它不会读取环境变量。但是你可以通过 trust_env=True传入构造函数来从HTTP_PROXYHTTPS_PROXYWS_PROXYWSS_PROXY环境变量aiohttp.ClientSession 中提取代理配置 (所有这些都不区分大小写):

async with aiohttp.ClientSession(trust_env=True) as session:
    async with session.get("http://python.org") as resp:
        print(resp.status)

3.8 版新功能:从 aiohttp v3.8 开始支持WS_PROXYWSS_PROXY

~/.netrc如果存在,则从文件 中提供代理凭据(aiohttp.ClientSession有关详细信息,请参阅)。

注意力

CPython 在 Python 3.7 周围的 TLS 中引入了对 TLS 的支持。但是,截至目前(Python 3.10),它对于 asyncio使用的传输被禁用。如果 Python 的进一步发布(比如 v3.11)切换了一个属性,它就会正常工作™

aiohttp v3.8 及更高版本已准备好发生这种情况,并且有代码支持 TLS-in-TLS,因此通过 HTTPS 代理隧道发送 HTTPS 请求。

⚠️ 只要您的 Python 运行时未声明支持 TLS-in-TLS,请不要使用 aiohttp 提交错误,而是尝试帮助 CPython 上游启用此功能。同时,如果你 真的需要这个工作,有一个补丁可以帮助你实现它,将它包含到你的应用程序的代码库中: https ://github.com/aio-libs/aiohttp/discussions/6044#discussioncomment-1432443 .

重要的

提供自定义ssl.SSLContext实例时,请记住,它不仅用于与您正在访问的 HTTPS 端点建立 TLS 会话,还用于建立到 HTTPS 代理的 TLS 隧道。为避免意外,请确保设置能够识别端点和代理使用的 TLS 证书的信任链。

优雅关机

当在块ClientSession结束时 (或通过直接调用)关闭时,由于 asyncio 内部细节,底层连接保持打开状态。在实践中,底层连接将在片刻后关闭。但是,如果事件循环在底层连接关闭之前停止, 则会发出警告(启用警告时)。async withClientSession.close()ResourceWarning: unclosed transport

为了避免这种情况,必须在关闭事件循环之前添加一个小的延迟,以允许任何打开的底层连接关闭。

对于ClientSession没有 SSL 的情况,一个简单的零睡眠 ( ) 就足够了:await asyncio.sleep(0)

async def read_website():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://example.org/') as resp:
            await resp.read()

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

对于ClientSession使用 SSL,应用程序必须在关闭前等待一小段时间:

...
# Wait 250 ms for the underlying SSL connections to close
loop.run_until_complete(asyncio.sleep(0.250))
loop.close()

请注意,等待的适当时间量因应用程序而异。

如果这最终会在 asyncio 内部发生变化时变得过时,以便 aiohttp 本身可以等待底层连接关闭。请关注 issue #1925了解这方面的进展。

 

示例

复制代码
import aiohttp
import asyncio
import requests


async def main():
    data = {
        "password": "123456",
        "username": "admin"
    }
    response = requests.post('http://49.234.14.39:5001/user/login', json=data)
    _token = response.json()['access_token']
    print(_token)
    async with aiohttp.ClientSession('http://49.234.14.39:5001') as session:
        data2 = {
            "access_token": _token
        }
        for i in range(10):
            async with session.post('/user/query', json=data2) as response:
                print(await response.json())

        async with session.post('/user/init',json=data2) as response:
            print(await response.json())


asyncio.run(main())


class TestAioHttp:

    def __init__(self):
        data = {
            "password": "123456",
            "username": "admin"
        }
        self.response = requests.post('http://49.234.14.39:5001/user/login', json=data)
        self._token = self.response.json()['access_token']

    async def main(self):
        for i in range(5):
            async with aiohttp.ClientSession('http://49.234.14.39:5001') as session:
                data2 = {
                    "access_token": self._token
                }
                for i in range(2):
                    async with session.post('/user/query', json=data2) as response:
                        print(await response.json())

                async with session.post('/user/init', json=data2) as response:
                    print(await response.json())

    async def test_xlrd(self):
        data3 = {
            "access_token": self._token
        }
        for i in range(2):
            async with aiohttp.ClientSession().request(method='post', url='http://49.234.14.39:5001/user/init', json=data3,
                                                       )as resp:
                print(await resp.json())


if __name__ == '__main__':
    t = TestAioHttp()

    # asyncio.run(t.main())
    asyncio.run(t.test_xlrd())
示例
复制代码

 

posted @   断浪狂刀忆年少  阅读(586)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示