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. Android 自动化测试——Monkey测试

    Android自带了很多方便的测试工具和方法,包括我们常用的单元测试.Robotium测试.Monkey测试.MonkeyRunner测试.senevent模拟等.这些方法对于我们编写高质量的APP十 ...

  2. 【iCore1S 双核心板_ARM】例程十:SYSTICK定时器实验——定时点亮LED

    实验原理: 通过STM32的三个GPIO口驱动三色LED的三个通道,设定GPIO为推挽输出,采用 灌电流的方式与LED连接,输出高电平LED灭,输出低电平LED亮,通过系统定时器实现 1s定时,每秒变 ...

  3. CMake结合PCL库学习(3)

    CMake常用指令结合PCL 的顶层CMake文件的解释 基本指令 (1)ADD_DEFINITIONS 向 C/C++编译器添加-D 定义,比如:ADD_DEFINITIONS(-DENABLE_D ...

  4. Qt动态库静态库的创建、使用、多级库依赖、动态库改成静态库等详细说明

    本文描述的是windows系统下,通过qtcreator在pro文件中添加动态库与静态库的方法: 1.添加动态库(直接添加动态库文件.dll,非子项目) 通过qtcreator创建动态库的方法就不在此 ...

  5. centos7系统下安装php-fpm并配置nginx支持并开启网站gzip压缩

    注:此处不介绍nginx的安装.以下教程默认已安装nginx. 1. yum install -y php-fpm yum install php-pdo yum install php-mysql ...

  6. 【Oracle】删除所有表

    BEGIN FOR cur_rec IN (SELECT object_name, object_type FROM user_objects WHERE object_type IN ('TABLE ...

  7. PHP在linux读取word文档

    几天帮朋友解决一个技术问题,在Linux下,将word文档中的内容读取,然后使用正则匹配,拼成sql入库 查阅了外文资料和google之后,步骤如下: #wget http://www.winfiel ...

  8. iOS - DNS劫持

    ******科普** 1.DNS劫持的危害 不知道大家有没有发现这样一个现象,在打开一些网页的时候会弹出一些与所浏览网页不相关的内容比如这样奇(se)怪(qing)的东西 图一   或者这样 图二   ...

  9. 转载->C#异常处理

    C# 异常处理 异常是在程序执行期间出现的问题.C# 中的异常是对程序运行时出现的特殊情况的一种响应,比如尝试除以零. 异常提供了一种把程序控制权从某个部分转移到另一个部分的方式.C# 异常处理时建立 ...

  10. 微信小游戏的本地缓存和清除的使用 (text.js image.js file-util.js)

    参考: 微信小游戏,文件系统 UpdateManager-小游戏 一.Egret提供的本地缓存工具类( 备注:新版本进行了修改,并增加了sound.js等) 在微信小游戏项目中,Egret提供了fil ...