Добавление страницы ошибки 500 в Drupal с помощью Event Subscriber
Часто мы сталкиваемся со страницей ошибки 500, когда Drupal, сервисы или другие сайты недоступны. Когда мы видим страницу ошибки 500 (или 501-504). В Drupal мы используем исключения (Exceptions), чтобы проверить, был ли выполнен критический код. Если возникает ошибка, например, при HTTP-запросе к другому сайту, Drupal покажет это сообщение: "На сайте произошла неожиданная ошибка. Пожалуйста, попробуйте позже":

Плохо, если на вашем сайте появляется WSOD (white screen of death — белый экран смерти), поэтому давайте улучшим ситуацию и будем показывать стилизованную HTML-страницу вместо этого.
У меня есть стилизованная страница 500.html
в корне моего сайта, по соображениям производительности. Мы можем использовать стилизованную Drupal-страницу для ошибки 500, но я также буду использовать эту же страницу для ошибок 503/504 в Apache/Nginx, и так проще хранить такую страницу в одном месте, в виде отдельного HTML-файла.

Теперь нам нужно добавить код в наш кастомный модуль DrupalBook Custom (drupalbook_custom
). В drupalbook_custom.services.yml
нужно добавить Event Subscriber:
services:
drupalbook_custom.exception_subscriber:
class: Drupal\drupalbook_custom\EventSubscriber\SeoExceptionSubscriber
arguments: ['@config.factory']
tags:
- { name: event_subscriber, priority: -250 }
Вот код для drupalbook_custom/src/EventSubscriber/SeoExceptionSubscriber
:
<?php
namespace Drupal\drupalbook_custom\EventSubscriber;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Utility\Error;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Заменяет обработчик ошибок Drupal\Core\EventSubscriber\FinalExceptionSubscriber для ошибки 500.
*/
class SeoExceptionSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Конфиги для настроек.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected ConfigFactoryInterface $configFactory;
public function __construct(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
/**
* Обрабатывает любое необработанное исключение и возвращает кастомный HTML-ответ.
*/
public function onException(ExceptionEvent $event): void {
// Показывать стандартный стек ошибок Drupal, если сайт в режиме VERBOSE.
if ($this->isErrorLevelVerbose()) {
return;
}
$exception = $event->getThrowable();
// Базовое сообщение (можно расширить для verbose режима).
$error = Error::decodeException($exception);
$message = new FormattableMarkup('@message', [
'@message' => $error['!message'] ?? $this->t('На сайте произошла неожиданная ошибка.'),
]);
$html = $this->buildHtml((string) $message);
$status = $exception instanceof HttpExceptionInterface
? $exception->getStatusCode()
: Response::HTTP_INTERNAL_SERVER_ERROR;
$response = new Response($html, $status, ['Content-Type' => 'text/html']);
// Сохраняем дополнительные заголовки, например Retry-After, если они есть.
if ($exception instanceof HttpExceptionInterface) {
$response->headers->add($exception->getHeaders());
}
// Отправляем ответ и останавливаем дальнейших подписчиков (включая core).
$event->setResponse($response);
$event->stopPropagation();
}
/**
* Читает web/500.html и подставляет токен {{ message }}, если он есть.
*/
protected function buildHtml(string $message): string {
$template = DRUPAL_ROOT . '/500.html';
if (is_readable($template)) {
$html = file_get_contents($template);
return str_replace('{{ message }}', Markup::create($message), $html);
}
// Безопасный вариант, если шаблон отсутствует.
return '<html><head><title>500</title></head><body>'
. Markup::create($message)
. '</body></html>';
}
/**
* TRUE, если уровень ошибок установлен в “Verbose”.
*
* Аналогично \Drupal\Core\EventSubscriber\FinalExceptionSubscriber::isErrorLevelVerbose().
*/
protected function isErrorLevelVerbose(): bool {
return $this->configFactory
->get('system.logging')
->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
// Приоритет -250 — выполняется непосредственно перед core FinalExceptionSubscriber (-256).
$events[KernelEvents::EXCEPTION][] = ['onException', -250];
return $events;
}
}
Этот класс-подписчик SeoExceptionSubscriber
перехватывает все необработанные исключения в Drupal. Он проверяет, включен ли на вашем сайте подробный режим вывода ошибок (verbose error reporting), и если да — позволяет Drupal отображать стандартные подробные сообщения об ошибках. Если же verbose-режим выключен (что обычно в production), класс ловит исключение и готовит дружественное для пользователя сообщение об ошибке.
В частности, он читает ваш файл 500.html
, размещённый в корне установки Drupal. Ошибка динамически вставляется в HTML-контент страницы, заменяя плейсхолдер {{ message }}
, чтобы показывать информативную и стилизованную страницу ошибки.
Кроме того, этот подписчик явно останавливает стандартный обработчик ошибок Drupal, чтобы не позволить ему перезаписать вашу кастомную HTML-страницу. Благодаря приоритету -250
обработчик выполняется сразу перед core-обработчиком, эффективно переопределяя стандартное поведение Drupal.
Для локальной среды вы можете прописать настройки для отображения ошибок вместо страницы 500.
settings.php:
$config['system.logging']['error_level'] = 'verbose';
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
Иногда Drupal бывает недоступен, тогда потребуется настроить дополнительные параметры для вашего веб-сервера или облака.
Добавление страницы ошибки 500 в Apache
Чтобы отдавать ваш существующий файл 500.html
из корня сайта при ошибках HTTP 500–504, настройте Apache соответствующим образом. Есть два простых способа это сделать:
1. Использование конфигурации виртуального хоста Apache (рекомендуется)
Отредактируйте файл конфигурации виртуального хоста (обычно находится в /etc/apache2/sites-available/your-site.conf
) и добавьте следующие директивы внутри блока <VirtualHost>
:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Затем перезагрузите Apache, чтобы применить изменения:
sudo systemctl reload apache2
2. Использование файла .htaccess
Если вы предпочитаете использовать .htaccess
(находится в корне сайта), просто добавьте эти строки:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Убедитесь, что файл 500.html
находится в корне вашего сайта и доступен для чтения Apache. После применения настроек Apache будет показывать вашу стилизованную HTML-страницу ошибки для статусов 500–504.
Добавление страницы ошибки 500 в Nginx
Чтобы настроить Nginx на отдачу вашей кастомной страницы 500.html
из корня сайта при ошибках HTTP 500–504, измените конфиг Nginx следующим образом:
Откройте файл конфигурации сайта для Nginx (обычно /etc/nginx/sites-available/your-site.conf
) и добавьте эти строки в блок server {}
:
error_page 500 501 502 503 504 /500.html;
location = /500.html {
root /var/www/html;
internal;
}
Убедитесь, что путь (/var/www/html
) корректно указывает на корень вашего сайта, где лежит 500.html
. После редактирования перезагрузите Nginx, чтобы применить изменения:
sudo nginx -s reload
Теперь Nginx будет стабильно показывать вашу HTML-страницу ошибки для всех статусов 500–504.