前记 状态弃坑中,官方文档更新了结构要重新编排 **完美弃坑,已有人翻译出来:https://hubertroy.gitbooks.io/aiohttp-chinese-documentation/content/ **
aiohtpp 是用于asyncio和Python的HTTP客户端/服务器。 也就是既能充当用户的请求,也能当做一个服务器。
ps:语法使用python3.5的语法,也就是从
1 2 3 @asyncio.coroutine def coro (... ): ret = yield from f()
改为
1 2 async def coro (... ): ret = await f()
之前为了学习英语+想查文档然后就简单的翻译下原网址
入门 aiohttp当做请求时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import aiohttpimport asyncioimport async_timeoutasync def fetch (session, url ): with async_timeout.timeout(10 ): async with session.get(url) as response: return await response.text()async def main (): async with aiohttp.ClientSession() as session: html = await fetch(session, 'http://python.org' ) print(html) loop = asyncio.get_event_loop() loop.run_until_complete(main())
当做服务器时
1 2 3 4 5 6 7 8 9 10 11 12 from aiohttp import webasync def handle (request ): name = request.match_info.get('name' , "Anonymous" ) text = "Hello, " + name return web.Response(text=text) app = web.Application() app.router.add_get('/' , handle) app.router.add_get('/{name}' , handle) web.run_app(app)
使用aiohttp进行请求 提出请求 先对 url:https://api.github.com/events 进行请求,然后返回文本
1 2 3 4 5 6 7 8 9 10 11 import aiohttpimport asyncioasync 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())
可以看出根requests的语法很像,代码中有一个ClientSession被调用session的ClientResponse对象resp。我们可以从响应中获得所需的所有信息,除了get外,也支持其他http协议
1 2 3 4 5 6 session.post('http://httpbin.org/post' , data=b'data' ) 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' )
需要注意的是,不必要为每个请求创建一个session,当有多个请求时,把url传入session就可以了,session中包含一个连接池,默认情况下打开连接重用和保持活动
JSON请求 通过以下设置,可以让aiohttp发起一个json请求
1 2 async with aiohttp.ClientSession() as session: async with session.post(json={'test' : 'object' })
默认情况下使用python自带的json序列,如果需要更改可以更改ClientSession里的json_serialize参数
1 2 3 4 import ujsonasync with aiohttp.ClientSession(json_serialize=ujson.dumps) as session: async with session.post(json={'test' : 'object' })
在url中传递参数 如果有多次相同请求,只是网址中的键/值不一样时,如httpbin.org/get?key=val,那可以使用params关键字参数,把封装好的dict给aiohttp调用
1 2 3 4 params = {'key1' : 'value1' , 'key2' : 'value2' }async with session.get('http://httpbin.org/get' , params=params) as resp: assert str (resp.url) == 'http://httpbin.org/get?key2=value2&key1=value1'
如果是需要一对多形式的键/值可以采用如下形式,为每个键指定多个值
1 2 3 4 params = [('key' , 'value1' ), ('key' , 'value2' )]async with session.get('http://httpbin.org/get' , params=params) as r: assert str (r.url) == 'http://httpbin.org/get?key=value2&key=value1'
也可以使用str来当做参数
1 2 3 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 标准化 例如URL(‘http://example.com/путь%30?a=%31')被转换为 URL(‘http://example.com/%D0%BF%D1%83%D1%82%D1%8C/0?a=1')。 如果服务器不接受时,可以通过设置encoded来改变
1 await session.get(URL('http://example.com/%30' , encoded=True ))
解析的内容
使用text() 在aiohttp中会根据网站定义的编码来自动解析网页中的内容,当然,也可以通过设置encoding变量来定义我们要解析的编码格式,如果习惯使用resquests的,要注意这里是text(),待括号的1 await resp.text(encoding='windows-1251' )
使用read() 则解析出来的是二进制的内容,且网页内容过多时,无法全部解析
使用json() 解析后自动转化为json格式1 2 async with session.get('https://api.github.com/events' ) as resp: print(await resp.json())
流响应内容 就是像我们在用python打开文件时,如果文件过大,都不会一下子去打开的,可能是一行一行打开,也可能是一段一段打开,总之就是为了避免内存爆满,后面的东西无法写入。 可以使用这样写入1 2 async with session.get('https://api.github.com/events' ) as resp: await resp.content.read(10 )
但是最好的方式应该为:1 2 3 4 5 6 with open (filename, 'wb' ) as fd: while True : chunk = await resp.content.read(chunk_size) if not chunk: break fd.write(chunk)
aiohttp中单的ClientResposeClientResponse对象包含request_info属性,其中包含Headers和cookies,可以通过一定的设置来向服务器发送不同的东西。
定义Headers1 2 3 4 5 6 7 8 import json url = 'https://api.github.com/some/endpoint' payload = {'some' : 'data' } headers = {'content-type' : 'application/json' }await session.post(url, data=json.dumps(payload), headers=headers)
定义Cookies1 2 3 4 5 6 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" }}
发送表单请求 在session.post()方法中通过定义data可以设置表单的内容
1 2 3 4 payload = {'key1' : 'value1' , 'key2' : 'value2' }async with session.post('http://httpbin.org/post' , data=payload) as resp: print(await resp.text())
发送文件 在发送文件时,也可以通过定义filename和content-type来设置文件名和类型
1 2 3 4 5 6 7 8 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)
发送大文件 如果文件过大,可以通过流的形式进行上传
1 2 with open ('massive-body' , 'rb' ) as f: await session.post('http://httpbin.org/post' , data=f)
或者可以使用@aiohttp.streamer装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 @aiohttp.streamer def file_sender (writer, file_name=None ): with open (file_name, 'rb' ) as f: chunk = f.read(2 **16 ) while chunk: yield from writer.write(chunk) chunk = f.read(2 **16 )async with session.post('http://httpbin.org/post' , data=file_sender(file_name='huge_file' )) as resp: print(await resp.text())
也可以使用StreamReader对象进行上传,比如从一个请求下来的内容再上传回去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 async def feed_stream (resp, stream ): h = hashlib.sha256() while True : chunk = await resp.content.readany() if not chunk: break h.update(chunk) stream.feed_data(chunk) return h.hexdigest() resp = session.get('http://httpbin.org/post' ) stream = StreamReader() loop.create_task(session.post('http://httpbin.org/post' , data=stream)) file_hash = await feed_stream(resp, stream)
上传预压缩的数据 要将传递给aiohttp之前已压缩的数据上传,请使用所使用的压缩算法名称(通常是deflate或zlib)作为Content-Encoding头的值来调用请求函数:
1 2 3 4 5 6 7 async def my_coroutine (session, headers, my_data ): data = zlib.compress(my_data) headers = {'Content-Encoding' : 'deflate' } async with session.post('http://httpbin.org/post' , data=data, headers=headers) pass
连接池,Keep-Alive和 cookie共享 ClientSession 支持保持活动的请求和连接池的开箱即用 ClientSession 可用于在多个请求之间共享cookie
1 2 3 4 5 6 7 8 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'
还可以为所有会话请求设置默认标题
1 2 3 4 5 6 async with aiohttp.ClientSession( headers={"Authorization" : "Basic bG9naW46cGFzcw==" }) as session: async with session.get("http://httpbin.org/headers" ) as r: json_body = await r.json() assert json_body['headers' ]['Authorization' ] == \ 'Basic bG9naW46cGFzcw=='
cookie安全 RFC 2109明确禁止Cookie使用IP地址而不是DNS名称来接受URL(例如 http://127.0.0.1:80/cookie)。 默认情况下ClientSession通过aiohttp.CookieJar提供该功能。 通过将unsafe = True传递给 aiohttp.CookieJar构造函数来完成,启用对这种cookie的支持
1 2 jar = aiohttp.CookieJar(unsafe=True ) session = aiohttp.ClientSession(cookie_jar=jar)
连接器 要调整或更改传输层的请求,您可以将自定义连接器传递 给ClientSession
1 2 conn = aiohttp.TCPConnector() session = aiohttp.ClientSession(connector=conn)
通过连机器限制连接池大小 默认是100个请求,但可以通过limit来设置连接池大小,如果limit是0则代表无限制
1 conn = aiohttp.TCPConnector(limit=30 )
如果要限制同时打开链接到同一个端点的数量,可以将limit_per_host 参数传递给连接器((host, port, is_ssl) triple)
1 conn = aiohttp.TCPConnector(limit_per_host=30 )
使用自定义名称服务器解析 1 2 3 4 from aiohttp.resolver import AsyncResolver resolver = AsyncResolver(nameservers=["8.8.8.8" , "8.8.4.4" ]) conn = aiohttp.TCPConnector(resolver=resolver)
默认情况下,aiohttp使用严格的HTTPS协议检查。通过将verify_ssl设置为False可以放宽认证检查
1 r = await session.get('https://example.com' , verify_ssl=False )
如果需要设置自定义ssl参数(例如使用自己的认证文件),您可以创建一个ssl.SSLContext实例并将其传递给ClientSession方法
1 2 3 sslcontext = ssl.create_default_context( cafile='/path/to/ca-bundle.crt' ) r = await session.get('https://example.com' , ssl_context=sslcontext)
如果您需要验证自签名证书,则可以执行与上一个示例相同的操作,但可以使用ssl.SSLContext.load_cert_chain()添加密钥
1 2 3 4 5 sslcontext = ssl.create_default_context( cafile='/path/to/ca-bundle.crt' ) sslcontext.load_cert_chain('/path/to/client/public/device.pem' , '/path/to/client/private/device.jey' ) r = await session.get('https://example.com' , ssl_context=sslcontext)
ssl验证失败时会有明显的错误
1 2 3 4 5 6 try : await session.get('https://expired.badssl.com/' )except aiohttp.ClientConnectorSSLError as e: assert isinstance (e, ssl.SSLError)
1 2 3 4 5 6 try : await session.get('https://wrong.host.badssl.com/' )except aiohttp.ClientConnectorCertificateError as e: assert isinstance (e, ssl.CertificateError)
如果需要跳过两个SSL相关的错误
1 2 3 4 5 6 7 8 9 10 11 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验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bad_fingerprint = b'0' *64 exc = None try : r = await session.get('https://www.python.org' , fingerprint=bad_fingerprint)except aiohttp.FingerprintMismatch as e: exc = eassert exc is not None assert exc.expected == bad_fingerprintassert exc.got == b'...'
Unix域名sockets 如果您的HTTP服务器使用UNIX域名sockets,则可以使用 UnixConnector:
1 2 conn = aiohttp.UnixConnector(path='/path/to/socket' ) session = aiohttp.ClientSession(connector=conn)
使用代理
原话:aiohttp supports支持http/https 代理
然而使用时如果有https代理,会说只支持http代理- -…
1 2 3 4 async with aiohttp.ClientSession() as session: async with session.get("http://python.org" , proxy="http://some.proxy.com" ) as resp: print(resp.status)
如果代理需要账户密码:
1 2 3 4 5 6 async with aiohttp.ClientSession() as session: proxy_auth = aiohttp.BasicAuth('user' , 'pass' ) async with session.get("http://python.org" , proxy="http://some.proxy.com" , proxy_auth=proxy_auth) as resp: print(resp.status)
不过与requests库相反,它不会默认读取环境变量。但是,您以通过 trust_env=True传入aiohttp.ClientSession 构造函数来从HTTP_PROXY或HTTPS_PROXY 环境变量中提取代理配置 (两者都不区分大小写)
1 2 3 async with aiohttp.ClientSession() as session: async with session.get("http://python.org" , trust_env=True ) as resp: print(resp.status)
响应状态码 可以通过如下查看状态码
1 2 async with session.get('http://httpbin.org/get' ) as resp: assert resp.status == 200
请求头 1 2 3 4 5 6 7 >>> 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文件,但是一份特殊的dict根据RFC 7230,HTTP标头名称是不区分大小写的。它也支持与HTTP协议相同的密钥的多个值。 所以,我们可以使用任何我们想要的大小来访问头文件
1 2 3 4 5 >>> resp.headers['Content-Type' ]'application/json' >>> resp.headers.get('content-type' )'application/json'
都使用UTF-8从二进制数据转换为 surrogateescape选项。这在大多数情况下工作正常,但有时如果服务器使用非标准编码,则需要未转换的数据,可以通过使用ClientResponse.raw_headers属性来检索
1 2 3 4 5 6 >>> 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' ))
获取请求的cookie 1 2 3 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属性查看之前的响应
1 2 3 4 5 >>> resp = 等待 会话。get ('http://example.com/some/redirect/' )>>> resp <ClientResponse(http://example.com/some/other/url/)[200 ]> >>> resp 。历史 (<ClientResponse(http://example.com/some/redirect/)[301 ]>)
如果没有发生重定向或allow_redirects设置为False,历史记录将是一个空的序列
WebSockets 在aiohttp中WebSockets是开箱即用的,但是必须使用aiohttp.ClientSession.ws_connect()协程来进行客户端websocket连接。它接受一个url作为第一个参数并返回ClientWebSocketResponse,通过该对象,你可以使用响应的方法与websocket服务器进行通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 session = aiohttp.ClientSession()async with session.ws_connect('http://example.org/websocket' ) as ws: async for msg in ws: if msg.type == aiohttp.WSMsgType.TEXT: if msg.data == 'close cmd' : await ws.close() break else : await ws.send_str(msg.data + '/answer' ) elif msg.type == aiohttp.WSMsgType.CLOSED: break elif msg.type == aiohttp.WSMsgType.ERROR: break
注意:只能使用一个websocket任务进行读写(如await ws.receive() 和 async for msg in ws:) 但是可以使用多个异步读写数据(如:ws.send_str(‘data’)
超时 默认情况下所有IO操作都有5分钟的超时时间 可以通过传入timeout参数到ClientSession.get()来设置超时,当设置None或0时,禁用超时检查
1 async with session.get('https://github.com' , timeout=60 ) as r:
如果是按流的形式写入数据,可以使用async_timeout.timeout(),为连接和响应正文读取过程添加了超时
1 2 3 4 5 import async_timeoutwith async_timeout.timeout(0.001 ): async with session.get('https://github.com' ) as r: await r.text()
注意的是超时是累计时间,包括发送请求,重定向,响应解析,消费响应等所有操作。
优雅的关闭(就是比较好的关闭协程???) 当ClientSession在块的末尾关闭(或通过直接调用)时,由于asyncio内部细节,基础连接保持打开状态在实践中,基础连接将在一段时间后关闭。但是,如果事件循环在底层连接关闭之前停止,则会发出警告(启用警告时):
async with.close()ResourceWarning: unclosed transport
为了避免这种情况,必须在关闭事件循环之前添加一个小的延迟,以允许任何打开的底层连接关闭 对于ClientSession没有使用SSL:
1 2 3 4 5 6 7 8 9 10 async def read_website (): async with aiohttp.ClientSession() as session: async with session.get('http://example.org/' ) as response: await response.read() loop = asyncio.get_event_loop() loop.run_until_complete(read_website()) loop.run_until_complete(asyncio.sleep(0 )) loop.close()
对于ClientSession使用SSL,在结束之前必须等待很短的时间:
1 2 3 4 5 6 7 8 9 async def read_website (): async with aiohttp.ClientSession() as session: async with session.get('http://example.org/' ) as response: await response.read() loop = asyncio.get_event_loop() loop.run_until_complete(read_website()) loop.run_until_complete(asyncio.sleep(0.250 )) loop.close()