diff --git a/sanic/exceptions.py b/sanic/exceptions.py new file mode 100644 index 00000000..c648512d --- /dev/null +++ b/sanic/exceptions.py @@ -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)) \ No newline at end of file diff --git a/sanic/response.py b/sanic/response.py index c19fee6c..26df2ecd 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -2,7 +2,17 @@ import ujson STATUS_CODES = { 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: __slots__ = ('body', 'status', 'content_type') @@ -36,12 +46,9 @@ class HTTPResponse: #b'\r\n' ]) - -def error_404(request, *args): - return HTTPResponse("404!", status=404) -error_404.is_async = False - -def json(input): - return HTTPResponse(ujson.dumps(input), content_type="application/json") -def text(input): - return HTTPResponse(input, content_type="text/plain") \ No newline at end of file +def json(body, status=200): + return HTTPResponse(ujson.dumps(body), status=status, content_type="application/json") +def text(body, status=200): + return HTTPResponse(body, status=status, content_type="text/plain") +def html(body, status=200): + return HTTPResponse(body, status=status, content_type="text/html") \ No newline at end of file diff --git a/sanic/router.py b/sanic/router.py index 93ea07bc..51abd880 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -1,19 +1,19 @@ from .log import log +from .exceptions import NotFound class Router(): routes = None - default = None - def __init__(self, default=None): + def __init__(self): self.routes = {} - self.default=default - def add(self, route, handler): - self.routes[route] = handler + def add(self, uri, handler): + self.routes[uri] = handler - def get(self, uri): - handler = self.routes.get(uri.decode('utf-8'), self.default) + def get(self, request): + uri_string = request.url.decode('utf-8') + handler = self.routes.get(uri_string) if handler: return handler else: - return self.default \ No newline at end of file + raise NotFound("Requested URL {} not found".format(uri_string)) \ No newline at end of file diff --git a/sanic/sanic.py b/sanic/sanic.py index eada7657..86d1df67 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -1,24 +1,29 @@ -import inspect from .router import Router -from .response import HTTPResponse, error_404 +from .exceptions import Handler +from .response import HTTPResponse from .server import serve from .log import log class Sanic: name = None + router = None + error_handler = None routes = [] - def __init__(self, name, router=None): + def __init__(self, name, router=None, error_handler=None): 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 response(handler): - handler.is_async = inspect.iscoroutinefunction(handler) - self.router.add(*args, **kwargs, handler=handler) + self.add_route(handler, *args, **kwargs) return handler 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): - return serve(router=self.router, host=host, port=port, debug=debug) \ No newline at end of file + return serve(sanic=self, host=host, port=port, debug=debug) \ No newline at end of file diff --git a/sanic/server.py b/sanic/server.py index 4b9f9673..4da74b6d 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -5,6 +5,7 @@ import signal import functools import httptools import logging +from inspect import iscoroutine from ujson import loads as json_loads from urllib.parse import parse_qs @@ -18,6 +19,7 @@ from socket import * from .log import log from .config import LOGO +from .exceptions import ServerError from .response import HTTPResponse PRINT = 0 @@ -65,16 +67,16 @@ class HttpProtocol(asyncio.Protocol): __slots__ = ('loop', 'transport', 'request', 'parser', - 'url', 'headers', 'router') + 'url', 'headers', 'sanic') - def __init__(self, *, router, loop): + def __init__(self, *, sanic, loop): self.loop = loop self.transport = None self.request = None self.parser = None self.url = None self.headers = None - self.router = router + self.sanic = sanic # -------------------------------------------- # # Connection @@ -140,14 +142,22 @@ class HttpProtocol(asyncio.Protocol): # -------------------------------------------- # async def get_response(self, request): - handler = self.router.get(request.url) try: - if handler.is_async: - response = await handler(request) - else: - response = handler(request) + handler = self.sanic.router.get(request) + if handler is None: + raise ServerError("'None' was returned while requesting a handler from the router") + + response = handler(request) + + # Check if the handler is asynchronous + if iscoroutine(response): + response = await response + 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) @@ -184,7 +194,7 @@ def abort(msg): sys.exit(1) -def serve(router, host, port, debug=False): +def serve(sanic, host, port, debug=False): # Create Event Loop loop = async_loop.new_event_loop() asyncio.set_event_loop(loop) @@ -205,7 +215,7 @@ def serve(router, host, port, debug=False): # Serve 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) try: loop.run_forever() diff --git a/test.py b/test.py index 8a059f69..0b0073bd 100644 --- a/test.py +++ b/test.py @@ -1,5 +1,6 @@ from sanic import Sanic from sanic.response import json, text +from sanic.exceptions import ServerError app = Sanic("test") @@ -11,6 +12,14 @@ async def test(request): def test(request): 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") def test(request): return json({ "received": True, "message": request.json })