sanic/sanic/app.py

904 lines
35 KiB
Python
Raw Normal View History

2017-12-07 13:00:54 +00:00
import os
import logging
import logging.config
2017-02-09 01:37:32 +00:00
import re
import warnings
from asyncio import get_event_loop, ensure_future, CancelledError
from collections import deque, defaultdict
from functools import partial
2018-01-22 06:12:41 +00:00
from inspect import getmodulename, isawaitable, signature, stack
2016-10-15 20:59:00 +01:00
from traceback import format_exc
2017-02-02 17:21:14 +00:00
from urllib.parse import urlencode, urlunparse
2017-04-08 21:31:11 +01:00
from ssl import create_default_context, Purpose
2016-10-15 20:59:00 +01:00
2017-09-10 19:11:16 +01:00
from sanic.config import Config
2017-02-16 02:54:00 +00:00
from sanic.constants import HTTP_METHODS
from sanic.exceptions import ServerError, URLBuildError, SanicException
2017-02-16 02:54:00 +00:00
from sanic.handlers import ErrorHandler
2017-09-13 07:42:42 +01:00
from sanic.log import logger, error_logger, LOGGING_CONFIG_DEFAULTS
2017-02-21 16:28:45 +00:00
from sanic.response import HTTPResponse, StreamingHTTPResponse
2017-02-16 02:54:00 +00:00
from sanic.router import Router
from sanic.server import serve, serve_multiple, HttpProtocol, Signal
2017-02-16 02:54:00 +00:00
from sanic.static import register as static_register
2017-03-08 00:39:26 +00:00
from sanic.testing import SanicTestClient
2017-02-16 02:54:00 +00:00
from sanic.views import CompositionView
2017-02-20 22:32:14 +00:00
from sanic.websocket import WebSocketProtocol, ConnectionClosed
2017-12-07 13:00:54 +00:00
import sanic.reloader_helpers as reloader_helpers
2016-10-15 20:59:00 +01:00
class Sanic:
def __init__(self, name=None, router=None, error_handler=None,
load_env=True, request_class=None,
strict_slashes=False, log_config=None,
configure_logging=True):
# Get name from previous stack frame
2016-11-19 01:06:16 +00:00
if name is None:
frame_records = stack()[1]
name = getmodulename(frame_records[1])
2017-09-13 07:42:42 +01:00
# logging
if configure_logging:
logging.config.dictConfig(log_config or LOGGING_CONFIG_DEFAULTS)
2017-09-13 07:42:42 +01:00
2016-10-15 20:59:00 +01:00
self.name = name
self.router = router or Router()
self.request_class = request_class
self.error_handler = error_handler or ErrorHandler()
self.config = Config(load_env=load_env)
self.request_middleware = deque()
self.response_middleware = deque()
self.blueprints = {}
self._blueprint_order = []
self.configure_logging = configure_logging
self.debug = None
2017-01-07 23:46:43 +00:00
self.sock = None
self.strict_slashes = strict_slashes
self.listeners = defaultdict(list)
self.is_running = False
2017-05-05 12:09:32 +01:00
self.is_request_stream = False
2017-02-20 22:32:14 +00:00
self.websocket_enabled = False
2017-10-11 12:02:26 +01:00
self.websocket_tasks = set()
2016-10-15 20:59:00 +01:00
# Register alternative method names
self.go_fast = self.run
2017-02-12 00:27:38 +00:00
@property
def loop(self):
"""Synonymous with asyncio.get_event_loop().
Only supported when using the `app.run` method.
"""
if not self.is_running:
raise SanicException(
'Loop can only be retrieved after the app has started '
'running. Not supported with `create_server` function')
2017-02-12 00:27:38 +00:00
return get_event_loop()
2016-10-15 20:59:00 +01:00
# -------------------------------------------------------------------- #
# Registration
# -------------------------------------------------------------------- #
2017-02-12 20:07:59 +00:00
def add_task(self, task):
"""Schedule a task to run later, after the loop has started.
2017-02-11 22:52:40 +00:00
Different from asyncio.ensure_future in that it does not
also return a future, and the actual ensure_future call
is delayed until before server start.
:param task: future, couroutine or awaitable
2017-02-11 22:52:40 +00:00
"""
2017-12-22 07:27:34 +00:00
try:
2017-02-11 22:52:40 +00:00
if callable(task):
try:
self.loop.create_task(task(self))
except TypeError:
self.loop.create_task(task())
2017-02-11 22:52:40 +00:00
else:
2017-12-22 07:27:34 +00:00
self.loop.create_task(task)
except SanicException:
@self.listener('before_server_start')
def run(app, loop):
if callable(task):
try:
loop.create_task(task(self))
except TypeError:
loop.create_task(task())
2017-12-22 07:27:34 +00:00
else:
loop.create_task(task)
2017-02-11 22:52:40 +00:00
# Decorator
def listener(self, event):
"""Create a listener from a decorated function.
:param event: event to listen to
"""
2018-01-22 06:12:41 +00:00
def decorator(listener):
self.listeners[event].append(listener)
return listener
2018-01-22 06:12:41 +00:00
return decorator
2017-02-11 22:52:40 +00:00
def register_listener(self, listener, event):
"""
Register the listener for a given event.
Args:
listener: callable i.e. setup_db(app, loop)
event: when to register listener i.e. 'before_server_start'
Returns: listener
"""
return self.listener(event)(listener)
2016-10-15 20:59:00 +01:00
# Decorator
2017-03-16 04:11:45 +00:00
def route(self, uri, methods=frozenset({'GET'}), host=None,
2017-08-21 11:05:34 +01:00
strict_slashes=None, stream=False, version=None, name=None):
"""Decorate a function to be registered as a route
2016-10-15 20:59:00 +01:00
:param uri: path of the URL
:param methods: list or tuple of methods allowed
:param host:
2017-05-05 12:09:32 +01:00
:param strict_slashes:
:param stream:
2017-08-21 11:05:34 +01:00
:param version:
:param name: user defined route name for url_for
2016-10-15 20:59:00 +01:00
:return: decorated function
"""
# Fix case where the user did not prefix the URL with a /
# and will probably get confused as to why it's not working
if not uri.startswith('/'):
uri = '/' + uri
2017-05-05 12:09:32 +01:00
if stream:
self.is_request_stream = True
if strict_slashes is None:
strict_slashes = self.strict_slashes
2016-10-15 20:59:00 +01:00
def response(handler):
2018-01-22 06:12:41 +00:00
args = [key for key in signature(handler).parameters.keys()]
if args:
if stream:
handler.is_stream = stream
self.router.add(uri=uri, methods=methods, handler=handler,
host=host, strict_slashes=strict_slashes,
version=version, name=name)
return handler
else:
2018-01-22 06:52:30 +00:00
raise ValueError(
2018-01-24 00:17:55 +00:00
'Required parameter `request` missing'
'in the {0}() route?'.format(
2018-01-22 06:52:30 +00:00
handler.__name__))
2016-10-15 20:59:00 +01:00
return response
2017-01-20 07:47:07 +00:00
# Shorthand method decorators
2017-08-21 11:05:34 +01:00
def get(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-16 04:11:45 +00:00
return self.route(uri, methods=frozenset({"GET"}), host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)
2017-01-20 07:47:07 +00:00
def post(self, uri, host=None, strict_slashes=None, stream=False,
2017-08-21 11:05:34 +01:00
version=None, name=None):
2017-03-16 04:11:45 +00:00
return self.route(uri, methods=frozenset({"POST"}), host=host,
2017-07-13 04:18:56 +01:00
strict_slashes=strict_slashes, stream=stream,
2017-08-21 11:05:34 +01:00
version=version, name=name)
2017-01-20 07:47:07 +00:00
def put(self, uri, host=None, strict_slashes=None, stream=False,
2017-08-21 11:05:34 +01:00
version=None, name=None):
2017-03-16 04:11:45 +00:00
return self.route(uri, methods=frozenset({"PUT"}), host=host,
2017-07-13 04:18:56 +01:00
strict_slashes=strict_slashes, stream=stream,
2017-08-21 11:05:34 +01:00
version=version, name=name)
2017-01-20 07:47:07 +00:00
2017-08-21 11:05:34 +01:00
def head(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-16 04:11:45 +00:00
return self.route(uri, methods=frozenset({"HEAD"}), host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)
2017-01-20 07:47:07 +00:00
2017-08-21 11:05:34 +01:00
def options(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-16 04:11:45 +00:00
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)
2017-01-20 07:47:07 +00:00
def patch(self, uri, host=None, strict_slashes=None, stream=False,
2017-08-21 11:05:34 +01:00
version=None, name=None):
2017-03-16 04:11:45 +00:00
return self.route(uri, methods=frozenset({"PATCH"}), host=host,
2017-07-13 04:18:56 +01:00
strict_slashes=strict_slashes, stream=stream,
2017-08-21 11:05:34 +01:00
version=version, name=name)
2017-01-20 07:47:07 +00:00
2017-08-21 11:05:34 +01:00
def delete(self, uri, host=None, strict_slashes=None, version=None,
name=None):
2017-03-16 04:11:45 +00:00
return self.route(uri, methods=frozenset({"DELETE"}), host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, version=version,
name=name)
2017-03-16 04:11:45 +00:00
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
2018-03-12 19:21:59 +00:00
strict_slashes=None, version=None, name=None, stream=False):
"""A helper method to register class instance or
2016-11-25 07:29:25 +00:00
functions as a handler to the application url
routes.
:param handler: function or class instance
:param uri: path of the URL
:param methods: list or tuple of methods allowed, these are overridden
if using a HTTPMethodView
:param host:
2017-08-21 11:05:34 +01:00
:param strict_slashes:
:param version:
:param name: user defined route name for url_for
2018-03-12 19:21:59 +00:00
:param stream: boolean specifying if the handler is a stream handler
:return: function or class instance
"""
# Handle HTTPMethodView differently
if hasattr(handler, 'view_class'):
2017-02-02 21:24:16 +00:00
methods = set()
for method in HTTP_METHODS:
_handler = getattr(handler.view_class, method.lower(), None)
if _handler:
2017-02-02 21:24:16 +00:00
methods.add(method)
if hasattr(_handler, 'is_stream'):
stream = True
2017-02-02 21:24:16 +00:00
# handle composition view differently
if isinstance(handler, CompositionView):
methods = handler.handlers.keys()
for _handler in handler.handlers.values():
if hasattr(_handler, 'is_stream'):
stream = True
break
2017-02-02 21:24:16 +00:00
if strict_slashes is None:
strict_slashes = self.strict_slashes
2017-03-16 04:11:45 +00:00
self.route(uri=uri, methods=methods, host=host,
2017-07-13 04:18:56 +01:00
strict_slashes=strict_slashes, stream=stream,
2017-08-21 11:05:34 +01:00
version=version, name=name)(handler)
return handler
# Decorator
def websocket(self, uri, host=None, strict_slashes=None,
2017-08-21 11:05:34 +01:00
subprotocols=None, name=None):
"""Decorate a function to be registered as a websocket route
:param uri: path of the URL
:param subprotocols: optional list of strings with the supported
subprotocols
:param host:
:return: decorated function
"""
self.enable_websocket()
# Fix case where the user did not prefix the URL with a /
# and will probably get confused as to why it's not working
if not uri.startswith('/'):
uri = '/' + uri
if strict_slashes is None:
strict_slashes = self.strict_slashes
def response(handler):
async def websocket_handler(request, *args, **kwargs):
2017-02-28 06:33:18 +00:00
request.app = self
try:
protocol = request.transport.get_protocol()
except AttributeError:
# On Python3.5 the Transport classes in asyncio do not
# have a get_protocol() method as in uvloop
protocol = request.transport._protocol
ws = await protocol.websocket_handshake(request, subprotocols)
# schedule the application handler
# its future is kept in self.websocket_tasks in case it
# needs to be cancelled due to the server being stopped
fut = ensure_future(handler(request, ws, *args, **kwargs))
2017-10-11 12:02:26 +01:00
self.websocket_tasks.add(fut)
try:
await fut
except (CancelledError, ConnectionClosed):
pass
finally:
self.websocket_tasks.remove(fut)
await ws.close()
self.router.add(uri=uri, handler=websocket_handler,
2017-03-16 04:11:45 +00:00
methods=frozenset({'GET'}), host=host,
2017-08-21 11:05:34 +01:00
strict_slashes=strict_slashes, name=name)
return handler
return response
2017-03-16 04:11:45 +00:00
def add_websocket_route(self, handler, uri, host=None,
strict_slashes=None, subprotocols=None, name=None):
2017-02-20 22:32:14 +00:00
"""A helper method to register a function as a websocket route."""
if strict_slashes is None:
strict_slashes = self.strict_slashes
2017-08-21 11:05:34 +01:00
return self.websocket(uri, host=host, strict_slashes=strict_slashes,
subprotocols=subprotocols, name=name)(handler)
2017-02-20 22:32:14 +00:00
def enable_websocket(self, enable=True):
"""Enable or disable the support for websocket.
Websocket is enabled automatically if websocket routes are
added to the application.
"""
if not self.websocket_enabled:
# if the server is stopped, we want to cancel any ongoing
# websocket tasks, to allow the server to exit promptly
@self.listener('before_server_stop')
def cancel_websocket_tasks(app, loop):
for task in self.websocket_tasks:
task.cancel()
2017-02-20 22:32:14 +00:00
self.websocket_enabled = enable
2017-01-08 23:48:12 +00:00
def remove_route(self, uri, clean_cache=True, host=None):
self.router.remove(uri, clean_cache, host)
2016-10-15 20:59:00 +01:00
# Decorator
def exception(self, *exceptions):
"""Decorate a function to be registered as a handler for exceptions
:param exceptions: exceptions
2016-10-15 20:59:00 +01:00
:return: decorated function
"""
def response(handler):
for exception in exceptions:
2017-03-08 00:22:23 +00:00
if isinstance(exception, (tuple, list)):
for e in exception:
self.error_handler.add(e, handler)
else:
self.error_handler.add(exception, handler)
2016-10-15 20:59:00 +01:00
return handler
return response
def register_middleware(self, middleware, attach_to='request'):
if attach_to == 'request':
self.request_middleware.append(middleware)
if attach_to == 'response':
self.response_middleware.appendleft(middleware)
return middleware
2016-10-15 20:59:00 +01:00
# Decorator
2017-02-11 12:27:25 +00:00
def middleware(self, middleware_or_request):
"""Decorate and register middleware to be called before a request.
Can either be called as @app.middleware or @app.middleware('request')
2016-10-15 20:59:00 +01:00
"""
# Detect which way this was called, @middleware or @middleware('AT')
2017-02-11 12:27:25 +00:00
if callable(middleware_or_request):
return self.register_middleware(middleware_or_request)
2017-02-11 12:27:25 +00:00
2016-10-15 20:59:00 +01:00
else:
return partial(self.register_middleware,
2017-02-11 12:39:04 +00:00
attach_to=middleware_or_request)
2016-10-15 20:59:00 +01:00
# Static Files
2017-04-13 05:11:38 +01:00
def static(self, uri, file_or_directory, pattern=r'/?.+',
use_modified_since=True, use_content_range=False,
stream_large_files=False, name='static', host=None,
strict_slashes=None, content_type=None):
"""Register a root to serve files from. The input can either be a
file or a directory. See
"""
2016-10-25 10:45:28 +01:00
static_register(self, uri, file_or_directory, pattern,
use_modified_since, use_content_range,
stream_large_files, name, host, strict_slashes,
content_type)
def blueprint(self, blueprint, **options):
"""Register a blueprint on the application.
2018-01-19 01:20:51 +00:00
:param blueprint: Blueprint object or (list, tuple) thereof
:param options: option dictionary with blueprint defaults
:return: Nothing
"""
2018-01-19 01:20:51 +00:00
if isinstance(blueprint, (list, tuple)):
for item in blueprint:
self.blueprint(item, **options)
return
if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint with the name "%s" is already registered. ' \
'Blueprint names must be unique.' % \
(blueprint.name,)
else:
self.blueprints[blueprint.name] = blueprint
self._blueprint_order.append(blueprint)
blueprint.register(self, options)
def register_blueprint(self, *args, **kwargs):
# TODO: deprecate 1.0
2017-01-28 01:59:08 +00:00
if self.debug:
warnings.simplefilter('default')
warnings.warn("Use of register_blueprint will be deprecated in "
"version 1.0. Please use the blueprint method"
" instead",
DeprecationWarning)
return self.blueprint(*args, **kwargs)
2017-02-02 17:21:14 +00:00
def url_for(self, view_name: str, **kwargs):
"""Build a URL based on a view name and the values provided.
2017-02-02 17:52:48 +00:00
In order to build a URL, all request parameters must be supplied as
keyword arguments, and each parameter must pass the test for the
specified parameter type. If these conditions are not met, a
`URLBuildError` will be thrown.
Keyword arguments that are not request parameters will be included in
the output URL's query string.
:param view_name: string referencing the view name
2017-03-16 05:52:18 +00:00
:param \*\*kwargs: keys and values that are used to build request
2017-02-02 17:52:48 +00:00
parameters and query string arguments.
:return: the built URL
Raises:
URLBuildError
"""
# find the route by the supplied view name
kw = {}
# special static files url_for
if view_name == 'static':
kw.update(name=kwargs.pop('name', 'static'))
elif view_name.endswith('.static'): # blueprint.static
kwargs.pop('name', None)
kw.update(name=view_name)
uri, route = self.router.find_route_by_view_name(view_name, **kw)
if not (uri and route):
2017-08-21 11:05:34 +01:00
raise URLBuildError('Endpoint with name `{}` was not found'.format(
2018-01-22 06:12:41 +00:00
view_name))
2017-02-02 17:21:14 +00:00
if view_name == 'static' or view_name.endswith('.static'):
filename = kwargs.pop('filename', None)
# it's static folder
if '<file_uri:' in uri:
folder_ = uri.split('<file_uri:', 1)[0]
if folder_.endswith('/'):
folder_ = folder_[:-1]
if filename.startswith('/'):
filename = filename[1:]
uri = '{}/{}'.format(folder_, filename)
2017-02-23 16:45:50 +00:00
if uri != '/' and uri.endswith('/'):
2017-02-20 23:58:27 +00:00
uri = uri[:-1]
2017-02-02 17:21:14 +00:00
out = uri
2017-02-02 17:52:48 +00:00
# find all the parameters we will need to build in the URL
2017-02-02 17:21:14 +00:00
matched_params = re.findall(
self.router.parameter_pattern, uri)
# _method is only a placeholder now, don't know how to support it
kwargs.pop('_method', None)
anchor = kwargs.pop('_anchor', '')
# _external need SERVER_NAME in config or pass _server arg
external = kwargs.pop('_external', False)
scheme = kwargs.pop('_scheme', '')
if scheme and not external:
raise ValueError('When specifying _scheme, _external must be True')
netloc = kwargs.pop('_server', None)
if netloc is None and external:
netloc = self.config.get('SERVER_NAME', '')
if external:
if not scheme:
2017-09-27 02:59:49 +01:00
if ':' in netloc[:8]:
scheme = netloc[:8].split(':', 1)[0]
else:
scheme = 'http'
if '://' in netloc[:8]:
netloc = netloc.split('://', 1)[-1]
2017-02-02 17:21:14 +00:00
for match in matched_params:
name, _type, pattern = self.router.parse_parameter_string(
match)
2017-02-02 17:52:48 +00:00
# we only want to match against each individual parameter
2017-02-02 17:21:14 +00:00
specific_pattern = '^{}$'.format(pattern)
supplied_param = None
2017-02-02 17:52:48 +00:00
if name in kwargs:
2017-02-02 17:21:14 +00:00
supplied_param = kwargs.get(name)
del kwargs[name]
else:
raise URLBuildError(
'Required parameter `{}` was not passed to url_for'.format(
name))
supplied_param = str(supplied_param)
2017-02-02 17:52:48 +00:00
# determine if the parameter supplied by the caller passes the test
# in the URL
2017-02-02 17:21:14 +00:00
passes_pattern = re.match(specific_pattern, supplied_param)
if not passes_pattern:
if _type != str:
msg = (
'Value "{}" for parameter `{}` does not '
'match pattern for type `{}`: {}'.format(
supplied_param, name, _type.__name__, pattern))
else:
msg = (
'Value "{}" for parameter `{}` '
'does not satisfy pattern {}'.format(
supplied_param, name, pattern))
raise URLBuildError(msg)
2017-02-02 17:52:48 +00:00
# replace the parameter in the URL with the supplied value
2017-02-02 17:21:14 +00:00
replacement_regex = '(<{}.*?>)'.format(name)
out = re.sub(
replacement_regex, supplied_param, out)
# parse the remainder of the keyword arguments into a querystring
query_string = urlencode(kwargs, doseq=True) if kwargs else ''
# scheme://netloc/path;parameters?query#fragment
out = urlunparse((scheme, netloc, out, '', query_string, anchor))
2017-02-02 17:21:14 +00:00
return out
2016-10-15 20:59:00 +01:00
# -------------------------------------------------------------------- #
# Request Handling
# -------------------------------------------------------------------- #
def converted_response_type(self, response):
pass
2017-02-21 16:28:45 +00:00
async def handle_request(self, request, write_callback, stream_callback):
"""Take a request from the HTTP Server and return a response object
to be sent back The HTTP Server only expects a response object, so
2016-10-16 14:01:59 +01:00
exception handling must be done here
2016-10-15 20:59:00 +01:00
:param request: HTTP Request object
2017-02-21 16:28:45 +00:00
:param write_callback: Synchronous response function to be
called with the response as the only argument
:param stream_callback: Coroutine that handles streaming a
StreamingHTTPResponse if produced by the handler.
2016-10-15 20:59:00 +01:00
:return: Nothing
"""
# Define `response` var here to remove warnings about
# allocation before assignment below.
response = None
cancelled = False
2016-10-15 20:59:00 +01:00
try:
# -------------------------------------------- #
# Request Middleware
# -------------------------------------------- #
2017-04-12 10:52:01 +01:00
request.app = self
response = await self._run_request_middleware(request)
2016-10-15 20:59:00 +01:00
# No middleware results
if not response:
# -------------------------------------------- #
# Execute Handler
# -------------------------------------------- #
2016-10-15 20:59:00 +01:00
# Fetch handler from router
2017-04-28 20:06:59 +01:00
handler, args, kwargs, uri = self.router.get(request)
2017-04-28 20:06:59 +01:00
request.uri_template = uri
2016-10-15 20:59:00 +01:00
if handler is None:
2016-10-16 14:01:59 +01:00
raise ServerError(
("'None' was returned while requesting a "
"handler from the router"))
2016-10-15 20:59:00 +01:00
# Run response handler
response = handler(request, *args, **kwargs)
if isawaitable(response):
response = await response
except CancelledError:
# If response handler times out, the server handles the error
# and cancels the handle_request job.
# In this case, the transport is already closed and we cannot
# issue a response.
response = None
cancelled = True
2016-10-15 20:59:00 +01:00
except Exception as e:
# -------------------------------------------- #
# Response Generation Failed
# -------------------------------------------- #
2016-10-15 20:59:00 +01:00
try:
response = self.error_handler.response(request, e)
if isawaitable(response):
response = await response
except Exception as e:
2018-01-05 22:12:22 +00:00
if isinstance(e, SanicException):
response = self.error_handler.default(request=request,
exception=e)
elif self.debug:
2016-10-16 14:01:59 +01:00
response = HTTPResponse(
"Error while handling error: {}\nStack: {}".format(
2018-01-05 22:12:22 +00:00
e, format_exc()), status=500)
2016-10-15 20:59:00 +01:00
else:
2016-10-16 14:01:59 +01:00
response = HTTPResponse(
2018-01-05 22:12:22 +00:00
"An error occurred while handling an error",
status=500)
finally:
# -------------------------------------------- #
# Response Middleware
# -------------------------------------------- #
# Don't run response middleware if response is None
if response is not None:
try:
response = await self._run_response_middleware(request,
response)
except CancelledError:
# Response middleware can timeout too, as above.
response = None
cancelled = True
except BaseException:
error_logger.exception(
'Exception occurred in one of response '
'middleware handlers'
)
if cancelled:
raise CancelledError()
2017-02-21 16:28:45 +00:00
# pass the response to the correct callback
if isinstance(response, StreamingHTTPResponse):
await stream_callback(response)
else:
write_callback(response)
2016-10-15 20:59:00 +01:00
2017-02-14 19:51:20 +00:00
# -------------------------------------------------------------------- #
# Testing
# -------------------------------------------------------------------- #
@property
def test_client(self):
2017-03-08 00:39:26 +00:00
return SanicTestClient(self)
2017-02-14 19:51:20 +00:00
2016-10-15 20:59:00 +01:00
# -------------------------------------------------------------------- #
# Execution
# -------------------------------------------------------------------- #
2017-05-21 11:14:58 +01:00
def run(self, host=None, port=None, debug=False, ssl=None,
sock=None, workers=1, protocol=None,
backlog=100, stop_event=None, register_sys_signals=True,
access_log=True, **kwargs):
"""Run the HTTP Server and listen until keyboard interrupt or term
signal. On termination, drain connections before closing.
2016-10-15 20:59:00 +01:00
:param host: Address to host on
:param port: Port to host on
:param debug: Enables debug output (slows server)
2017-03-08 03:54:02 +00:00
:param ssl: SSLContext, or location of certificate and key
for SSL encryption of worker(s)
2016-10-18 09:22:49 +01:00
:param sock: Socket for the server to accept connections from
:param workers: Number of processes
2017-03-08 03:54:02 +00:00
received before it is respected
:param backlog:
:param stop_event:
:param register_sys_signals:
2016-12-29 04:11:27 +00:00
:param protocol: Subclass of asyncio protocol class
2016-10-15 20:59:00 +01:00
:return: Nothing
"""
# Default auto_reload to false
auto_reload = False
# If debug is set, default it to true (unless on windows)
if debug and os.name == 'posix':
auto_reload = True
# Allow for overriding either of the defaults
auto_reload = kwargs.get("auto_reload", auto_reload)
2017-09-13 07:42:42 +01:00
2017-05-23 04:28:12 +01:00
if sock is None:
host, port = host or "127.0.0.1", port or 8000
if protocol is None:
2017-02-20 22:32:14 +00:00
protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol)
2017-02-27 00:31:39 +00:00
if stop_event is not None:
if debug:
warnings.simplefilter('default')
warnings.warn("stop_event will be removed from future versions.",
DeprecationWarning)
# compatibility old access_log params
self.config.ACCESS_LOG = access_log
2017-01-29 22:01:00 +00:00
server_settings = self._helper(
host=host, port=port, debug=debug, ssl=ssl, sock=sock,
workers=workers, protocol=protocol, backlog=backlog,
register_sys_signals=register_sys_signals, auto_reload=auto_reload)
2016-10-15 20:59:00 +01:00
try:
self.is_running = True
2016-10-18 09:22:49 +01:00
if workers == 1:
if auto_reload and os.name != 'posix':
# This condition must be removed after implementing
# auto reloader for other operating systems.
raise NotImplementedError
if auto_reload and \
os.environ.get('SANIC_SERVER_RUNNING') != 'true':
2017-12-07 13:00:54 +00:00
reloader_helpers.watchdog(2)
else:
serve(**server_settings)
2016-10-18 09:22:49 +01:00
else:
2017-02-27 00:31:39 +00:00
serve_multiple(server_settings, workers)
2017-10-26 05:58:31 +01:00
except BaseException:
2017-09-12 06:12:49 +01:00
error_logger.exception(
2016-11-19 07:16:20 +00:00
'Experienced exception while trying to serve')
raise
finally:
self.is_running = False
2017-09-12 06:12:49 +01:00
logger.info("Server Stopped")
2016-10-18 09:22:49 +01:00
2016-10-15 20:59:00 +01:00
def stop(self):
"""This kills the Sanic"""
2016-10-18 09:22:49 +01:00
get_event_loop().stop()
2017-03-12 08:19:34 +00:00
def __call__(self):
"""gunicorn compatibility"""
return self
2017-05-21 11:14:58 +01:00
async def create_server(self, host=None, port=None, debug=False,
ssl=None, sock=None, protocol=None,
backlog=100, stop_event=None,
2017-09-13 18:35:34 +01:00
access_log=True):
"""Asynchronous version of `run`.
NOTE: This does not support multiprocessing and is not the preferred
way to run a Sanic application.
"""
2017-09-13 07:42:42 +01:00
2017-05-23 04:28:12 +01:00
if sock is None:
host, port = host or "127.0.0.1", port or 8000
if protocol is None:
2017-02-20 22:32:14 +00:00
protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol)
2017-02-27 00:31:39 +00:00
if stop_event is not None:
if debug:
warnings.simplefilter('default')
warnings.warn("stop_event will be removed from future versions.",
DeprecationWarning)
# compatibility old access_log params
self.config.ACCESS_LOG = access_log
2017-01-29 22:01:00 +00:00
server_settings = self._helper(
host=host, port=port, debug=debug, ssl=ssl, sock=sock,
loop=get_event_loop(), protocol=protocol,
backlog=backlog, run_async=True)
# Trigger before_start events
2017-08-09 06:21:40 +01:00
await self.trigger_events(
server_settings.get('before_start', []),
server_settings.get('loop')
)
return await serve(**server_settings)
async def trigger_events(self, events, loop):
"""Trigger events (functions or async)
:param events: one or more sync or async functions to execute
:param loop: event loop
"""
for event in events:
result = event(loop)
if isawaitable(result):
await result
async def _run_request_middleware(self, request):
# The if improves speed. I don't know why
if self.request_middleware:
for middleware in self.request_middleware:
response = middleware(request)
if isawaitable(response):
response = await response
if response:
return response
return None
async def _run_response_middleware(self, request, response):
if self.response_middleware:
for middleware in self.response_middleware:
_response = middleware(request, response)
if isawaitable(_response):
_response = await _response
if _response:
response = _response
break
return response
2017-05-21 11:14:58 +01:00
def _helper(self, host=None, port=None, debug=False,
ssl=None, sock=None, workers=1, loop=None,
protocol=HttpProtocol, backlog=100, stop_event=None,
register_sys_signals=True, run_async=False, auto_reload=False):
"""Helper function used by `run` and `create_server`."""
2017-03-08 03:54:02 +00:00
if isinstance(ssl, dict):
# try common aliaseses
cert = ssl.get('cert') or ssl.get('certificate')
key = ssl.get('key') or ssl.get('keyfile')
2017-04-08 21:31:11 +01:00
if cert is None or key is None:
2017-03-08 03:54:02 +00:00
raise ValueError("SSLContext or certificate and key required.")
2017-04-08 21:31:11 +01:00
context = create_default_context(purpose=Purpose.CLIENT_AUTH)
2017-03-08 03:54:02 +00:00
context.load_cert_chain(cert, keyfile=key)
ssl = context
2017-02-27 00:31:39 +00:00
if stop_event is not None:
if debug:
warnings.simplefilter('default')
warnings.warn("stop_event will be removed from future versions.",
DeprecationWarning)
2017-01-28 01:59:08 +00:00
self.error_handler.debug = debug
self.debug = debug
2017-01-24 03:58:37 +00:00
server_settings = {
'protocol': protocol,
'request_class': self.request_class,
2017-05-05 12:09:32 +01:00
'is_request_stream': self.is_request_stream,
'router': self.router,
2017-01-24 03:58:37 +00:00
'host': host,
'port': port,
'sock': sock,
'ssl': ssl,
2017-04-20 10:27:28 +01:00
'signal': Signal(),
2017-01-24 03:58:37 +00:00
'debug': debug,
'request_handler': self.handle_request,
'error_handler': self.error_handler,
'request_timeout': self.config.REQUEST_TIMEOUT,
'response_timeout': self.config.RESPONSE_TIMEOUT,
'keep_alive_timeout': self.config.KEEP_ALIVE_TIMEOUT,
2017-01-24 03:58:37 +00:00
'request_max_size': self.config.REQUEST_MAX_SIZE,
2017-04-17 06:43:49 +01:00
'keep_alive': self.config.KEEP_ALIVE,
2017-01-24 03:58:37 +00:00
'loop': loop,
'register_sys_signals': register_sys_signals,
'backlog': backlog,
'access_log': self.config.ACCESS_LOG,
'websocket_max_size': self.config.WEBSOCKET_MAX_SIZE,
'websocket_max_queue': self.config.WEBSOCKET_MAX_QUEUE,
2018-02-10 04:44:02 +00:00
'websocket_read_limit': self.config.WEBSOCKET_READ_LIMIT,
'websocket_write_limit': self.config.WEBSOCKET_WRITE_LIMIT,
'graceful_shutdown_timeout': self.config.GRACEFUL_SHUTDOWN_TIMEOUT
2017-01-24 03:58:37 +00:00
}
# -------------------------------------------- #
# Register start/stop events
# -------------------------------------------- #
for event_name, settings_name, reverse in (
("before_server_start", "before_start", False),
("after_server_start", "after_start", False),
("before_server_stop", "before_stop", True),
("after_server_stop", "after_stop", True),
):
listeners = self.listeners[event_name].copy()
2017-01-24 03:58:37 +00:00
if reverse:
listeners.reverse()
# Prepend sanic to the arguments when listeners are triggered
listeners = [partial(listener, self) for listener in listeners]
server_settings[settings_name] = listeners
if self.configure_logging and debug:
2017-09-12 06:12:49 +01:00
logger.setLevel(logging.DEBUG)
2017-12-07 13:00:54 +00:00
if self.config.LOGO is not None and \
os.environ.get('SANIC_SERVER_RUNNING') != 'true':
2017-09-12 06:12:49 +01:00
logger.debug(self.config.LOGO)
2017-01-24 03:58:37 +00:00
2017-01-29 22:01:00 +00:00
if run_async:
server_settings['run_async'] = True
2017-01-24 03:58:37 +00:00
# Serve
if host and port and os.environ.get('SANIC_SERVER_RUNNING') != 'true':
2017-03-12 08:19:34 +00:00
proto = "http"
if ssl is not None:
proto = "https"
2017-09-12 06:12:49 +01:00
logger.info('Goin\' Fast @ {}://{}:{}'.format(proto, host, port))
2017-01-24 03:58:37 +00:00
return server_settings