2023-10-23 02:51:39 +01:00
|
|
|
from functools import wraps
|
|
|
|
|
|
|
|
import msgspec
|
|
|
|
from sanic import errorpages
|
|
|
|
from sanic.exceptions import SanicException
|
|
|
|
from sanic.log import logger
|
|
|
|
from sanic.response import raw, redirect
|
|
|
|
|
|
|
|
from cista import auth
|
|
|
|
from cista.protocol import ErrorMsg
|
|
|
|
|
|
|
|
|
|
|
|
def asend(ws, msg):
|
|
|
|
"""Send JSON message or bytes to a websocket"""
|
|
|
|
return ws.send(msg if isinstance(msg, bytes) else msgspec.json.encode(msg).decode())
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-23 02:51:39 +01:00
|
|
|
def jres(data, **kwargs):
|
|
|
|
"""JSON Sanic response, using msgspec encoding"""
|
|
|
|
return raw(msgspec.json.encode(data), content_type="application/json", **kwargs)
|
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-23 02:51:39 +01:00
|
|
|
async def handle_sanic_exception(request, e):
|
|
|
|
context, code = {}, 500
|
|
|
|
message = str(e)
|
|
|
|
if isinstance(e, SanicException):
|
|
|
|
context = e.context or {}
|
|
|
|
code = e.status_code
|
|
|
|
if not message or not request.app.debug and code == 500:
|
|
|
|
message = "Internal Server Error"
|
|
|
|
message = f"⚠️ {message}" if code < 500 else f"🛑 {message}"
|
|
|
|
# Non-browsers get JSON errors
|
|
|
|
if "text/html" not in request.headers.accept:
|
2023-10-26 15:18:59 +01:00
|
|
|
return jres(
|
2023-11-08 20:38:40 +00:00
|
|
|
ErrorMsg({"code": code, "message": message, **context}),
|
|
|
|
status=code,
|
2023-10-26 15:18:59 +01:00
|
|
|
)
|
2023-10-23 02:51:39 +01:00
|
|
|
# Redirections flash the error message via cookies
|
|
|
|
if "redirect" in context:
|
|
|
|
res = redirect(context["redirect"])
|
|
|
|
res.cookies.add_cookie("message", message, max_age=5)
|
|
|
|
return res
|
|
|
|
# Otherwise use Sanic's default error page
|
2023-11-17 01:10:18 +00:00
|
|
|
res = errorpages.HTMLRenderer(request, e, debug=request.app.debug).full()
|
|
|
|
res.status = status # Unfortunately Sanic <23.12 doesn't set this
|
|
|
|
return res
|
2023-10-23 02:51:39 +01:00
|
|
|
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-23 02:51:39 +01:00
|
|
|
def websocket_wrapper(handler):
|
|
|
|
"""Decorator for websocket handlers that catches exceptions and sends them back to the client"""
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-23 02:51:39 +01:00
|
|
|
@wraps(handler)
|
|
|
|
async def wrapper(request, ws, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
auth.verify(request)
|
|
|
|
await handler(request, ws, *args, **kwargs)
|
|
|
|
except Exception as e:
|
|
|
|
context, code, message = {}, 500, str(e) or "Internal Server Error"
|
|
|
|
if isinstance(e, SanicException):
|
|
|
|
context = e.context or {}
|
|
|
|
code = e.status_code
|
|
|
|
message = f"⚠️ {message}" if code < 500 else f"🛑 {message}"
|
|
|
|
await asend(ws, ErrorMsg({"code": code, "message": message, **context}))
|
2023-11-17 01:10:18 +00:00
|
|
|
if not getattr(e, "quiet", False):
|
|
|
|
logger.exception(f"{code} {e!r}")
|
2023-10-23 02:51:39 +01:00
|
|
|
raise
|
2023-10-26 15:18:59 +01:00
|
|
|
|
2023-10-23 02:51:39 +01:00
|
|
|
return wrapper
|