Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement web.run_app utility function #734

Merged
merged 20 commits into from
Jan 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should someone bind to public interface?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, read from end to top))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use :: for IPv6 (:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't follow.
Isn't knowledge of host/port info enough?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I already said -- I read diff from bottom to top) thought that you replaced '0.0.0.0' with localhost and bind to it)

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)