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 .application import Application
|
||||||
|
from .request import Request
|
||||||
|
from .response import Response
|
||||||
|
from .router import post
|
||||||
|
|
||||||
|
|
||||||
app = Application(
|
app = Application(
|
||||||
"test", "basgi.application:handle_run_app", "0.0.0.0", dev = True, reload_dirs = ["basgi"]
|
"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()
|
app.run()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
import traceback
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
|
@ -71,14 +72,22 @@ class Application:
|
||||||
if response is None:
|
if response is None:
|
||||||
raise HttpError(500, "Empty response")
|
raise HttpError(500, "Empty response")
|
||||||
|
|
||||||
|
await self.on_request.handle_emit(request)
|
||||||
|
|
||||||
except InvalidMethodError:
|
except InvalidMethodError:
|
||||||
response = Response(405, request.method)
|
response = Response(405, request.method)
|
||||||
|
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
response = Response(404, request.path)
|
response = Response(404, request.path)
|
||||||
|
|
||||||
except HttpError as e:
|
except HttpError as error:
|
||||||
response = Response(e.status, e.message, headers = e.headers)
|
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({
|
await send({
|
||||||
"type": "http.response.start",
|
"type": "http.response.start",
|
||||||
|
@ -114,6 +123,26 @@ class Application:
|
||||||
pass
|
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:
|
def run(self) -> None:
|
||||||
try:
|
try:
|
||||||
asyncio.run(self.handle_run_server())
|
asyncio.run(self.handle_run_server())
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Any, Self
|
from typing import Any, Self
|
||||||
|
|
||||||
from .enum import HttpStatus
|
from .enums import HttpStatus
|
||||||
|
|
||||||
|
|
||||||
class HttpError(Exception):
|
class HttpError(Exception):
|
||||||
|
@ -86,4 +86,4 @@ class HttpError(Exception):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reason(self) -> str:
|
def reason(self) -> str:
|
||||||
return self.status.reason # type: ignore
|
return self.status.reason
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
from typing import Any
|
from typing import Any, Self
|
||||||
|
|
||||||
|
from .error import HttpError
|
||||||
from .misc import convert_to_bytes
|
from .misc import convert_to_bytes
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,14 +12,36 @@ class Response:
|
||||||
mimetype: str | None = None,
|
mimetype: str | None = None,
|
||||||
headers: dict[str, str] | None = None) -> None:
|
headers: dict[str, str] | None = None) -> None:
|
||||||
|
|
||||||
|
self._body = b""
|
||||||
|
|
||||||
self.status: int = status
|
self.status: int = status
|
||||||
self.body: bytes = convert_to_bytes(body)
|
|
||||||
self.headers: CIMultiDict[str] = CIMultiDict(headers or {})
|
self.headers: CIMultiDict[str] = CIMultiDict(headers or {})
|
||||||
|
|
||||||
|
if body:
|
||||||
|
self.body = body
|
||||||
|
|
||||||
if mimetype:
|
if mimetype:
|
||||||
self.mimetype = 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
|
@property
|
||||||
def mimetype(self) -> str:
|
def mimetype(self) -> str:
|
||||||
return self.headers["Content-Type"]
|
return self.headers["Content-Type"]
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import http_router
|
import http_router
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable, Iterable
|
from collections.abc import Awaitable, Callable, Iterable
|
||||||
|
from http_router.routes import RouteMatch
|
||||||
from typing import Any, Pattern
|
from typing import Any, Pattern
|
||||||
|
|
||||||
|
from .error import HttpError
|
||||||
from .request import Request
|
from .request import Request
|
||||||
from .response import Response
|
from .response import Response
|
||||||
|
|
||||||
|
@ -14,7 +16,17 @@ class Router(http_router.Router):
|
||||||
def __init__(self, name: str, **kwargs: Any):
|
def __init__(self, name: str, **kwargs: Any):
|
||||||
http_router.Router.__init__(self, **kwargs)
|
http_router.Router.__init__(self, **kwargs)
|
||||||
self.name: str = name
|
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:
|
def __repr__(self) -> str:
|
||||||
|
|
Loading…
Reference in a new issue