Merge branch 'master' into ssl
This commit is contained in:
commit
d2217b5c5f
|
@ -11,9 +11,16 @@ from sanic.blueprints import Blueprint
|
||||||
app = Sanic()
|
app = Sanic()
|
||||||
bp = Blueprint("bp", host="bp.example.com")
|
bp = Blueprint("bp", host="bp.example.com")
|
||||||
|
|
||||||
|
@app.route('/', host=["example.com",
|
||||||
|
"somethingelse.com",
|
||||||
|
"therestofyourdomains.com"])
|
||||||
|
async def hello(request):
|
||||||
|
return text("Some defaults")
|
||||||
|
|
||||||
@app.route('/', host="example.com")
|
@app.route('/', host="example.com")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("Answer")
|
return text("Answer")
|
||||||
|
|
||||||
@app.route('/', host="sub.example.com")
|
@app.route('/', host="sub.example.com")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("42")
|
return text("42")
|
||||||
|
|
|
@ -173,6 +173,7 @@ class Handler:
|
||||||
try:
|
try:
|
||||||
response = handler(request=request, exception=exception)
|
response = handler(request=request, exception=exception)
|
||||||
except:
|
except:
|
||||||
|
log.error(format_exc())
|
||||||
if self.sanic.debug:
|
if self.sanic.debug:
|
||||||
response_message = (
|
response_message = (
|
||||||
'Exception raised in exception handler "{}" '
|
'Exception raised in exception handler "{}" '
|
||||||
|
@ -185,6 +186,7 @@ class Handler:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def default(self, request, exception):
|
def default(self, request, exception):
|
||||||
|
log.error(format_exc())
|
||||||
if issubclass(type(exception), SanicException):
|
if issubclass(type(exception), SanicException):
|
||||||
return text(
|
return text(
|
||||||
'Error: {}'.format(exception),
|
'Error: {}'.format(exception),
|
||||||
|
|
|
@ -3,6 +3,7 @@ from collections import defaultdict, namedtuple
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .exceptions import NotFound, InvalidUsage
|
from .exceptions import NotFound, InvalidUsage
|
||||||
|
from .views import CompositionView
|
||||||
|
|
||||||
Route = namedtuple('Route', ['handler', 'methods', 'pattern', 'parameters'])
|
Route = namedtuple('Route', ['handler', 'methods', 'pattern', 'parameters'])
|
||||||
Parameter = namedtuple('Parameter', ['name', 'cast'])
|
Parameter = namedtuple('Parameter', ['name', 'cast'])
|
||||||
|
@ -75,11 +76,15 @@ class Router:
|
||||||
if self.hosts is None:
|
if self.hosts is None:
|
||||||
self.hosts = set(host)
|
self.hosts = set(host)
|
||||||
else:
|
else:
|
||||||
|
if isinstance(host, list):
|
||||||
|
host = set(host)
|
||||||
self.hosts.add(host)
|
self.hosts.add(host)
|
||||||
|
if isinstance(host, str):
|
||||||
uri = host + uri
|
uri = host + uri
|
||||||
|
else:
|
||||||
if uri in self.routes_all:
|
for h in host:
|
||||||
raise RouteExists("Route already registered: {}".format(uri))
|
self.add(uri, methods, handler, h)
|
||||||
|
return
|
||||||
|
|
||||||
# Dict for faster lookups of if method allowed
|
# Dict for faster lookups of if method allowed
|
||||||
if methods:
|
if methods:
|
||||||
|
@ -113,6 +118,32 @@ class Router:
|
||||||
pattern_string = re.sub(r'<(.+?)>', add_parameter, uri)
|
pattern_string = re.sub(r'<(.+?)>', add_parameter, uri)
|
||||||
pattern = re.compile(r'^{}$'.format(pattern_string))
|
pattern = re.compile(r'^{}$'.format(pattern_string))
|
||||||
|
|
||||||
|
def merge_route(route, methods, handler):
|
||||||
|
# merge to the existing route when possible.
|
||||||
|
if not route.methods or not methods:
|
||||||
|
# method-unspecified routes are not mergeable.
|
||||||
|
raise RouteExists(
|
||||||
|
"Route already registered: {}".format(uri))
|
||||||
|
elif route.methods.intersection(methods):
|
||||||
|
# already existing method is not overloadable.
|
||||||
|
duplicated = methods.intersection(route.methods)
|
||||||
|
raise RouteExists(
|
||||||
|
"Route already registered: {} [{}]".format(
|
||||||
|
uri, ','.join(list(duplicated))))
|
||||||
|
if isinstance(route.handler, CompositionView):
|
||||||
|
view = route.handler
|
||||||
|
else:
|
||||||
|
view = CompositionView()
|
||||||
|
view.add(route.methods, route.handler)
|
||||||
|
view.add(methods, handler)
|
||||||
|
route = route._replace(
|
||||||
|
handler=view, methods=methods.union(route.methods))
|
||||||
|
return route
|
||||||
|
|
||||||
|
route = self.routes_all.get(uri)
|
||||||
|
if route:
|
||||||
|
route = merge_route(route, methods, handler)
|
||||||
|
else:
|
||||||
route = Route(
|
route = Route(
|
||||||
handler=handler, methods=methods, pattern=pattern,
|
handler=handler, methods=methods, pattern=pattern,
|
||||||
parameters=parameters)
|
parameters=parameters)
|
||||||
|
|
|
@ -232,7 +232,7 @@ class Sanic:
|
||||||
e, format_exc()))
|
e, format_exc()))
|
||||||
else:
|
else:
|
||||||
response = HTTPResponse(
|
response = HTTPResponse(
|
||||||
"An error occured while handling an error")
|
"An error occurred while handling an error")
|
||||||
|
|
||||||
response_callback(response)
|
response_callback(response)
|
||||||
|
|
||||||
|
@ -241,10 +241,10 @@ class Sanic:
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
|
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
|
||||||
|
|
||||||
after_start=None, before_stop=None, after_stop=None, ssl=None,
|
after_start=None, before_stop=None, after_stop=None, ssl=None,
|
||||||
sock=None, workers=1, loop=None, protocol=HttpProtocol,
|
sock=None, workers=1, loop=None, protocol=HttpProtocol,
|
||||||
backlog=100, stop_event=None):
|
backlog=100, stop_event=None):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Runs the HTTP Server and listens until keyboard interrupt or term
|
Runs the HTTP Server and listens until keyboard interrupt or term
|
||||||
signal. On termination, drains connections before closing.
|
signal. On termination, drains connections before closing.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import traceback
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from signal import SIGINT, SIGTERM
|
from signal import SIGINT, SIGTERM
|
||||||
|
@ -189,6 +190,12 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
"Writing error failed, connection closed {}".format(e))
|
"Writing error failed, connection closed {}".format(e))
|
||||||
|
|
||||||
def bail_out(self, message):
|
def bail_out(self, message):
|
||||||
|
if self.transport.is_closing():
|
||||||
|
log.error(
|
||||||
|
"Connection closed before error was sent to user @ {}".format(
|
||||||
|
self.transport.get_extra_info('peername')))
|
||||||
|
log.debug('Error experienced:\n{}'.format(traceback.format_exc()))
|
||||||
|
else:
|
||||||
exception = ServerError(message)
|
exception = ServerError(message)
|
||||||
self.write_error(exception)
|
self.write_error(exception)
|
||||||
log.error(message)
|
log.error(message)
|
||||||
|
|
|
@ -61,3 +61,38 @@ class HTTPMethodView:
|
||||||
view.__doc__ = cls.__doc__
|
view.__doc__ = cls.__doc__
|
||||||
view.__module__ = cls.__module__
|
view.__module__ = cls.__module__
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
|
||||||
|
class CompositionView:
|
||||||
|
""" Simple method-function mapped view for the sanic.
|
||||||
|
You can add handler functions to methods (get, post, put, patch, delete)
|
||||||
|
for every HTTP method you want to support.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
view = CompositionView()
|
||||||
|
view.add(['GET'], lambda request: text('I am get method'))
|
||||||
|
view.add(['POST', 'PUT'], lambda request: text('I am post/put method'))
|
||||||
|
|
||||||
|
etc.
|
||||||
|
|
||||||
|
If someone tries to use a non-implemented method, there will be a
|
||||||
|
405 response.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.handlers = {}
|
||||||
|
|
||||||
|
def add(self, methods, handler):
|
||||||
|
for method in methods:
|
||||||
|
if method in self.handlers:
|
||||||
|
raise KeyError(
|
||||||
|
'Method {} already is registered.'.format(method))
|
||||||
|
self.handlers[method] = handler
|
||||||
|
|
||||||
|
def __call__(self, request, *args, **kwargs):
|
||||||
|
handler = self.handlers.get(request.method.upper(), None)
|
||||||
|
if handler is None:
|
||||||
|
raise InvalidUsage(
|
||||||
|
'Method {} not allowed for URL {}'.format(
|
||||||
|
request.method, request.url), status_code=405)
|
||||||
|
return handler(request, *args, **kwargs)
|
||||||
|
|
|
@ -463,3 +463,67 @@ def test_remove_route_without_clean_cache():
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, uri='/test')
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_overload_routes():
|
||||||
|
app = Sanic('test_dynamic_route')
|
||||||
|
|
||||||
|
@app.route('/overload', methods=['GET'])
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
@app.route('/overload', methods=['POST', 'PUT'])
|
||||||
|
async def handler2(request):
|
||||||
|
return text('OK2')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'get', uri='/overload')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'post', uri='/overload')
|
||||||
|
assert response.text == 'OK2'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'put', uri='/overload')
|
||||||
|
assert response.text == 'OK2'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'delete', uri='/overload')
|
||||||
|
assert response.status == 405
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
@app.route('/overload', methods=['PUT', 'DELETE'])
|
||||||
|
async def handler3(request):
|
||||||
|
return text('Duplicated')
|
||||||
|
|
||||||
|
|
||||||
|
def test_unmergeable_overload_routes():
|
||||||
|
app = Sanic('test_dynamic_route')
|
||||||
|
|
||||||
|
@app.route('/overload_whole')
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
@app.route('/overload_whole', methods=['POST', 'PUT'])
|
||||||
|
async def handler2(request):
|
||||||
|
return text('Duplicated')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'get', uri='/overload_whole')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'post', uri='/overload_whole')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/overload_part', methods=['GET'])
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
@app.route('/overload_part')
|
||||||
|
async def handler2(request):
|
||||||
|
return text('Duplicated')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'get', uri='/overload_part')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'post', uri='/overload_part')
|
||||||
|
assert response.status == 405
|
||||||
|
|
|
@ -4,7 +4,7 @@ from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
|
||||||
def test_vhosts():
|
def test_vhosts():
|
||||||
app = Sanic('test_text')
|
app = Sanic('test_vhosts')
|
||||||
|
|
||||||
@app.route('/', host="example.com")
|
@app.route('/', host="example.com")
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
|
@ -21,3 +21,19 @@ def test_vhosts():
|
||||||
headers = {"Host": "subdomain.example.com"}
|
headers = {"Host": "subdomain.example.com"}
|
||||||
request, response = sanic_endpoint_test(app, headers=headers)
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
assert response.text == "You're at subdomain.example.com!"
|
assert response.text == "You're at subdomain.example.com!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_vhosts_with_list():
|
||||||
|
app = Sanic('test_vhosts')
|
||||||
|
|
||||||
|
@app.route('/', host=["hello.com", "world.com"])
|
||||||
|
async def handler(request):
|
||||||
|
return text("Hello, world!")
|
||||||
|
|
||||||
|
headers = {"Host": "hello.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
|
assert response.text == "Hello, world!"
|
||||||
|
|
||||||
|
headers = {"Host": "world.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
|
assert response.text == "Hello, world!"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user