AIOHTTP

用于asyncio和Python的异步HTTP客户端/服务器

主要特点:

  • 支持客户端和HTTP服务器。
  • 支持服务器WebSockets和 客户端WebSockets开箱即用,没有回调地狱。
  • Web服务器具有中间件, 信号和可插拔路由。

入门

客户端:

import aiohttp
import asyncio async def fetch(session, url):
async with session.get(url) as response:
return await response.text() async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://python.org')
print(html) loop = asyncio.get_event_loop()
loop.run_until_complete(main())  

服务端:

from aiohttp import web

async def handle(request):
name = request.match_info.get('name', "Anonymous")
text = "Hello, " + name
return web.Response(text=text) app = web.Application()
app.add_routes([web.get('/', handle),
web.get('/{name}', handle)]) web.run_app(app)

  

Web服务

 1、post、get接收参数数据

#!/usr/bin/env python
# -*- coding:utf-8 -*- import asyncio
from aiohttp import web class Application(object):
'''
aiohttp 接口
''' def __init__(self):
pass async def prepare_init(self):
'''
预加载
:return:
'''
self.hearders_setting = [
web.post('/', self.post),
web.get('/', self.get),
] async def get(self,request):
print(request.app['db'])
arguments = request.query
print(arguments)
# < MultiDictProxy('model': 'aiohttp') >
return web.Response(text='get') async def post(self,request):
arguments = await request.post()
print(arguments)
# < MultiDictProxy('model': 'aiohttp') >
# 获取ip地址
print(request.transport.get_extra_info('peername'))
# ('127.0.0.1', 53245)
print(request.path)
# /
print(request.raw_path)
return web.Response(text='post') async def app_factory(self):
'''
配置app
:return:
'''
await self.prepare_init()
app = web.Application()
app.add_routes(self.hearders_setting)
app['db'] = 'db'
return app def run_forever(self):
'''
开启端口
:return:
'''
web.run_app(self.app_factory()) Application().run_forever()
 import requests

 requests.post(url='http://127.0.0.1:8080/',data={'model':'aiohttp'},headers={'Content-Type': 'application/x-www-form-urlencoded'})
requests.get(url='http://127.0.0.1:8080/',params={'model':'aiohttp'},headers={'Content-Type': 'application/x-www-form-urlencoded'})

请求脚本

2、web处理程序取消

#!/usr/bin/env python
# -*- coding:utf-8 -*- import asyncio
from aiohttp import web async def something():
await asyncio.sleep(2)
print('handle out') async def post(request):
arguments = await request.post()
await something()
return web.Response(text='post')

注:当客户端请求过程中,中断请求,此时handle out 并不会打印执行,直接取消运行;

官方:await 如果客户端断开连接而不读取整个响应的BODY,则可以取消每个Web处理程序执行。这种行为与经典的WSGI框架(如Flask和Django)截然不同

有时它是一种理想的行为:在处理GET请求时,代码可能从数据库或其他Web资源获取数据,提取可能很慢。

  取消此非常好:对等已经断开连接,没有理由通过从DB获取数据而没有任何机会将其发送回对等来浪费时间和资源(内存等)。

  但有时取消很糟糕:根据POST要求,通常需要将数据保存到数据库,而不管对等关闭。

取消预防可以通过以下几种方式实施:

  • 应用于asyncio.shield()将数据保存到DB的协同程序
  • 产生DB保存的新任务
  • 使用aiojobs或其他第三方库

①  shield

import asyncio
from aiohttp import web async def something():
await asyncio.sleep(2)
print('handle out') async def post(request):
arguments = await request.post()
await asyncio.shield(something())
return web.Response(text='post')

asyncio.shield()工作得很好。唯一的缺点是你需要将Web处理程序分成两个异步函数:一个用于处理程序本身,另一个用于受保护的代码。

async def handler(request):
await asyncio.shield(write_to_redis(request))
await asyncio.shield(write_to_postgres(request))
return web.Response(text='OK')

在REDIS中保存数据后可能会发生取消, write_to_postgres不会被调用

② 创建任务

import asyncio
from aiohttp import web async def something():
await asyncio.sleep(2)
print('handle out') async def post(request):
arguments = await request.post()
request.loop.create_task(something())
return web.Response(text='post')

产生一项新任务的情况要糟糕得多:没有地方可以await 产生任务 ;request.loop.create_task(something()) 这个没有办法awit获取值,如果加上awit,则导致中断时,something停止工作

3、数据共享

aiohttp.web不鼓励使用全局变量,每个变量都应该有自己的非全局上下文。

所以,ApplicationRequest 支持一个collections.abc.MutableMapping接口(即它们是类似dict的对象),允许它们用作数据存储。

① 应用程序的配置

要存储类似全局变量,请随意将它们保存在 Application实例中:

app['my_private_key'] = data

并在Web处理程序中获取它:

async def handler(request):
data = request.app['my_private_key']

在嵌套应用程序的情况下,所需的查找策略可能如下:

  1. 搜索当前嵌套应用程序中的键。
  2. 如果未找到密钥,请继续在父应用程序中进行搜索。

为此,请使用Request.config_dict只读属性:

async def handler(request):
data = request.config_dict['my_private_key']

② 请求的存储

Variables that are only needed for the lifetime of a Request, can be stored in a Request:

async def handler(request):
request['my_private_key'] = "data"
...

这对于中间件和 信号处理程序来说非常有用,可以存储数据以供链中的下一个处理程序进一步处理。

③ 响应的存储

StreamResponseResponse对象也支持collections.abc.MutableMapping接口。当您希望在处理程序中的所有工作完成后与信号和中间件共享数据时,这非常有用:

async def handler(request):
[ do all the work ]
response['my_metric'] = 123
return response

4、中间件

aiohttp.web提供了一种通过中间件自定义请求处理程序的强大机制 。

一个中间件是可以修改请求或响应中的协程。例如,这是一个附加 到响应的简单中间件:'wink'

from aiohttp.web import middleware

@middleware
async def middleware(request, handler):
resp = await handler(request)
resp.text = resp.text + ' wink'
return resp

注意:该示例不适用于流式响应或websockets

每个中间件都应该接受两个参数,一个request实例和一个处理程序,并返回响应或引发异常。如果异常不是HTTPException它的实例,则500 HTTPInternalServerError在处理中间件链之后将 其转换为。

警告:第二个参数应该完全命名为handler

创建时Application,这些中间件将传递给仅限关键字的middlewares参数:

app = web.Application(middlewares=[middleware_1,
middleware_2])

在内部,通过以相反的顺序将中间件链应用于原始处理程序来构造单个请求处理程序,并由RequestHandler作为常规处理程序调用。

由于中间件本身就是协程,因此await在创建新的处理程序时可能会执行额外的 调用,例如调用数据库等。

中间件通常会调用处理程序,但是他们可能会选择忽略它,例如,如果用户没有访问底层资源的权限,则显示403 Forbidden页面或引发HTTPForbidden异常。它们还可能呈现处理程序引发的错误,执行一些预处理或后处理,如处理CORS等。

以下代码演示了中间件的执行顺序:

from aiohttp import web

async def test(request):
print('Handler function called')
return web.Response(text="Hello") @web.middleware
async def middleware1(request, handler):
print('Middleware 1 called')
response = await handler(request)
print('Middleware 1 finished')
return response @web.middleware
async def middleware2(request, handler):
print('Middleware 2 called')
response = await handler(request)
print('Middleware 2 finished')
return response app = web.Application(middlewares=[middleware1, middleware2])
app.router.add_get('/', test)
web.run_app(app) 

输出

Middleware 1 called
Middleware 2 called
Handler function called
Middleware 2 finished
Middleware 1 finished

中间件的常见用途是实现自定义错误页面。以下示例将使用JSON响应呈现404错误,因为可能适合JSON REST服务:

from aiohttp import web

@web.middleware
async def error_middleware(request, handler):
try:
response = await handler(request)
if response.status != 404:
return response
message = response.message
except web.HTTPException as ex:
if ex.status != 404:
raise
message = ex.reason
return web.json_response({'error': message}) app = web.Application(middlewares=[error_middleware])

中间件工厂

一个中间件工厂是创建与传递参数的中间件功能。例如,这是一个简单的中间件工厂:

def middleware_factory(text):
@middleware
async def sample_middleware(request, handler):
resp = await handler(request)
resp.text = resp.text + text
return resp
return sample_middleware

请记住,与常规中间件相反,您需要中间件工厂的结果而不是功能本身。因此,当将中间件工厂传递给应用程序时,您实际需要调用它:

app = web.Application(middlewares=[middleware_factory(' wink')])

  

5、信号

虽然middleware可以在准备响应之前或之后定制请求处理程序,但在准备响应时不能定制响应。For this aiohttp.web provides signals.

例如,中间件只能为未准备好的 响应更改HTTP标头(请参阅参考资料StreamResponse.prepare()),但有时我们需要一个钩子来更改流式响应和WebSockets的HTTP标头。这可以通过订阅Application.on_response_prepare信号来完成 :

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

此外,可以订阅Application.on_startup和 Application.on_cleanup信号以进行应用程序组件设置并相应地拆除。

