在aiohttp应用程序中收听ZeroMQ

时间:2016-08-12 21:12:27

标签: python zeromq gunicorn python-asyncio aiohttp

我在aiohttp后面Gunicorn运行nginx申请。 在我的应用程序的初始化模块中,我不使用web.run_app(app)运行应用程序,而是创建一个将由Gunicorn导入的实例,以便在每个工作程序Gunicorn中运行它创建。 因此Gunicorn会在其中创建一些工作进程,事件循环,然后在这些循环中runs应用程序的请求处理程序。

我的aiohttp应用程序有一组连接的WebSockets(移动应用程序客户端),我想通知Gunicorn启动的任何应用程序进程中发生的事件。 我想通知与所有申请流程相关的所有 WebSockets。 因此,我使用ZeroMQ创建某种上游代理,并且我希望使用每个应用程序进程中的zmq.SUB套接字来订阅它。

...所以基本上我想在每个应用程序工作者中实现类似的东西:

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect('tcp://localhost:5555')

while True:
    event = socket.recv()
    for ws in app['websockets']:
        ws.send_bytes(event)
    # break before app shutdown. How?

如何在ZeroMQ应用程序中收听aiohttp代理以将邮件转发至WebSockets

在哪里可以将此代码放在事件循环中的后台运行以及如何在aiohttp应用程序的生命周期内正确运行和关闭它?

更新

我已经在aiohttp的GitHub存储库中创建了一个issue来描述问题并提出了一个可能的解决方案。我非常感谢在这里或那里就所描述问题提出的意见。

1 个答案:

答案 0 :(得分:2)

好的,关于这个issue的问题和讨论导致了我为aiohttp做出的新功能,即版本 1.0 我们将拥有能够使用on_startup方法注册Application.on_startup()应用程序信号。

Documentation
Working example on the master branch

#!/usr/bin/env python3
"""Example of aiohttp.web.Application.on_startup signal handler"""
import asyncio

import aioredis
from aiohttp.web import Application, WebSocketResponse, run_app

async def websocket_handler(request):
    ws = WebSocketResponse()
    await ws.prepare(request)
    request.app['websockets'].append(ws)
    try:
        async for msg in ws:
            print(msg)
            await asyncio.sleep(1)
    finally:
        request.app['websockets'].remove(ws)
    return ws


async def on_shutdown(app):
    for ws in app['websockets']:
        await ws.close(code=999, message='Server shutdown')


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))
            print("message in {}: {}".format(ch.name, msg))
    except asyncio.CancelledError:
        pass
    finally:
        print('Cancel Redis listener: close connection...')
        await sub.unsubscribe(ch.name)
        await sub.quit()
        print('Redis connection closed.')


async def start_background_tasks(app):
    app['redis_listener'] = app.loop.create_task(listen_to_redis(app))


async def cleanup_background_tasks(app):
    print('cleanup background tasks...')
    app['redis_listener'].cancel()
    await app['redis_listener']


async def init(loop):
    app = Application(loop=loop)
    app['websockets'] = []
    app.router.add_get('/news', websocket_handler)
    app.on_startup.append(start_background_tasks)
    app.on_cleanup.append(cleanup_background_tasks)
    app.on_shutdown.append(on_shutdown)
    return app

loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))
run_app(app)