logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动

使用事件订阅器在 Drupal 中添加 500 错误页面

02/09/2025, by Ivan

Menu

我们经常会遇到 500 错误页面,尤其当 Drupal、服务或其他站点不可用时。出现 500(或 501-504)错误页面时,Drupal 会通过异常(Exceptions)来处理关键代码错误。例如在向其他网站发起 HTTP 请求时发生错误,Drupal 会显示类似如下的错误信息:“网站遇到意外错误,请稍后再试”:

Drupal 默认 500 错误页面
Drupal 默认 500 错误页面

在你的网站上出现 WSOD(白屏死机)并不好,因此我们来优化这个页面,改为展示一个有样式的 HTML 页面。

我在站点根目录放置了一个已经设计好的 500.html 页面,这样做是为了性能原因。虽然我们也可以使用 Drupal 页面作为 500 错误页,但我打算将该页面同时用于 Apache/Nginx 的 503/504 错误,因此将其保存在一个统一位置的单一 HTML 文件中更为便捷。

500 HTML 页面
500 HTML 错误页面

现在我们需要在自定义模块 DrupalBook Custom(drupalbook_custom)中添加代码。在 drupalbook_custom.services.yml 中注册事件订阅器:

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;

class SeoExceptionSubscriber implements EventSubscriberInterface {

  use StringTranslationTrait;

  protected ConfigFactoryInterface $configFactory;

  public function __construct(ConfigFactoryInterface $config_factory) {
    $this->configFactory = $config_factory;
  }

  public function onException(ExceptionEvent $event): void {
    if ($this->isErrorLevelVerbose()) {
      return;
    }

    $exception = $event->getThrowable();

    $error = Error::decodeException($exception);
    $message = new FormattableMarkup('@message', [
      '@message' => $error['!message'] ?? $this->t('The website encountered an unexpected error.'),
    ]);

    $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']);

    if ($exception instanceof HttpExceptionInterface) {
      $response->headers->add($exception->getHeaders());
    }

    $event->setResponse($response);
    $event->stopPropagation();
  }

  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>';
  }

  protected function isErrorLevelVerbose(): bool {
    return $this->configFactory
      ->get('system.logging')
      ->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE;
  }

  public static function getSubscribedEvents(): array {
    $events[KernelEvents::EXCEPTION][] = ['onException', -250];
    return $events;
  }

}

这个订阅器类 SeoExceptionSubscriber 会拦截 Drupal 中所有未捕获的异常。如果站点设置为详细(verbose)错误报告模式,它将允许 Drupal 显示默认的详细错误信息。而在非 verbose 模式下(如生产环境),它会捕获异常并显示用户友好的错误页面。

具体来说,它会读取 Drupal 根目录下的 500.html 页面,将错误信息插入 {{ message }} 占位符,并返回完整的 HTML 页面作为响应。

此外,订阅器还会阻止 Drupal 默认的错误处理器进一步执行,确保不会覆盖你自定义的错误页。通过设置优先级为 -250,该订阅器的执行时机刚好早于 Drupal 核心的默认处理器(优先级 -256)。

如果你希望在本地环境中显示详细错误信息,而不是 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 无法访问,你可能还需要为 Web 服务器或云环境设置额外的错误页面配置。

在 Apache 中添加 500 错误页面

要在 Apache 中为 HTTP 500–504 错误显示自定义 500.html 页面,你可以如下配置:

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 将在出现 500-504 错误时显示该页面。

在 Nginx 中添加 500 错误页面

在 Nginx 中配置以使用自定义 500.html 页面,请编辑 /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 会在 500–504 错误时统一显示该自定义 HTML 页面。