异步启停 aiohttp 服务程序
一、引言
aiohttp 是一个很好的 python 异步 web 库,可以用它建立高效的 web 服务端和客户端。它支持 http 协议,也支持 websocket 长连接。
一般情况下,开发 web 服务端时,我们会用 run_app 启动一个 aiohttp 的 Application,run_app 是阻塞运行的,直到整个程序的退出才关闭。
如果需要在一个线程的序运行中按需启停服务,或者是启动多个服务,就需要异步地启动 Application,本文根据 aiohttp 的官方文档,对这个问题进行一些探讨。
有关异步启停和优雅关闭服务方面可供参考的官方文档网址如下:https://docs.aiohttp.org/en/stable/web_advanced.html#aiohttp-web-graceful-shutdown
二、aiohttp 服务端的基本用法
aiohttp 官网给出服务端的基本用法 。启动一个服务端须经历以下几个步骤:
1. 生成一个 aiohttp.web.Application 实例;
2. 编写一个或多个服务程序;
3. 将服务程序加入 Application 实例的路由(route);
4. 运行 Application 实例,启动服务。
下面的程序简单演示了 aiohttp 的基本用法:
from aiohttp import web
# 2. 编写服务程序;
async def hello(request):
return web.Response(text="Hello, world")
# 1. 生成 Application 实例;
app = web.Application()
# 3. 将服务加入 Application 的路由;
app.add_routes([web.get('/', hello)])
# 4. 启动服务。
web.run_app(app, host="127.0.0.1", port=8888)
运行这个程序,在本机打开浏览器,地址栏输入:127.0.0.1:8888。浏览器就会显示服务返回的“Hello world”字符串。
上述程序中,还可以用 route 装饰形成路由表,启动前直接将路由表加入 Application 实例:
from aiohttp import web
# 定义路由表
routes = web.RouteTableDef()
# 定义路由表中的应用服务程序
@routes.get('/')
async def hello(request):
return web.Response(text="Hello, world")
app = web.Application()
# 将路由表加入 Application 实例
app.add_routes(routes)
web.run_app(app, host="127.0.0.1", port=8888)
三、异步启停 Application 实例
上述 web.run_app 方法是阻塞运行的。它无法在一个线程中启动多个不同 url 的服务,也不方便服务的随时启动和停止。为此,aiohttp 提供了 runner 类,可以异步启停多个服务。
上述使用 web.run_app 方法运行 Application 的程序略加修改,就可以使用 runner 类启动:
from aiohttp import web
import asyncio
async def hello(request):
return web.Response(text="Hello, world")
# 由于 runner 的设置和 site 的启动是异步方法,所以它们必须处于一个异步函数中
async def run_server():
app = web.Application()
app.add_routes([web.get('/', hello)])
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, 'localhost', 8080)
# 异步启动 site,下面的语句不阻塞
await site.start()
# 如果需要启停一个或多个服务,可以将代码放在这个位置,但要保证 run_server 不退出,
# 否则所有的服务都退出了。
# 这是模拟的代码,保证 run_server 不退出。
while True:
await asyncio.sleep(3600)
# 启动程序的同步代码
asyncio.run(run_server())
它的运行结果和和前面的程序是一样的,但与之相比,这个程序做了如下修改和补充:
1. 用已经生成的 Application 实例建立一个 web.AppRunner 的实例(runner);
2. 用 setup 方法对 runner 进行设置;
3. 建立一个 web.TCPSite 实例,并用 start 方法启动这个实例。
如果将 run_server 方法的最后两行(while True: 和 await asyncio …两行)改为:asyncio.sleep(20),再运行这个程序就会发现:运行20秒后程序就退出了。
实际应用程序中,服务的退出不应该如此粗暴,关于优雅地退出服务的内容,可以参见官网文档。