以下示例将正确初始化并配置aiopg连接引擎:

from aiopg.sa import create_engine

async def create_aiopg(app):
app['pg_engine'] = await create_engine(
user='postgre',
database='postgre',
host='localhost',
port=5432,
password=''
) async def dispose_aiopg(app):
app['pg_engine'].close()
await app['pg_engine'].wait_closed() app.on_startup.append(create_aiopg)
app.on_cleanup.append(dispose_aiopg)

信号处理程序不应返回值,但可以修改传入的可变参数。

信号处理程序将按顺序运行,以便添加它们。从aiohttp 3.0开始,所有处理程序必须是异步的。

6、清理上下文

Application.on_startupApplication.on_cleanup 对仍有陷阱:信号处理程序彼此独立。

E.g. we have [create_pg, create_redis] in startup signal and [dispose_pg,dispose_redis] in cleanup.

If, for example, create_pg(app) call fails create_redis(app) is not called. But on application cleanup both dispose_pg(app) and dispose_redis(app) are still called:

清理信号不知道启动/清理对及其执行状态。

解决方案是Application.cleanup_ctx用法:

async def pg_engine(app):
app['pg_engine'] = await create_engine(
user='postgre',
database='postgre',
host='localhost',
port=5432,
password=''
)
yield
app['pg_engine'].close()
await app['pg_engine'].wait_closed() app.cleanup_ctx.append(pg_engine)

属性是列表生成器,代码之前 yield是(称为上初始化阶段的启动),码 之后yield被上执行清理。生成器必须只有一个yield

aiohttp保证当且仅当启动代码成功完成时才调用 清理代码。

Python 3.6+支持异步生成器,在Python 3.5上请使用async_generator 库。

版本3.1中的新功能。

 7、后台任务

有时需要在应用程序启动后执行一些异步操作。

更重要的是,在一些复杂的系统中,可能需要在事件循环中运行一些后台任务以及应用程序的请求处理程序。例如,监听消息队列或其他网络消息/事件源(例如,ZeroMQ,Redis Pub / Sub,AMQP等)以对应用程序内的接收消息作出反应。

例如,后台任务可以在zmq.SUB套接字上侦听ZeroMQ ,处理并将检索到的消息转发到通过WebSocket连接的客户端,这些客户端存储在应用程序中的某个位置(例如,在application['websockets']列表中)。

为了运行这种短期和长期运行的后台任务,aiohttp提供了注册Application.on_startup将与应用程序的请求处理程序一起运行的信号处理程序的能力。

例如,需要运行一个快速任务和两个长时间运行的任务,这些任务将一直存在,直到应用程序处于活动状态。相应的后台任务可以注册为Application.on_startup 信号处理程序,如下例所示:

async def listen_to_redis(app):
try:
sub = await aioredis.create_redis(('localhost', 6379), loop=app.loop)
ch, *_ = await sub.subscribe('news')
async for msg in ch.iter(encoding='utf-8'):
# Forward message to all connected websockets:
for ws in app['websockets']:
ws.send_str('{}: {}'.format(ch.name, msg))
except asyncio.CancelledError:
pass
finally:
await sub.unsubscribe(ch.name)
await sub.quit() async def start_background_tasks(app):
app['redis_listener'] = app.loop.create_task(listen_to_redis(app)) async def cleanup_background_tasks(app):
app['redis_listener'].cancel()
await app['redis_listener'] app = web.Application()
app.on_startup.append(start_background_tasks)
app.on_cleanup.append(cleanup_background_tasks)
web.run_app(app)

任务listen_to_redis()将永远运行。要正确关闭它,Application.on_cleanup信号处理程序可用于向其发送取消。

8、处理异常错误

https://aiohttp-demos.readthedocs.io/en/latest/tutorial.html#aiohttp-demos-polls-middlewares

9、request中获取add_routes中url

    async def post(self, request):
url = [resource._path for resource in request.app.router._resources]
print(url)
# ['/', '/6773', '/', '/1234/']
Response = web.Response(text='post')
return Response

  

