17

I'm testing aiohttp and asyncio. I want the same event loop to have a socket, http server, http client.

I'm using this sample code:

@routes.get('/')
async def hello(request):
    return web.Response(text="Hello, world")

app = web.Application()
app.add_routes(routes)
web.run_app(app)

The problem is run_app is blocking. I want to add the http server into an existing event loop, that I create using:

asyncio.get_event_loop()
user3599803
  • 6,435
  • 17
  • 69
  • 130

2 Answers2

22

The problem is run_app is blocking. I want to add the http server into an existing event loop

run_app is just a convenience API. To hook into an existing event loop, you can directly instantiate the AppRunner:

loop = asyncio.get_event_loop()
# add stuff to the loop
...

# set up aiohttp - like run_app, but non-blocking
runner = aiohttp.web.AppRunner(app)
loop.run_until_complete(runner.setup())
site = aiohttp.web.TCPSite(runner)    
loop.run_until_complete(site.start())

# add more stuff to the loop
...

loop.run_forever()

In asyncio 3.8 and later you can use asyncio.run():

async def main():
    # add stuff to the loop, e.g. using asyncio.create_task()
    ...

    runner = aiohttp.web.AppRunner(app)
    await runner.setup()
    site = aiohttp.web.TCPSite(runner)    
    await site.start()

    # add more stuff to the loop, if needed
    ...

    # wait forever
    await asyncio.Event().wait()

asyncio.run(main())
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • 1
    Thanks, any reason why the server wont terminate on SIGINT (pressing CTRL+C)? I'm using windows powershell – user3599803 Nov 25 '18 at 08:48
  • @user3599803 Not using Windows, sorry, but googling possibly reveals it as [a long-standing issue](https://stackoverflow.com/q/27480967/1600898) in asyncio. – user4815162342 Nov 25 '18 at 09:11
  • 1
    @user3599803 https://stackoverflow.com/a/37420223/2955584 check this answer for terminating the script in windows machine – rsb Nov 26 '18 at 11:18
  • 1
    Just a note how to specify IP and port, which takes some googling: `site = web.TCPSite(runner, host='0.0.0.0', port=80)` – omegastripes Mar 26 '22 at 11:20
10

For the future traveler from Google, here is a simpler way.

async def main():
    await aio.gather(
        web._run_app(app, port=args.port),
        SomeotherTask(),
        AndAnotherTask()
    )

aio.run(main())

Explanation: web.runapp is a thin wrapper over internal function web._runapp. The function uses the old style way of getting the eventloop and then calling loop.run_until_complete.

We replace it with aio.gather alongside other tasks that we want to run concurrently and use the aio.run to schedule them

Source

Adnan Y
  • 2,982
  • 1
  • 26
  • 29
  • 1
    They now provide the Application Runner API to allow for this use case: https://docs.aiohttp.org/en/stable/web_advanced.html#application-runners – Stuart Axon Jun 10 '20 at 10:18
  • 7
    Please avoid using private APIs in StackOverflow answers. Methods starting with `_`, such as `_run_app`, can be removed, renamed, or change meaning without prior notice. – user4815162342 Jul 21 '20 at 14:57