handle http errors better and add access logging

This commit is contained in:
Izalia Mae 2024-04-10 20:33:54 -04:00
parent d74dd18ed2
commit 7541349422
5 changed files with 80 additions and 7 deletions

View file

@ -1,8 +1,17 @@
from .application import Application
from .request import Request
from .response import Response
from .router import post
app = Application(
"test", "basgi.application:handle_run_app", "0.0.0.0", dev = True, reload_dirs = ["basgi"]
)
@app.router.route("/heck", methods = "POST")
async def handle_heck(request: Request) -> Response:
return Response(200, "UvU")
app.run()

View file

@ -1,5 +1,6 @@
import asyncio
import os
import traceback
import uvicorn
from collections.abc import Awaitable, Callable
@ -71,14 +72,22 @@ class Application:
if response is None:
raise HttpError(500, "Empty response")
await self.on_request.handle_emit(request)
except InvalidMethodError:
response = Response(405, request.method)
except NotFoundError:
response = Response(404, request.path)
except HttpError as e:
response = Response(e.status, e.message, headers = e.headers)
except HttpError as error:
response = Response.new_from_http_error(error)
except Exception:
response = Response(500, "Internal Server Error")
traceback.print_exc()
self.print_access_log(request, response)
await send({
"type": "http.response.start",
@ -114,6 +123,26 @@ class Application:
pass
def print_access_log(self, request: Request, response: Response) -> None:
message = "{}: {} \"{} {}\" {} {} \"{}\"".format(
"INFO",
request.headers.get(
"X-Forwarded-For",
request.headers.get(
"X-Real-Ip",
request.remote or "n/a"
)
),
request.method,
request.path,
response.status.value,
int(response.headers.get("Content-Length", 0)),
request.headers.get("User-Agent", "n/a")
)
print(message, flush = True)
def run(self) -> None:
try:
asyncio.run(self.handle_run_server())

View file

@ -1,6 +1,6 @@
from typing import Any, Self
from .enum import HttpStatus
from .enums import HttpStatus
class HttpError(Exception):
@ -86,4 +86,4 @@ class HttpError(Exception):
@property
def reason(self) -> str:
return self.status.reason # type: ignore
return self.status.reason

View file

@ -1,6 +1,7 @@
from multidict import CIMultiDict
from typing import Any
from typing import Any, Self
from .error import HttpError
from .misc import convert_to_bytes
@ -11,14 +12,36 @@ class Response:
mimetype: str | None = None,
headers: dict[str, str] | None = None) -> None:
self._body = b""
self.status: int = status
self.body: bytes = convert_to_bytes(body)
self.headers: CIMultiDict[str] = CIMultiDict(headers or {})
if body:
self.body = body
if mimetype:
self.mimetype = mimetype
@classmethod
def new_from_http_error(cls: type[Self], error: HttpError) -> Self:
message = f"HTTP Error {error.status.value}: {error.message}"
return cls(error.status, message, headers = error.headers)
@property
def body(self) -> bytes:
return self._body
@body.setter
def body(self, value: Any) -> None:
self._body = convert_to_bytes(value)
self.headers.popall("Content-Length", None)
self.headers["Content-Length"] = str(len(self._body))
@property
def mimetype(self) -> str:
return self.headers["Content-Type"]

View file

@ -1,8 +1,10 @@
import http_router
from collections.abc import Awaitable, Callable, Iterable
from http_router.routes import RouteMatch
from typing import Any, Pattern
from .error import HttpError
from .request import Request
from .response import Response
@ -14,7 +16,17 @@ class Router(http_router.Router):
def __init__(self, name: str, **kwargs: Any):
http_router.Router.__init__(self, **kwargs)
self.name: str = name
set_router(self)
def __call__(self, *args: Any, **kwargs: Any) -> RouteMatch:
try:
return http_router.Router.__call__(self, *args, **kwargs)
except http_router.NotFoundError as e:
raise HttpError(404, e.args[1]) from None
except http_router.InvalidMethodError as e:
raise HttpError(405, e.args[0]) from None
def __repr__(self) -> str: