/
liquid-g
/
liquid-code
Обзор
Документация
Войти
/
liquid-g
/
liquid-code
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/liquidcode/middleware/error_handling.py
136 строк
5 KB
User
ci: настроен Black и flake8 для автоматического форматирования кода
04 июл 2026, 08:14
04 июл 2026, 08:14
4778ed6
Код
Авторство
О чём код?
""" Middleware для обработки ошибок с поддержкой HTML и JSON. """ import logging import traceback from typing import Callable, Dict from .base import Middleware from ..contracts import Request, Response from ..http import HttpResponse from ..template import TemplateManager logger = logging.getLogger(__name__) class ErrorHandlingMiddleware(Middleware): """ Перехватывает исключения и возвращает кастомные страницы ошибок в HTML или JSON в зависимости от заголовка Accept. """ def __init__(self, container=None, prefer_json: bool = False, debug: bool = False): self.container = container self.prefer_json = prefer_json self.debug = debug self._template_manager = None if container: try: self._template_manager = container.get(TemplateManager) logger.info( "ErrorHandlingMiddleware: TemplateManager получен из контейнера" ) except Exception as e: logger.warning( f"ErrorHandlingMiddleware: не удалось получить TemplateManager: {e}" ) def __call__( self, request: Request, call_next: Callable[[Request], Response] ) -> Response: try: return call_next(request) except Exception as e: status = getattr(e, "status_code", 500) if self.debug: logger.exception(f"Ошибка: {request.get_method()} {request.get_path()}") else: logger.error( f"Ошибка: {request.get_method()} {request.get_path()} — {e}" ) return self._error_response(request, status, e) def _parse_accept(self, accept_header: str) -> Dict[str, float]: """ Парсит Accept заголовок с учётом q-значений. Возвращает словарь {mime_type: weight}. """ result = {} if not accept_header: return result for part in accept_header.split(","): part = part.strip() if ";q=" in part: mime_type, q_part = part.split(";q=") try: q_value = float(q_part.strip()) except ValueError: q_value = 1.0 result[mime_type.strip()] = q_value else: result[part] = 1.0 return result def _error_response( self, request: Request, status: int, exception: Exception ) -> Response: accept = request.get_headers().get("Accept", "text/html") q_values = self._parse_accept(accept) # Определяем предпочтительный формат is_json = self.prefer_json or q_values.get( "application/json", 0 ) > q_values.get("text/html", 0) if is_json: content = { "error": str(exception), "status": status, "path": request.get_path(), } if self.debug and status >= 500: content["traceback"] = traceback.format_exc().split("\n") return HttpResponse( content=content, status=status, headers={"Content-Type": "application/json"}, ) # HTML-ответ if self._template_manager: try: html = self._template_manager.render( f"errors/{status}.html.j2", status=status, error=str(exception), path=request.get_path(), method=request.get_method(), ) return HttpResponse( html.encode("utf-8"), status=status, headers={"Content-Type": "text/html"}, ) except Exception as e: logger.warning(f"Не удалось отрендерить шаблон ошибки: {e}") # Встроенный fallback HTML html = f""" <!DOCTYPE html> <html> <head><title>Ошибка {status}</title></head> <body> <h1>Ошибка {status}</h1> <p>{exception}</p> <p>Метод: {request.get_method()}, Путь: {request.get_path()}</p> </body> </html> """ return HttpResponse( html.encode("utf-8"), status=status, headers={"Content-Type": "text/html"}, )