handle http errors better and add access logging
This commit is contained in:
parent
d74dd18ed2
commit
7541349422
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue