🐛 improve exception handling
This commit is contained in:
parent
8e37e58a99
commit
23360a3d91
@ -1,4 +1,5 @@
|
|||||||
from spiderweb import WebServer
|
from spiderweb import WebServer
|
||||||
|
from spiderweb.exceptions import ServerError
|
||||||
from spiderweb.response import HttpResponse, JsonResponse, TemplateResponse, RedirectResponse
|
from spiderweb.response import HttpResponse, JsonResponse, TemplateResponse, RedirectResponse
|
||||||
|
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ def json(request):
|
|||||||
|
|
||||||
@app.route("/error")
|
@app.route("/error")
|
||||||
def error(request):
|
def error(request):
|
||||||
return HttpResponse(status_code=500, body="Internal Server Error")
|
raise ServerError
|
||||||
|
|
||||||
|
|
||||||
@app.route("/middleware")
|
@app.route("/middleware")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "spiderweb"
|
name = "spiderweb"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
description = "A small web framework, just big enough to hold your average spider."
|
description = "A small web framework, just big enough to hold your average spider."
|
||||||
authors = ["Joe Kaufeld <opensource@joekaufeld.com>"]
|
authors = ["Joe Kaufeld <opensource@joekaufeld.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
class SpiderwebException(Exception):
|
class SpiderwebException(Exception):
|
||||||
# parent error class; all child exceptions should inherit from this
|
# parent error class; all child exceptions should inherit from this
|
||||||
pass
|
def __str__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.code}, {self.msg})"
|
||||||
|
|
||||||
|
|
||||||
class SpiderwebNetworkException(SpiderwebException):
|
class SpiderwebNetworkException(SpiderwebException):
|
||||||
@ -11,14 +12,46 @@ class SpiderwebNetworkException(SpiderwebException):
|
|||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.desc = desc
|
self.desc = desc
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.__class__.__name__}({self.code}, {self.msg})"
|
|
||||||
|
|
||||||
|
|
||||||
class APIError(SpiderwebNetworkException):
|
class APIError(SpiderwebNetworkException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotFound(SpiderwebNetworkException):
|
||||||
|
def __init__(self):
|
||||||
|
self.code = 404
|
||||||
|
self.msg = "Not Found"
|
||||||
|
self.desc = "The requested resource could not be found"
|
||||||
|
|
||||||
|
|
||||||
|
class BadRequest(SpiderwebNetworkException):
|
||||||
|
def __init__(self, desc=None):
|
||||||
|
self.code = 400
|
||||||
|
self.msg = "Bad Request"
|
||||||
|
self.desc = desc if desc else "The request could not be understood by the server"
|
||||||
|
|
||||||
|
|
||||||
|
class Unauthorized(SpiderwebNetworkException):
|
||||||
|
def __init__(self, desc=None):
|
||||||
|
self.code = 401
|
||||||
|
self.msg = "Unauthorized"
|
||||||
|
self.desc = desc if desc else "The request requires user authentication"
|
||||||
|
|
||||||
|
|
||||||
|
class Forbidden(SpiderwebNetworkException):
|
||||||
|
def __init__(self, desc=None):
|
||||||
|
self.code = 403
|
||||||
|
self.msg = "Forbidden"
|
||||||
|
self.desc = desc if desc else "You are not allowed to access this resource"
|
||||||
|
|
||||||
|
|
||||||
|
class ServerError(SpiderwebNetworkException):
|
||||||
|
def __init__(self, desc=None):
|
||||||
|
self.code = 500
|
||||||
|
self.msg = "Internal Server Error"
|
||||||
|
self.desc = desc if desc else "The server has encountered an error"
|
||||||
|
|
||||||
|
|
||||||
class ConfigError(SpiderwebException):
|
class ConfigError(SpiderwebException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
@ -23,7 +22,7 @@ from spiderweb.exceptions import (
|
|||||||
ConfigError,
|
ConfigError,
|
||||||
ParseError,
|
ParseError,
|
||||||
GeneralException,
|
GeneralException,
|
||||||
NoResponseError, UnusedMiddleware,
|
NoResponseError, UnusedMiddleware, SpiderwebNetworkException, NotFound,
|
||||||
)
|
)
|
||||||
from spiderweb.request import Request
|
from spiderweb.request import Request
|
||||||
from spiderweb.response import HttpResponse, JsonResponse, TemplateResponse, RedirectResponse
|
from spiderweb.response import HttpResponse, JsonResponse, TemplateResponse, RedirectResponse
|
||||||
@ -142,11 +141,8 @@ class WebServer(HTTPServer):
|
|||||||
raise ParseError(f"Unknown converter {converter}")
|
raise ParseError(f"Unknown converter {converter}")
|
||||||
else:
|
else:
|
||||||
converter = StrConverter # noqa: F405
|
converter = StrConverter # noqa: F405
|
||||||
parts[i] = r"(?P<%s>%s)" % (
|
parts[i] = rf"(?P<{name}__{str(converter.__name__)}>{converter.regex})"
|
||||||
f"{name}__{str(converter.__name__)}",
|
return re.compile(rf"^{'/'.join(parts)}$")
|
||||||
converter.regex,
|
|
||||||
)
|
|
||||||
return re.compile(r"^%s$" % "/".join(parts))
|
|
||||||
|
|
||||||
def check_for_route_duplicates(self, path: str):
|
def check_for_route_duplicates(self, path: str):
|
||||||
if self.convert_path(path) in self.handler_class._routes:
|
if self.convert_path(path) in self.handler_class._routes:
|
||||||
@ -233,6 +229,9 @@ class WebServer(HTTPServer):
|
|||||||
class RequestHandler(BaseHTTPRequestHandler):
|
class RequestHandler(BaseHTTPRequestHandler):
|
||||||
# I can't help the naming convention of these because that's what
|
# I can't help the naming convention of these because that's what
|
||||||
# BaseHTTPRequestHandler uses for some weird reason
|
# BaseHTTPRequestHandler uses for some weird reason
|
||||||
|
|
||||||
|
# These stop pycharm from complaining about these not existing. They're
|
||||||
|
# injected by the WebServer class at runtime
|
||||||
_routes = {}
|
_routes = {}
|
||||||
middleware = []
|
middleware = []
|
||||||
|
|
||||||
@ -269,7 +268,7 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
return self._routes[option], convert_match_to_dict(
|
return self._routes[option], convert_match_to_dict(
|
||||||
match_data.groupdict()
|
match_data.groupdict()
|
||||||
)
|
)
|
||||||
raise APIError(404, "No route found")
|
raise NotFound()
|
||||||
|
|
||||||
def get_error_route(self, code: int) -> Callable:
|
def get_error_route(self, code: int) -> Callable:
|
||||||
try:
|
try:
|
||||||
@ -319,10 +318,10 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
self.middleware.remove(middleware)
|
self.middleware.remove(middleware)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def prepare_response(self, request, resp) -> HttpResponse:
|
def prepare_and_fire_response(self, request, resp) -> None:
|
||||||
try:
|
try:
|
||||||
if isinstance(resp, dict):
|
if isinstance(resp, dict):
|
||||||
self.fire_response(JsonResponse(data=resp))
|
self.fire_response(request, JsonResponse(data=resp))
|
||||||
if isinstance(resp, TemplateResponse):
|
if isinstance(resp, TemplateResponse):
|
||||||
if hasattr(self, "env"): # injected from above
|
if hasattr(self, "env"): # injected from above
|
||||||
resp.set_template_loader(self.env)
|
resp.set_template_loader(self.env)
|
||||||
@ -335,17 +334,25 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
except APIError:
|
except APIError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
except ConnectionAbortedError as e:
|
|
||||||
log.error(f"GET {self.path} : {e}")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
self.fire_response(request, self.get_error_route(500)(request))
|
self.fire_response(request, self.get_error_route(500)(request))
|
||||||
|
|
||||||
|
def send_error_response(self, request: Request, e: SpiderwebNetworkException):
|
||||||
|
try:
|
||||||
|
self.send_error(e.code, e.msg, e.desc)
|
||||||
|
except ConnectionAbortedError as e:
|
||||||
|
log.error(f"{request.method} {self.path} : {e}")
|
||||||
|
|
||||||
def handle_request(self, request):
|
def handle_request(self, request):
|
||||||
|
|
||||||
request.url = urlparse.urlparse(request.path)
|
request.url = urlparse.urlparse(request.path)
|
||||||
|
|
||||||
handler, additional_args = self.get_route(request.url.path)
|
try:
|
||||||
|
handler, additional_args = self.get_route(request.url.path)
|
||||||
|
except NotFound:
|
||||||
|
handler = self.get_error_route(404)
|
||||||
|
additional_args = {}
|
||||||
|
|
||||||
if request.url.query:
|
if request.url.query:
|
||||||
params = urlparse.parse_qs(request.url.query)
|
params = urlparse.parse_qs(request.url.query)
|
||||||
@ -363,18 +370,9 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
resp = handler(request, **additional_args)
|
resp = handler(request, **additional_args)
|
||||||
if resp is None:
|
if resp is None:
|
||||||
raise NoResponseError(f"View {handler} returned None.")
|
raise NoResponseError(f"View {handler} returned None.")
|
||||||
if isinstance(resp, dict):
|
# run the response through the middleware and send it
|
||||||
self.fire_response(request, JsonResponse(data=resp))
|
self.prepare_and_fire_response(request, resp)
|
||||||
if isinstance(resp, TemplateResponse):
|
|
||||||
if hasattr(self, "env"): # injected from above
|
|
||||||
resp.set_template_loader(self.env)
|
|
||||||
|
|
||||||
self.process_response_middleware(request, resp)
|
|
||||||
self.fire_response(request, resp)
|
|
||||||
else:
|
else:
|
||||||
raise APIError(404)
|
raise SpiderwebNetworkException(404)
|
||||||
except APIError as e:
|
except SpiderwebNetworkException as e:
|
||||||
try:
|
self.send_error_response(request, e)
|
||||||
self.send_error(e.code, e.msg, e.desc)
|
|
||||||
except ConnectionAbortedError as e:
|
|
||||||
log.error(f"GET {self.path} : {e}")
|
|
||||||
|
Loading…
Reference in New Issue
Block a user