logo

Дополнительные типы блоков (EBT) — новый опыт конструктора страниц❗

Дополнительные типы блоков (EBT) — стилизованные, настраиваемые типы блоков: слайдшоу, вкладки, карточки, аккордеоны и многие другие. Встроенные настройки для фона, DOM Box, плагины Javascript.

Демо EBT модули Скачать EBT модули

❗Дополнительные типы параграфов (EPT) — новый опыт работы с параграфами

Дополнительные типы параграфов (EPT) — набор модулей, основанный на аналогичных параграфах.

Демо EPT модули Скачать EPT модули

Scroll
11/06/2025, by Ivan

Menu

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

Drupal default 500 error page
Drupal default 500 error page

Плохо, если на вашем сайте появляется WSOD (white screen of death — белый экран смерти), поэтому давайте улучшим ситуацию и будем показывать стилизованную HTML-страницу вместо этого.

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

500 HTML page
500 HTML error page

Теперь нам нужно добавить код в наш кастомный модуль 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.