From bfc9c2ce542299f3de11cee2e39ab737a93d8177 Mon Sep 17 00:00:00 2001 From: elliott Date: Wed, 23 Nov 2022 01:19:46 +0800 Subject: [PATCH 1/2] Adding error handing into request processing 1. Adding Exception handling when processing route. 2. Adding decorator "errorhandler" for customizing in user code. --- phew/server.py | 67 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/phew/server.py b/phew/server.py index 02dce58..a5a9f50 100644 --- a/phew/server.py +++ b/phew/server.py @@ -1,9 +1,30 @@ import uasyncio, os, time from . import logging +import sys +import io _routes = [] catchall_handler = None +_error_handler = None +_default_error_message = """ + + + +Error + + +

Internal Server Error

+

Please check log for detail.

+ + +""" +# convert exception to string +def convert_exc2str(e): + __ = io.StringIO() + sys.print_exception(e, __) + __.seek(0) + return __.read() def file_exists(filename): try: @@ -11,7 +32,6 @@ def file_exists(filename): except OSError: return False - def urldecode(text): text = text.replace("+", " ") result = "" @@ -137,7 +157,7 @@ def call_handler(self, request): parameters[name] = compare return self.handler(request, **parameters) - + def __str__(self): return f"""\ path: {self.path} @@ -208,7 +228,7 @@ async def _parse_json_body(reader, headers): status_message_map = { - 200: "OK", 201: "Created", 202: "Accepted", + 200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", @@ -217,12 +237,11 @@ async def _parse_json_body(reader, headers): 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 408: "Request Timeout", 409: "Conflict", 410: "Gone", - 414: "URI Too Long", 415: "Unsupported Media Type", + 414: "URI Too Long", 415: "Unsupported Media Type", 416: "Range Not Satisfiable", 418: "I'm a teapot", 500: "Internal Server Error", 501: "Not Implemented" } - # handle an incoming request to the web server async def _handle_request(reader, writer): response = None @@ -245,13 +264,23 @@ async def _handle_request(reader, writer): request.data = await _parse_json_body(reader, request.headers) if request.headers["content-type"].startswith("application/x-www-form-urlencoded"): form_data = await reader.read(int(request.headers["content-length"])) - request.form = _parse_query_string(form_data.decode()) + request.form = _parse_query_string(form_data.decode()) route = _match_route(request) - if route: - response = route.call_handler(request) - elif catchall_handler: - response = catchall_handler(request) + + try: + if route: + response = route.call_handler(request) + elif catchall_handler: + response = catchall_handler(request) + + except Exception as e: + error_msg = convert_exc2str(e) + logging.error(error_msg) + if _error_handler is None: + response = Response(_default_error_message, status = 500) + else: + response = _error_handler(e, error_msg) # if shorthand body generator only notation used then convert to tuple if type(response).__name__ == "generator": @@ -270,7 +299,7 @@ async def _handle_request(reader, writer): response.add_header("Content-Type", content_type) if hasattr(body, '__len__'): response.add_header("Content-Length", len(body)) - + # write status line status_message = status_message_map.get(response.status, "Unknown") writer.write(f"HTTP/1.1 {response.status} {status_message}\r\n".encode("ascii")) @@ -281,7 +310,7 @@ async def _handle_request(reader, writer): # blank line to denote end of headers writer.write("\r\n".encode("ascii")) - + if isinstance(response, FileResponse): # file with open(response.file, "rb") as f: @@ -300,10 +329,10 @@ async def _handle_request(reader, writer): # string/bytes writer.write(response.body) await writer.drain() - + writer.close() await writer.wait_closed() - + processing_time = time.ticks_ms() - request_start_time logging.info(f"> {request.method} {request.path} ({response.status} {status_message}) [{processing_time}ms]") @@ -335,7 +364,15 @@ def _catchall(f): set_callback(f) return f return _catchall - + +def errorhandler(): + def _wrap(f): + global _error_handler + _error_handler = f + return f + return _wrap + + def redirect(url, status = 301): return Response("", status, {"Location": url}) From aebd2ce090bbf16305d9c5b1ab234672f1f1ce79 Mon Sep 17 00:00:00 2001 From: elliott Date: Sat, 26 Nov 2022 15:32:46 +0800 Subject: [PATCH 2/2] Adding fallback when user _error_handler raise Exception --- phew/server.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/phew/server.py b/phew/server.py index a5a9f50..d146706 100644 --- a/phew/server.py +++ b/phew/server.py @@ -267,7 +267,7 @@ async def _handle_request(reader, writer): request.form = _parse_query_string(form_data.decode()) route = _match_route(request) - + global _error_handler try: if route: response = route.call_handler(request) @@ -276,11 +276,20 @@ async def _handle_request(reader, writer): except Exception as e: error_msg = convert_exc2str(e) - logging.error(error_msg) - if _error_handler is None: - response = Response(_default_error_message, status = 500) - else: - response = _error_handler(e, error_msg) + try: + + if _error_handler is not None: + response = _error_handler(e, error_msg) + + except Exception as ee: + # replacing error message with new one + error_msg = convert_exc2str(ee) + + finally: + if response is None: + response = Response(_default_error_message, status = 500) + + logging.error(error_msg) # if shorthand body generator only notation used then convert to tuple if type(response).__name__ == "generator":