Python开发【模块】:aiohttp(一)的更多相关文章

  1. python开发模块基础:re正则

    一,re模块的用法 #findall #直接返回一个列表 #正常的正则表达式 #但是只会把分组里的显示出来#search #返回一个对象 .group()#match #返回一个对象 .group() ...

  2. python开发模块基础:异常处理&hashlib&logging&configparser

    一,异常处理 # 异常处理代码 try: f = open('file', 'w') except ValueError: print('请输入一个数字') except Exception as e ...

  3. python开发模块基础:os&sys

    一,os模块 os模块是与操作系统交互的一个接口 #!/usr/bin/env python #_*_coding:utf-8_*_ ''' os.walk() 显示目录下所有文件和子目录以元祖的形式 ...

  4. python开发模块基础:序列化模块json,pickle,shelve

    一,为什么要序列化 # 将原本的字典.列表等内容转换成一个字符串的过程就叫做序列化'''比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?现在我们能想到的方法就是存在文 ...

  5. python开发模块基础:time&random

    一,time模块 和时间有关系的我们就要用到时间模块.在使用模块之前,应该首先导入这个模块 常用方法1.(线程)推迟指定的时间运行.单位为秒. time.sleep(1) #括号内为整数 2.获取当前 ...

  6. python开发模块基础:collections模块&paramiko模块

    一,collections模块 在内置数据类型(dict.list.set.tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter.deque.defaultdic ...

  7. python开发模块基础:正则表达式

    一,正则表达式 1.字符组:[0-9][a-z][A-Z] 在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示字符分为很多类,比如数字.字母.标点等等.假如你现在要求一个位置&q ...

  8. Python开发——目录

    Python基础 Python开发——解释器安装 Python开发——基础 Python开发——变量 Python开发——[选择]语句 Python开发——[循环]语句 Python开发——数据类型[ ...

  9. Python开发【第六篇】:模块

    模块,用一砣代码实现了某个功能的代码集合. 类似于函数式编程和面向过程编程,函数式编程则完成一个功能,其他代码用来调用即可,提供了代码的重用性和代码间的耦合.而对于一个复杂的功能来,可能需要多个函数才 ...

  10. python辅助开发模块(非官方)如pil,mysqldb,openpyxl,xlrd,xlwd

    官方文档 只是支持win32, 不支持win64 所以很麻烦 民间高人,集中做了一堆辅助库,下载后,用python安装目录下的scripts中,pip和easy_install就可以安装了 pytho ...

随机推荐

  1. mininet下建立拓扑时关于远程控制器的一个小问题

    最近重装了系统和mininet后,使用mininet时遇到了一点小问题,一开始忽视了细节,使得自己被这个问题困扰了好一会儿,好在后来还是发现了问题所在,故记录下来. $ sudo mn --topo ...

  2. DOS、Mac 和 Unix 文件格式+ UltraEdit使用

    文件格式 区分DOS.Mac 和 Unix分别对应三种系统 从文件编码的方式来看,文件可分为ASCII码文件和二进制码文件两种 文件模式 区分ASCII模式和Binary模式  通常由系统决定,大多数 ...

  3. [Artoolkit] Android Sample of nftSimple

    结合:[Artoolkit] ARToolKit's SDK Structure on Android 重难点:aRBaseLib/, nftSimple/, libcpufeatures.a aRB ...

  4. SpringBoot 国际化配置,SpringBoot Locale 国际化

    SpringBoot 国际化配置,SpringBoot Locale 国际化 ================================ ©Copyright 蕃薯耀 2018年3月27日 ht ...

  5. 使用log4cplus时遇到的链接错误:无法解析的外部符号 "public: static class log4cplus::Logger __cdecl log4cplus::Logger::getInstance(class std::basic_string<wchar_t,struct std::char_traits<wchar_t>,

    #include "stdafx.h" #include <log4cplus/logger.h> #include <log4cplus/loggingmacr ...

  6. 推荐一款好用的文件/文件夹对比工具 —— Beyond Compare

    推荐一款好用的文件/文件夹对比工具 —— Beyond Compare! 有需要的人,用了都说好: 不知道这个是干嘛用的,说再多也没用.

  7. Elasticsearch 学习之携程机票ElasticSearch集群运维驯服记(强烈推荐)

    转自: https://mp.weixin.qq.com/s/wmSTyIGCVhItVNPHcH7nsA 一.整体架构 为什么采用ES作为搜索引擎呢?在做任何事情的时候,不要一上来就急着了解怎么做这 ...

  8. maven子项目的springboot配置

    正常来说一个maven子项目的parent是父项目,而不是直接继承,这时候就需要改下配置 默认生成的代码入下: <?xml version="1.0" encoding=&q ...

  9. poj3376 Finding Palindromes【exKMP】【Trie】

    Finding Palindromes Time Limit: 10000MS   Memory Limit: 262144K Total Submissions:4710   Accepted: 8 ...

  10. SQL Server 2012 安装过程详解(包含每一步设置的含义)

    转http://www.cnblogs.com/EastLiRoar/p/4051969.html 一.启动安装程序,点击“安装”选项卡,选择“全新SQL Server独立安装或向现有安装添加功能”. ...