Added better error handling and coroutine checking

This commit is contained in:
Channel Cat 2016-10-02 20:47:15 -07:00
parent e0b9260644
commit b59dc2729f
6 changed files with 95 additions and 36 deletions

28
sanic/exceptions.py Normal file
View File

@ -0,0 +1,28 @@
from .response import html
class NotFound(Exception):
status_code = 404
class InvalidUsage(Exception):
status_code = 400
class ServerError(Exception):
status_code = 500
class Handler:
handlers = None
def __init__(self):
self.handlers = {}
def add(self, exception_type, handler):
self.handlers[exception_type] = handler
def response(self, request, exception):
handler = self.handlers.get(type(exception))
if handler:
response = handler(request, exception)
else:
response = Handler.default(request, exception)
return response
@staticmethod
def default(request, exception):
return html("Error: {}".format(exception), status=getattr(exception, 'status_code', 500))

View File

@ -2,7 +2,17 @@ import ujson
STATUS_CODES = { STATUS_CODES = {
200: 'OK', 200: 'OK',
404: 'Not Found' 400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
400: 'Method Not Allowed',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
} }
class HTTPResponse: class HTTPResponse:
__slots__ = ('body', 'status', 'content_type') __slots__ = ('body', 'status', 'content_type')
@ -36,12 +46,9 @@ class HTTPResponse:
#b'\r\n' #b'\r\n'
]) ])
def json(body, status=200):
def error_404(request, *args): return HTTPResponse(ujson.dumps(body), status=status, content_type="application/json")
return HTTPResponse("404!", status=404) def text(body, status=200):
error_404.is_async = False return HTTPResponse(body, status=status, content_type="text/plain")
def html(body, status=200):
def json(input): return HTTPResponse(body, status=status, content_type="text/html")
return HTTPResponse(ujson.dumps(input), content_type="application/json")
def text(input):
return HTTPResponse(input, content_type="text/plain")

View File

@ -1,19 +1,19 @@
from .log import log from .log import log
from .exceptions import NotFound
class Router(): class Router():
routes = None routes = None
default = None
def __init__(self, default=None): def __init__(self):
self.routes = {} self.routes = {}
self.default=default
def add(self, route, handler): def add(self, uri, handler):
self.routes[route] = handler self.routes[uri] = handler
def get(self, uri): def get(self, request):
handler = self.routes.get(uri.decode('utf-8'), self.default) uri_string = request.url.decode('utf-8')
handler = self.routes.get(uri_string)
if handler: if handler:
return handler return handler
else: else:
return self.default raise NotFound("Requested URL {} not found".format(uri_string))

View File

@ -1,24 +1,29 @@
import inspect
from .router import Router from .router import Router
from .response import HTTPResponse, error_404 from .exceptions import Handler
from .response import HTTPResponse
from .server import serve from .server import serve
from .log import log from .log import log
class Sanic: class Sanic:
name = None name = None
router = None
error_handler = None
routes = [] routes = []
def __init__(self, name, router=None): def __init__(self, name, router=None, error_handler=None):
self.name = name self.name = name
self.router = router or Router(default=error_404) self.router = router or Router()
self.error_handler = error_handler or Handler()
def route(self, *args, **kwargs): def route(self, *args, **kwargs):
def response(handler): def response(handler):
handler.is_async = inspect.iscoroutinefunction(handler) self.add_route(handler, *args, **kwargs)
self.router.add(*args, **kwargs, handler=handler)
return handler return handler
return response return response
def add_route(self, handler, *args, **kwargs):
self.router.add(*args, **kwargs, handler=handler)
def run(self, host="127.0.0.1", port=8000, debug=False): def run(self, host="127.0.0.1", port=8000, debug=False):
return serve(router=self.router, host=host, port=port, debug=debug) return serve(sanic=self, host=host, port=port, debug=debug)

View File

@ -5,6 +5,7 @@ import signal
import functools import functools
import httptools import httptools
import logging import logging
from inspect import iscoroutine
from ujson import loads as json_loads from ujson import loads as json_loads
from urllib.parse import parse_qs from urllib.parse import parse_qs
@ -18,6 +19,7 @@ from socket import *
from .log import log from .log import log
from .config import LOGO from .config import LOGO
from .exceptions import ServerError
from .response import HTTPResponse from .response import HTTPResponse
PRINT = 0 PRINT = 0
@ -65,16 +67,16 @@ class HttpProtocol(asyncio.Protocol):
__slots__ = ('loop', __slots__ = ('loop',
'transport', 'request', 'parser', 'transport', 'request', 'parser',
'url', 'headers', 'router') 'url', 'headers', 'sanic')
def __init__(self, *, router, loop): def __init__(self, *, sanic, loop):
self.loop = loop self.loop = loop
self.transport = None self.transport = None
self.request = None self.request = None
self.parser = None self.parser = None
self.url = None self.url = None
self.headers = None self.headers = None
self.router = router self.sanic = sanic
# -------------------------------------------- # # -------------------------------------------- #
# Connection # Connection
@ -140,14 +142,22 @@ class HttpProtocol(asyncio.Protocol):
# -------------------------------------------- # # -------------------------------------------- #
async def get_response(self, request): async def get_response(self, request):
handler = self.router.get(request.url)
try: try:
if handler.is_async: handler = self.sanic.router.get(request)
response = await handler(request) if handler is None:
else: raise ServerError("'None' was returned while requesting a handler from the router")
response = handler(request) response = handler(request)
# Check if the handler is asynchronous
if iscoroutine(response):
response = await response
except Exception as e: except Exception as e:
response = HTTPResponse("Error: {}".format(e)) try:
response = self.sanic.error_handler.response(request, e)
except Exception as e:
response = HTTPResponse("Error while handling error: {}".format(e))
self.write_response(request, response) self.write_response(request, response)
@ -184,7 +194,7 @@ def abort(msg):
sys.exit(1) sys.exit(1)
def serve(router, host, port, debug=False): def serve(sanic, host, port, debug=False):
# Create Event Loop # Create Event Loop
loop = async_loop.new_event_loop() loop = async_loop.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
@ -205,7 +215,7 @@ def serve(router, host, port, debug=False):
# Serve # Serve
log.info('Goin\' Fast @ {}:{}'.format(host, port)) log.info('Goin\' Fast @ {}:{}'.format(host, port))
server_coroutine = loop.create_server(lambda: HttpProtocol(loop=loop, router=router), host, port) server_coroutine = loop.create_server(lambda: HttpProtocol(loop=loop, sanic=sanic), host, port)
server_loop = loop.run_until_complete(server_coroutine) server_loop = loop.run_until_complete(server_coroutine)
try: try:
loop.run_forever() loop.run_forever()

View File

@ -1,5 +1,6 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import json, text from sanic.response import json, text
from sanic.exceptions import ServerError
app = Sanic("test") app = Sanic("test")
@ -11,6 +12,14 @@ async def test(request):
def test(request): def test(request):
return text('hi') return text('hi')
@app.route("/exception")
def test(request):
raise ServerError("yep")
@app.route("/exception/async")
async def test(request):
raise ServerError("asunk")
@app.route("/post_json") @app.route("/post_json")
def test(request): def test(request):
return json({ "received": True, "message": request.json }) return json({ "received": True, "message": request.json })