Add sprockets.http.run.

This commit is contained in:
Dave Shawley 2015-07-22 14:04:26 -04:00
parent cff4e03eb7
commit f9645b0025
7 changed files with 224 additions and 1 deletions

View file

@ -1,4 +1,4 @@
Copyright (c) 2014 AWeber Communications
Copyright (c) 2015 AWeber Communications
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,

9
docs/api.rst Normal file
View file

@ -0,0 +1,9 @@
API Documentation
=================
.. automodule:: sprockets.http
:members:
Internal Interfaces
-------------------
.. automodule:: sprockets.http.runner
:members:

View file

@ -5,3 +5,4 @@ Release History
Next Release
------------
- Add :func:`sprockets.http.run`

View file

@ -1,7 +1,12 @@
.. include:: ../README.rst
License
-------
.. include:: ../LICENSE
.. toctree::
:hidden:
api
contributing
history

View file

@ -0,0 +1,2 @@
sprockets.logging>=1.2.1,<2
tornado>=3.1,<5

View file

@ -1,2 +1,50 @@
import os
version_info = (0, 0, 0)
__version__ = '.'.join(str(v) for v in version_info)
def run(create_application, settings=None):
"""
Run a Tornado create_application.
:param create_application: function to call to create a new
application instance
:param dict|None settings: optional configuration dictionary
that will be passed through to ``create_application``
as kwargs.
.. rubric:: settings['debug']
If the `settings` parameter includes a value for the ``debug``
key, then the application will be run in Tornado debug mode.
This setting also changes how the logging layer is configured.
When running in "debug" mode, logs are written to standard out
using a human-readable format instead of the standard JSON
payload.
If the `settings` parameter does not include a ``debug`` key,
then debug mode will be enabled based on the :envvar:`DEBUG`
environment variable.
.. rubric:: settings['port']
If the `settings` parameter includes a value for the ``port``
key, then the application will be configured to listen on the
specified port. If this key is not present, then the :envvar:`PORT`
environment variable determines which port to bind to. The
default port is 8000 if nothing overrides it.
"""
from . import runner
app_settings = {} if settings is None else settings.copy()
debug_mode = bool(app_settings.get('debug',
int(os.environ.get('DEBUG', 0)) != 0))
app_settings['debug'] = debug_mode
runner._configure_logging(debug_mode)
port_number = int(app_settings.pop('port', os.environ.get('PORT', 8000)))
server = runner.Runner(create_application(**app_settings))
server.run(port_number)

158
sprockets/http/runner.py Normal file
View file

@ -0,0 +1,158 @@
"""
Run a Tornado HTTP service.
- :func:`.run`: calls a ``make_app`` *callable*, configures the
environment intelligently, and runs the application.
- :class:`.Runner`: encapsulates the running of the application
"""
import logging.config
import signal
from tornado import httpserver, ioloop
class Runner(object):
"""
HTTP service runner.
:param tornado.web.Application application: the application to serve
This class implements the logic necessary to safely run a
Tornado HTTP service inside of a docker container.
.. rubric:: Usage Example
.. code-block:: python
def make_app():
return web.Application(...)
def run():
server = runner.Runner(make_app())
server.start_server()
ioloop.IOLoop.instance().start()
The :meth:`.start_server` method sets up the necessary signal handling
to ensure that we have a clean shutdown in the face of signals.
"""
def __init__(self, application):
self.application = application
self.logger = logging.getLogger('Runner')
self.server = None
self.shutdown_limit = 5
def start_server(self, port_number):
"""
Create a HTTP server and start it.
:param int port_number: the port number to bind the server to
If the application's ``debug`` setting is ``True``, then we are
going to run in a single-process mode; otherwise, we'll let
tornado decide how many sub-processes to spawn.
"""
signal.signal(signal.SIGTERM, self._on_signal)
signal.signal(signal.SIGINT, self._on_signal)
self.server = httpserver.HTTPServer(self.application)
if self.application.settings.get('debug', False):
self.logger.info('starting 1 process on port %d', port_number)
self.server.listen(port_number)
else:
self.logger.info('starting processes on port %d', port_number)
self.server.bind(port_number)
self.server.start(0)
def run(self, port_number):
"""
Create the server and run the IOLoop.
:param int port_number: the port number to bind the server to
If the application's ``debug`` setting is ``True``, then we are
going to run in a single-process mode; otherwise, we'll let
tornado decide how many sub-processes to spawn.
"""
self.start_server(port_number)
ioloop.IOLoop.instance().start()
def _on_signal(self, signo, frame):
self.logger.info('signal %s received, stopping', signo)
ioloop.IOLoop.instance().add_callback_from_signal(self._shutdown)
def _shutdown(self):
self.server.stop()
iol = ioloop.IOLoop.instance()
deadline = iol.time() + self.shutdown_limit
def maybe_stop():
now = iol.time()
if now < deadline and (iol._callbacks or iol._timeouts):
return iol.add_timeout(now + 1, maybe_stop)
iol.stop()
self.logger.info('stopped')
self.logger.info('stopping within %s seconds', self.shutdown_limit)
maybe_stop()
def _configure_logging(debug):
"""
Configure the ``logging`` package appropriately.
:param bool debug: are we running in debug mode?
"""
if debug:
log_config = {
'version': 1,
'disable_existing_loggers': False,
'incremental': False,
'formatters': {
'debug': {
'format': ('[%(asctime)s] %(levelname)-8s %(process)-6s '
'%(name)s: %(message)s.')
},
},
'handlers': {
'debug-console': {
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout',
'level': 'DEBUG',
'formatter': 'debug',
},
},
'root': {
'level': 'DEBUG',
'handlers': ['debug-console'],
}
}
else:
log_config = {
'version': 1,
'disable_existing_loggers': False,
'incremental': False,
'formatters': {
'json': {
'()': 'sprockets.logging.JSONRequestFormatter',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'json',
},
},
'root': {
'level': 'INFO',
'handlers': ['console'],
}
}
logging.config.dictConfig(log_config)