fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
@@ -5,32 +5,52 @@ declare(strict_types=1);
|
||||
namespace App\Framework\ExceptionHandling\Renderers;
|
||||
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
|
||||
use App\Framework\Http\Response;
|
||||
use App\Framework\ExceptionHandling\ErrorRenderer;
|
||||
use App\Framework\ExceptionHandling\Translation\ExceptionMessageTranslator;
|
||||
use App\Framework\ExceptionHandling\ValueObjects\StackTrace;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\HttpResponse;
|
||||
use App\Framework\Http\Status;
|
||||
use App\Framework\Meta\MetaData;
|
||||
use App\Framework\SyntaxHighlighter\FileHighlighter;
|
||||
use App\Framework\View\Engine;
|
||||
use App\Framework\View\ProcessingMode;
|
||||
use App\Framework\View\RenderContext;
|
||||
|
||||
/**
|
||||
* HTTP Response factory for API and HTML error pages
|
||||
* HTTP Response renderer for API and HTML error pages
|
||||
*
|
||||
* Extracts Response generation logic from ErrorKernel for reuse
|
||||
* in middleware recovery patterns.
|
||||
* Uses Template System for HTML rendering and creates JSON responses for API requests.
|
||||
* Extracts Response generation logic from ErrorKernel for reuse in middleware recovery patterns.
|
||||
*/
|
||||
final readonly class ResponseErrorRenderer
|
||||
final readonly class ResponseErrorRenderer implements ErrorRenderer
|
||||
{
|
||||
public function __construct(
|
||||
private bool $isDebugMode = false
|
||||
private Engine $engine,
|
||||
private bool $isDebugMode = false,
|
||||
private ?ExceptionMessageTranslator $messageTranslator = null
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create HTTP Response from exception
|
||||
* Check if this renderer can handle the exception
|
||||
*/
|
||||
public function canRender(\Throwable $exception): bool
|
||||
{
|
||||
// Can render any exception in HTTP context
|
||||
return PHP_SAPI !== 'cli';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render exception to HTTP Response
|
||||
*
|
||||
* @param \Throwable $exception Exception to render
|
||||
* @param ExceptionContextProvider|null $contextProvider Optional context provider
|
||||
* @return Response HTTP Response object
|
||||
* @return HttpResponse HTTP Response object
|
||||
*/
|
||||
public function createResponse(
|
||||
public function render(
|
||||
\Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider = null
|
||||
): Response {
|
||||
): HttpResponse {
|
||||
// Determine if API or HTML response needed
|
||||
$isApiRequest = $this->isApiRequest();
|
||||
|
||||
@@ -47,24 +67,38 @@ final readonly class ResponseErrorRenderer
|
||||
private function createApiResponse(
|
||||
\Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider
|
||||
): Response {
|
||||
): HttpResponse {
|
||||
$statusCode = $this->getHttpStatusCode($exception);
|
||||
|
||||
// Get user-friendly message if translator is available
|
||||
$context = $contextProvider?->get($exception);
|
||||
$userMessage = $this->messageTranslator?->translate($exception, $context)
|
||||
?? new \App\Framework\ExceptionHandling\Translation\UserFriendlyMessage(
|
||||
message: $this->isDebugMode
|
||||
? $exception->getMessage()
|
||||
: 'An error occurred while processing your request.'
|
||||
);
|
||||
|
||||
$errorData = [
|
||||
'error' => [
|
||||
'message' => $this->isDebugMode
|
||||
? $exception->getMessage()
|
||||
: 'An error occurred while processing your request.',
|
||||
'type' => $this->isDebugMode ? get_class($exception) : 'ServerError',
|
||||
'message' => $userMessage->message,
|
||||
'type' => $this->isDebugMode ? $this->getShortClassName(get_class($exception)) : 'ServerError',
|
||||
'code' => $exception->getCode(),
|
||||
]
|
||||
];
|
||||
|
||||
if ($userMessage->title !== null) {
|
||||
$errorData['error']['title'] = $userMessage->title;
|
||||
}
|
||||
if ($userMessage->helpText !== null) {
|
||||
$errorData['error']['help'] = $userMessage->helpText;
|
||||
}
|
||||
|
||||
// Add debug information if enabled
|
||||
if ($this->isDebugMode) {
|
||||
$errorData['error']['file'] = $exception->getFile();
|
||||
$errorData['error']['line'] = $exception->getLine();
|
||||
$errorData['error']['trace'] = $this->formatStackTrace($exception);
|
||||
$errorData['error']['trace'] = StackTrace::fromThrowable($exception)->toArray();
|
||||
|
||||
// Add context from WeakMap if available
|
||||
if ($contextProvider !== null) {
|
||||
@@ -82,53 +116,161 @@ final readonly class ResponseErrorRenderer
|
||||
|
||||
$body = json_encode($errorData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return new Response(
|
||||
return new HttpResponse(
|
||||
status: Status::from($statusCode),
|
||||
body: $body,
|
||||
headers: [
|
||||
headers: new Headers([
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTML error page response
|
||||
* Create HTML error page response using Template System
|
||||
*/
|
||||
private function createHtmlResponse(
|
||||
\Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider
|
||||
): Response {
|
||||
): HttpResponse {
|
||||
$statusCode = $this->getHttpStatusCode($exception);
|
||||
|
||||
$html = $this->generateErrorHtml(
|
||||
$exception,
|
||||
$contextProvider,
|
||||
$statusCode
|
||||
);
|
||||
// Try to render using template system
|
||||
$html = $this->renderWithTemplate($exception, $contextProvider, $statusCode);
|
||||
|
||||
return new Response(
|
||||
// Fallback to simple HTML if template rendering fails
|
||||
if ($html === null) {
|
||||
$html = $this->generateFallbackHtml($exception, $contextProvider, $statusCode);
|
||||
}
|
||||
|
||||
return new HttpResponse(
|
||||
status: Status::from($statusCode),
|
||||
body: $html,
|
||||
headers: [
|
||||
headers: new Headers([
|
||||
'Content-Type' => 'text/html; charset=utf-8',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTML error page
|
||||
* Render error page using Template System
|
||||
*
|
||||
* @return string|null Rendered HTML or null if template not found or rendering failed
|
||||
*/
|
||||
private function generateErrorHtml(
|
||||
private function renderWithTemplate(
|
||||
\Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider,
|
||||
int $statusCode
|
||||
): ?string {
|
||||
try {
|
||||
// Determine template name based on status code
|
||||
$templateName = $this->getTemplateName($statusCode);
|
||||
|
||||
// Prepare template data
|
||||
$templateData = $this->prepareTemplateData($exception, $contextProvider, $statusCode);
|
||||
|
||||
// Create RenderContext
|
||||
$renderContext = new RenderContext(
|
||||
template: $templateName,
|
||||
metaData: new MetaData($this->getErrorTitle($statusCode)),
|
||||
data: $templateData,
|
||||
processingMode: ProcessingMode::FULL
|
||||
);
|
||||
|
||||
// Render template
|
||||
return $this->engine->render($renderContext);
|
||||
} catch (\Throwable $e) {
|
||||
// Template not found or rendering failed - log error and return null for fallback
|
||||
error_log(sprintf(
|
||||
'Failed to render error template "%s" for status %d: %s in %s:%d',
|
||||
$templateName ?? 'unknown',
|
||||
$statusCode,
|
||||
$e->getMessage(),
|
||||
$e->getFile(),
|
||||
$e->getLine()
|
||||
));
|
||||
|
||||
// Return null to trigger fallback HTML generation
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template name based on status code
|
||||
*/
|
||||
private function getTemplateName(int $statusCode): string
|
||||
{
|
||||
return match ($statusCode) {
|
||||
404 => 'errors/404',
|
||||
500 => 'errors/500',
|
||||
default => 'errors/error',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare template data for rendering
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function prepareTemplateData(
|
||||
\Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider,
|
||||
int $statusCode
|
||||
): array {
|
||||
$data = [
|
||||
'statusCode' => $statusCode,
|
||||
'title' => $this->getErrorTitle($statusCode),
|
||||
'message' => $this->isDebugMode
|
||||
? $exception->getMessage()
|
||||
: 'An error occurred while processing your request.',
|
||||
'exceptionClass' => $this->getShortClassName(get_class($exception)),
|
||||
'isDebugMode' => $this->isDebugMode,
|
||||
];
|
||||
|
||||
// Add debug information if enabled
|
||||
if ($this->isDebugMode) {
|
||||
$stackTrace = StackTrace::fromThrowable($exception);
|
||||
$data['debug'] = [
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $stackTrace->formatForHtml(),
|
||||
];
|
||||
|
||||
// Add context from WeakMap if available
|
||||
if ($contextProvider !== null) {
|
||||
$context = $contextProvider->get($exception);
|
||||
if ($context !== null) {
|
||||
$data['context'] = [
|
||||
'operation' => $context->operation,
|
||||
'component' => $context->component,
|
||||
'request_id' => $context->requestId,
|
||||
'occurred_at' => $context->occurredAt?->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate fallback HTML error page (when template not found)
|
||||
*/
|
||||
private function generateFallbackHtml(
|
||||
\Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider,
|
||||
int $statusCode
|
||||
): string {
|
||||
$title = $this->getErrorTitle($statusCode);
|
||||
$message = $this->isDebugMode
|
||||
? $exception->getMessage()
|
||||
: 'An error occurred while processing your request.';
|
||||
// HTML-encode all variables for security
|
||||
$title = htmlspecialchars($this->getErrorTitle($statusCode), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$message = htmlspecialchars(
|
||||
$this->isDebugMode
|
||||
? $exception->getMessage()
|
||||
: 'An error occurred while processing your request.',
|
||||
ENT_QUOTES | ENT_HTML5,
|
||||
'UTF-8'
|
||||
);
|
||||
|
||||
$debugInfo = '';
|
||||
if ($this->isDebugMode) {
|
||||
@@ -211,32 +353,42 @@ HTML;
|
||||
\Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider
|
||||
): string {
|
||||
$exceptionClass = get_class($exception);
|
||||
$file = $exception->getFile();
|
||||
// HTML-encode all debug information for security
|
||||
$exceptionClass = htmlspecialchars($this->getShortClassName(get_class($exception)), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$file = htmlspecialchars($exception->getFile(), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$line = $exception->getLine();
|
||||
$trace = $this->formatStackTrace($exception);
|
||||
$stackTrace = StackTrace::fromThrowable($exception);
|
||||
$trace = $stackTrace->formatForHtml(); // Already HTML-encoded in formatForHtml
|
||||
|
||||
$contextHtml = '';
|
||||
if ($contextProvider !== null) {
|
||||
$context = $contextProvider->get($exception);
|
||||
if ($context !== null) {
|
||||
$operation = htmlspecialchars($context->operation ?? '', ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$component = htmlspecialchars($context->component ?? '', ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$requestId = htmlspecialchars($context->requestId ?? '', ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$occurredAt = $context->occurredAt?->format('Y-m-d H:i:s') ?? '';
|
||||
|
||||
$contextHtml = <<<HTML
|
||||
<div class="context-item">
|
||||
<span class="context-label">Operation:</span> {$context->operation}
|
||||
<span class="context-label">Operation:</span> {$operation}
|
||||
</div>
|
||||
<div class="context-item">
|
||||
<span class="context-label">Component:</span> {$context->component}
|
||||
<span class="context-label">Component:</span> {$component}
|
||||
</div>
|
||||
<div class="context-item">
|
||||
<span class="context-label">Request ID:</span> {$context->requestId}
|
||||
<span class="context-label">Request ID:</span> {$requestId}
|
||||
</div>
|
||||
<div class="context-item">
|
||||
<span class="context-label">Occurred At:</span> {$context->occurredAt?->format('Y-m-d H:i:s')}
|
||||
<span class="context-label">Occurred At:</span> {$occurredAt}
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
||||
// Code-Ausschnitt für die Exception-Zeile
|
||||
$codeSnippet = $this->getCodeSnippet($exception->getFile(), $line);
|
||||
|
||||
return <<<HTML
|
||||
<div class="debug-info">
|
||||
<h3>Debug Information</h3>
|
||||
@@ -247,6 +399,7 @@ HTML;
|
||||
<span class="context-label">File:</span> {$file}:{$line}
|
||||
</div>
|
||||
{$contextHtml}
|
||||
{$codeSnippet}
|
||||
<h4>Stack Trace:</h4>
|
||||
<pre>{$trace}</pre>
|
||||
</div>
|
||||
@@ -319,18 +472,35 @@ HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format stack trace for display
|
||||
* Gibt Klassennamen ohne Namespace zurück
|
||||
*/
|
||||
private function formatStackTrace(\Throwable $exception): string
|
||||
private function getShortClassName(string $fullClassName): string
|
||||
{
|
||||
$trace = $exception->getTraceAsString();
|
||||
$parts = explode('\\', $fullClassName);
|
||||
return end($parts);
|
||||
}
|
||||
|
||||
// Limit trace depth in production
|
||||
if (!$this->isDebugMode) {
|
||||
$lines = explode("\n", $trace);
|
||||
$trace = implode("\n", array_slice($lines, 0, 5));
|
||||
/**
|
||||
* Gibt Code-Ausschnitt für die Exception-Zeile zurück
|
||||
*/
|
||||
private function getCodeSnippet(string $file, int $line): string
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return htmlspecialchars($trace, ENT_QUOTES, 'UTF-8');
|
||||
try {
|
||||
$fileHighlighter = new FileHighlighter();
|
||||
$startLine = max(0, $line - 5); // 5 Zeilen vor der Exception
|
||||
$range = 11; // 5 vor + 1 Exception + 5 nach = 11 Zeilen
|
||||
|
||||
// FileHighlighter gibt bereits HTML mit Syntax-Highlighting zurück
|
||||
$highlightedCode = $fileHighlighter($file, $startLine, $range, $line);
|
||||
|
||||
return '<h4>Code Context:</h4>' . $highlightedCode;
|
||||
} catch (\Throwable $e) {
|
||||
// Bei Fehler beim Lesen der Datei, ignoriere Code-Ausschnitt
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user