Skip to content

Commit

Permalink
Merge pull request #734 from KeepSafe/run_app
Browse files Browse the repository at this point in the history
Implement web.run_app utility function
  • Loading branch information
asvetlov committed Jan 13, 2016
2 parents d8f6a14 + 0733de2 commit 90f1616
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 20 deletions.
34 changes: 34 additions & 0 deletions aiohttp/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,37 @@ def __call__(self):

def __repr__(self):
return "<Application>"


def run_app(app, *, host='0.0.0.0', port=None,
shutdown_timeout=60.0, ssl_context=None):
"""Run an app locally"""
if port is None:
if not ssl_context:
port = 8080
else:
port = 8443

loop = app.loop

handler = app.make_handler()
srv = loop.run_until_complete(loop.create_server(handler, host, port,
ssl=ssl_context))

scheme = 'https' if ssl_context else 'http'
prompt = '127.0.0.1' if host == '0.0.0.0' else host
print("======== Running on {scheme}://{prompt}:{port}/ ========\n"
"(Press CTRL+C to quit)".format(
scheme=scheme, prompt=prompt, port=port))

try:
loop.run_forever()
except KeyboardInterrupt: # pragma: no branch
pass
finally:
srv.close()
loop.run_until_complete(srv.wait_closed())
loop.run_until_complete(app.shutdown())
loop.run_until_complete(handler.finish_connections(shutdown_timeout))
loop.run_until_complete(app.finish())
loop.close()
41 changes: 22 additions & 19 deletions docs/web.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,16 @@ particular *HTTP method* and *path*::
app = web.Application()
app.router.add_route('GET', '/', hello)

After that, create a server and run the *asyncio loop* as usual::
After that, run the application by :func:`run_app` call::

loop = asyncio.get_event_loop()
handler = app.make_handler()
f = loop.create_server(handler, '0.0.0.0', 8080)
srv = loop.run_until_complete(f)
print('serving on', srv.sockets[0].getsockname())
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
srv.close()
loop.run_until_complete(srv.wait_closed())
loop.run_until_complete(app.on_shutdown.send())
loop.run_until_complete(handler.finish_connections(1.0))
loop.run_until_complete(app.finish())
loop.close()
run_app(app)

That's it. Now, head over to ``http://localhost:8080/`` to see the results.

.. seealso:: :ref:`aiohttp-web-graceful-shutdown` section
explains what :func:`run_app` does and how implement
complex server initialization/finalization from scratch.


.. _aiohttp-web-handler:

Expand Down Expand Up @@ -834,9 +823,22 @@ Signal handler may looks like:
app.on_shutdown.append(on_shutdown)
Proper finalization procedure has three steps:

Server finalizer should raise shutdown signal by
:meth:`Application.shutdown` call::
1. Stop accepting new client connections by
:meth:`asyncio.Server.close` and
:meth:`asyncio.Server.wait_closed` calls.

2. Fire :meth:`Application.shutdown` event.

3. Close accepted connections from clients by
:meth:`RequestHandlerFactory.finish_connections` call with
reasonable small delay.

4. Call registered application finalizers by :meth:`Application.finish`.

The following code snippet performs proper application start, run and
finalizing. It's pretty close to :func:`run_app` utility function::

loop = asyncio.get_event_loop()
handler = app.make_handler()
Expand All @@ -857,6 +859,7 @@ Server finalizer should raise shutdown signal by




CORS support
------------

Expand Down
35 changes: 35 additions & 0 deletions docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,41 @@ Utilities
.. seealso:: :ref:`aiohttp-web-file-upload`


.. function:: run_app(app, *, host='0.0.0.0', port=None, loop=None, \
shutdown_timeout=60.0, ssl_context=None)

An utility function for running an application, serving it until
keyboard interrupt and performing a
:ref:`aiohttp-web-graceful-shutdown`.

Suitable as handy tool for scaffolding aiohttp based projects.
Perhaps production config will use more sophisticated runner but it
good enough at least at very beginning stage.

The function uses *app.loop* as event loop to run.

:param app: :class:`Application` instance to run

:param str host: host for HTTP server, ``'0.0.0.0'`` by default

:param int port: port for HTTP server. By default is ``8080`` for
plain text HTTP and ``8443`` for HTTP via SSL
(when *ssl_context* parameter is specified).

:param int shutdown_timeout: a delay to wait for graceful server
shutdown before disconnecting all
open client sockets hard way.

A system with properly
:ref:`aiohttp-web-graceful-shutdown`
implemented never waits for this
timeout but closes a server in a few
milliseconds.

:param ssl_context: :class:`ssl.SSLContext` for HTTPS server,
``None`` for HTTP connection.


Constants
---------

Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def loop(request):
yield loop

if not loop._closed:
loop.stop()
loop.call_soon(loop.stop)
loop.run_forever()
loop.close()
gc.collect()
Expand Down
46 changes: 46 additions & 0 deletions tests/test_run_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import asyncio
import ssl

from unittest import mock
from aiohttp import web


def test_run_app_http(loop):
loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop)
loop.call_later(0.01, loop.stop)

app = web.Application(loop=loop)

web.run_app(app)

loop.close.assert_called_with()
loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8080, ssl=None)


def test_run_app_https(loop):
loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop)
loop.call_later(0.01, loop.stop)

app = web.Application(loop=loop)

ssl_context = ssl.create_default_context()

web.run_app(app, ssl_context=ssl_context)

loop.close.assert_called_with()
loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8443,
ssl=ssl_context)


def test_run_app_nondefault_host_port(loop, unused_port):
port = unused_port()
host = 'localhost'

loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop)
loop.call_later(0.01, loop.stop)

app = web.Application(loop=loop)

web.run_app(app, host=host, port=port)

loop.create_server.assert_called_with(mock.ANY, host, port, ssl=None)

0 comments on commit 90f1616

Please sign in to comment.