Multiple aiohttp Application()'s running in the same process?

Viewed 6919

Can two aiohttp.web.Application() objects be running in the same process, e.g. on different ports?

I see a bunch of examples of aiohttp code like:

from aiohttp import web
app = web.Application()
app.router.add_get('/foo', foo_view, name='foo')
web.run_app(app, host='0.0.0.0', port=10000)

I'm wondering if there's some equivalent where multiple web.Applications() can be configured to run at the same time. Something like:

from aiohttp import web
app1 = web.Application()
app1.router.add_get('/foo', foo_view, name='foo')
app2 = web.Application()
app2.router.add_get('/bar', bar_view, name='bar')
# This is the wishful thinking code:
web.configure_app(app1, host='0.0.0.0', port=10000)
web.configure_app(app2, host='0.0.0.0', port=10001)
web.run_apps()

My use case is that I have an existing python web framework that does this kind of thing, and I'm building a prototype that's analogous in python 3.6 with aiohttp.

I understand that multiple python servers can run behind e.g. nginx (see also http://aiohttp.readthedocs.io/en/stable/deployment.html); that's not what I'm after. I want to explore the possibility of two aiohttp web servers with the same asyncio event loop, running in the same python process, serving on two different ports.

3 Answers

Looks like version 3.0 adds a better way to do this that wasn't available previously: https://aiohttp.readthedocs.io/en/stable/web_advanced.html#aiohttp-web-app-runners

EDIT

The documentation is (as pointed out), a bit unclear (I had to fight with it myself, too). To run multiple servers on multiple ports, just repeat the code from the documentation for each site. In short, you will need to create an Application, AppRunner (and call it's setup()), and a TCPSite (and call it's start()) for each separate Application/Server you want to run on a separate address/port.

Easiest way to do this is to create an async function for the repetitive site setup, and then can pass it your Application instance and a port. I also included the mentioned cleanup of the runners for when the loop exits.

Hope this helps.

import asyncio
from aiohttp import web

runners = []

async def start_site(app, address='localhost', port=8080):
    runner = web.AppRunner(app)
    runners.append(runner)
    await runner.setup()
    site = web.TCPSite(runner, address, port)
    await site.start()

loop = asyncio.get_event_loop()

loop.create_task(start_site(web.Application()))
loop.create_task(start_site(web.Application(), port=8081))
loop.create_task(start_site(web.Application(), port=8082))

try:
    loop.run_forever()
except:
    pass
finally:
    for runner in runners:
        loop.run_until_complete(runner.cleanup())

Though the above answer has been accepted, here is an another approach:

Create test.py:

from aiohttp import web
import asyncio
import sys

@asyncio.coroutine
def status1(request):
    return web.json_response('App1 OK')

@asyncio.coroutine
def status2(request):
    return web.json_response('App2 OK')

def start():
    try:
        loop = asyncio.get_event_loop()

        # App1
        app1 = web.Application()
        app1.router.add_get('/status', status1)
        handler1 = app1.make_handler()
        coroutine1 = loop.create_server(handler1, '0.0.0.0', 8081)
        server1 = loop.run_until_complete(coroutine1)
        address1, port1 = server1.sockets[0].getsockname()
        print('App1 started on http://{}:{}'.format(address1, port1))

        # App2
        app2 = web.Application()
        app2.router.add_get('/status', status2)
        handler2 = app2.make_handler()
        coroutine2 = loop.create_server(handler2, '0.0.0.0', 8082)
        server2 = loop.run_until_complete(coroutine2)
        address2, port2 = server2.sockets[0].getsockname()
        print('App2 started on http://{}:{}'.format(address2, port2))

        try:
            loop.run_forever()
        except KeyboardInterrupt:
            pass
        finally:
            server1.close()
            loop.run_until_complete(app1.shutdown())
            loop.run_until_complete(handler1.shutdown(60.0))
            loop.run_until_complete(handler1.finish_connections(1.0))
            loop.run_until_complete(app1.cleanup())

            server2.close()
            loop.run_until_complete(app2.shutdown())
            loop.run_until_complete(handler2.shutdown(60.0))
            loop.run_until_complete(handler2.finish_connections(1.0))
            loop.run_until_complete(app2.cleanup())

        loop.close()
    except Exception as e:
        sys.stderr.write('Error: ' + format(str(e)) + "\n")
        sys.exit(1)

if __name__ == '__main__':
    start()

At terminal, open two tabs. In one tab, run

python test.py

In other tab, run

curl -X GET http://localhost:8081/status
curl -X GET http://localhost:8082/status

You will get response

"App1 OK"
"App2 OK"
Related