Обработка ошибок HTTP

105

В дополнение к универсальной странице ошибки (которую мы рассмотрели в предыдущей статье) можно сконфигурировать страницы ошибок, которые будут применяться для специфичных кодов состояния HTTP, таких как 404 (возвращается, когда не удается найти целевой URL). В этой статье мы не собираемся вдаваться в подробности кодов состояния HTTP. Полный их список доступен по адресу Список кодов состояния HTTP.

Ответственность за выдачу страниц ошибок делится между ASP.NET и IIS на основе вида запрашиваемого URL. Таким образом, например, если пользователь запрашивает файл веб-формы aspx, такой как "/DoesNotExist.aspx", отвечать за генерацию ошибок будет среда ASP.NET, т.к. сервер IIS передает все запросы к веб-формам в ASP.NET Framework.

Если пользователь запрашивает URL, который не обслуживается ASP.NET, наподобие "/DoesNotExit.html", генерировать страницу ошибки будет сервер IIS. Обычно мы стремимся обращаться с ошибками в согласованной манере, так что это разделение ответственности просто означает необходимость применения желаемой политики в двух местах внутри файла Web.config. Мы хотим подчеркнуть отличающиеся ответственности, поэтому создадим отдельные страницы ошибок, которые позволят понять, когда ошибка поступает из IIS, а когда из ASP.NET.

Работа с кодами состояния HTTP, поступающими из ASP.NET

Сначала мы рассмотрим поддержку кодов состояния HTTP в ASP.NET. Для этого мы создали новый файл по имени NotFoundASP.html с содержимым, приведенным в примере ниже:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>body { font-family: sans-serif;}</style>
</head>
<body>
    <h1>Ошибка 404 - страница не найдена</h1>
    <p>Среда ASP.NET не нашла указанную страницу.</p>
    <p><a href="/">Перейти на главную?</a></p>
</body>
</html>

Этот файл содержит сообщение, отображаемое пользователю, и ссылку на корневой URL приложения. В данном примере мы перенаправляем пользователя на статический URL, ассоциированный с приложением. Файл NotFoundASP.html регистрируется в качестве обработчика для ошибок 404 внутри Web.config, как показано в примере ниже:

...
<customErrors mode="On" defaultRedirect="/DynamicFailure.aspx">
      <error statusCode="404" redirect="/NotFoundASP.html"/>
</customErrors>
...

В элементе error, помещенном внутрь добавленного ранее элемента customErrors определены атрибуты, которые кратко описаны в таблице ниже:

Атрибуты, определенные в элементе customErrors/error
Имя Описание
statusCode

Код состояния HTTP, к которому относится определение

redirect

Указатель URL, который будет использоваться, когда возникает ошибка, представленная атрибутом statusCode

Можно определять несколько элементов error, по одному для каждого кода состояния, который необходимо поддерживать. В примере ниже было указано, что файл NotFoundASP.html будет использоваться в ситуации, когда клиенту должен быть возвращен код состояния 404.

Обратите внимание на формулировку: код состояния должен быть возвращен клиенту. Определяя специальные обработчики ошибок, мы предотвращаем отправку в браузер кодов состояния HTTP. Взамен браузер получает перенаправление на страницу ошибки, которое возвращает код 200, указывающий на успешность запроса.

Это делается потому, что в случае возврата из сервера кода состояния вроде 404 многие браузеры будут отображать собственные сообщения об ошибках, не позволяя видеть специальное сообщение. Единственный недостаток такого подхода связан с тем, что клиенту совершенно не известно о том, что запрос привел к возникновению ошибки, поскольку он видит только успешные запросы, а это может оказаться проблемой, когда клиентом не является браузер, делающий запросы к обычному HTML-контенту.

Чтобы увидеть специальную страницу ошибки, необходимо запросить URL для файла с расширением, которое обслуживается ASP.NET, таким как aspx или ashx. На рисунке ниже показано сообщение об ошибке, полученное за счет запрашивания URL вида "/DoesNotExist.aspx":

Специальная страница ошибки, сгенерированная при запросе URL для несуществующего файла ASP.NET

Отметим, что URL страницы с ошибкой содержит описанный в предыдущей статье параметр aspxerrorpath строки запроса.

Работа с кодами состояния HTTP, поступающими из IIS

Среда ASP.NET генерирует страницы ошибок для запросов, которые относятся к обслуживаемым ею типам файлов, а сервер IIS заботится обо всех остальных. Чтобы продемонстрировать это, мы добавили в проект примера новый файл по имени NotFoundIIS.html с применением шаблона элемента HTML Page. Содержимое этого файла представлено в примере ниже:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>
        body { font-family: sans-serif; }
    </style>
</head>
<body>
    <h1>Ошибка 404 - страница не найдена</h1>
    <p>Сервер IIS не нашел указанную страницу.</p>
    <p><a href="/">Перейти на главную?</a></p>
</body>
</html>

В сущности это тот же самый контент, который использовался в примере с ASP.NET, но с текстом, скорректированным для отражения того, что страница ошибки сгенерирована сервером IIS. Специальные страницы ошибок IIS тоже регистрируются в файле Web.config, однако в другом разделе, как показано в примере ниже:

<?xml version="1.0"?>
<configuration>

  <system.web>
    ...
  </system.web>

  <system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="404"/>
      <error statusCode="404" responseMode="Redirect" path="/NotFoundIIS.html"/>
    </httpErrors>
  </system.webServer>

</configuration>

Специальная страница ошибки устанавливается с помощью элемента httpErrors, который находится в разделе system.webServer файла Web.config. В элементе httpErrors определены атрибуты, описанные в таблице ниже:

Атрибуты, определенные в элементе system.webServer/httpErrors
Имя Описание
defaultPath

Указывает путь к универсальной странице ошибки, но вы не сможете установить этот атрибут до тех пор, пока явно не разблокируете этот раздел конфигурации с помощью диспетчера IIS. Как следствие, вы не должны полагаться на этот атрибут

defaultResponseMode

Указывает, как контент страницы ошибки возвращается браузеру. Значение Redirect приводит к отправке HTTP-перенаправления на страницу ошибки, значение ExecuteUPL предусматривает генерацию динамического ответа (такого как получаемый из веб-формы), а значение File обеспечивает загрузку страницы ошибки из файла. (Значение File применяется, если нужно использовать файл, не являющийся частью проекта.)

errorMode

Указывает, каким образом генерируются страницы ошибок. Значение Custom приводит к генерации страниц ошибок с применением значения defaultPath (если вы можете разблокировать его) или отдельных страниц, определенных элементами error (которые рассматриваются ниже).

Значение Detailed предусматривает генерацию сообщения об ошибке, которое включает подробные сведения для разработчиков, а значение DetailedLocalOnly обеспечивает генерацию специального сообщения об ошибке для удаленных запросов и детальное сообщение для локальных запросов

existingResponse

Указывает, каким образом IIS поступает с ошибками, генерируемыми средой ASP.NET Framework, но только в случае отключения специальных ошибок ASP.NET. Значение PassThrough приводит к передаче ошибок ASP.NET, значение Replace предусматривает генерацию в ответ ошибки IIS для замены ошибки ASP.NET, а значение Auto обеспечивает динамическое определение поведения на основе ответа ASP.NET

В примере выше для атрибута errorMode было определено значение, включающее специальные ошибки IIS. Установить универсальную страницу ошибки, как это делалось для ошибок ASP.NET, невозможно, потому что атрибут defaultPath заблокирован.

Страницы ошибок для отдельных кодов состояния задаются с помощью элемента error, в котором определены атрибуты, перечисленные в таблице ниже:

system.webServer/httpErrors/error
Имя Описание
path

Указывает URL или файл, который будет использоваться для генерации страницы ошибки

responseMode

Указывает способ генерации ошибки. Этот атрибут использует те же самые значения (и переопределяет их), что и атрибут defaultResponseMode элемента httpErrors

statusCode

Указывает код состояния HTTP, к которому относится элемент

subsStatusCode

Используя код подсостояния, чтобы можно было дополнительно конкретизировать причину ошибки. Эти коды не являются частью HTTP, и в своих проектах мы их не применяем

В примере выше был определен элемент error для кода состояния 404, который осуществляет перенаправление браузера на /NotFoundIIS.html. Обратите внимание, что непосредственно перед элементом error находится элемент remove:

<remove statusCode="404"/>
<error statusCode="404" responseMode="Redirect" path="/NotFoundIIS.html"/>

Набор элементов error поддерживается в виде коллекции, и внутри сервера IIS определен стандартный набор как часть его конфигурации. Если определить элемент error без предварительного использования remove для удаления стандартного определения, то возникнет ошибка. В элементе remove имеется атрибут statusCode, который позволяет указать, какой стандартный элемент error должен быть удален.

Чтобы увидеть специальную страницу ошибки, поступающую из IIS, необходимо запросить URL файла, имеющего расширение, которое не обслуживается ASP.NET, такое как html. На рисунке ниже показано сообщение об ошибке, полученное в результате запроса URL вида /DoesNotExist.html:

Генерация специальной страницы ошибки из IIS

В URL, по которому перенаправляется браузер, отсутствует информация из первоначального запроса, поэтому для настройки динамического контента страницы ошибки придется предпринять другой подход. В следующем разделе описано, как это делается.

Создание разделяемой динамической страницы ошибки

Итак, мы показали две разных статических HTML-страницы ошибок, которые позволяют различать ошибки, поступающие из APS.NET, и ошибки, поступающие из IIS. В реальных проектах это требуется редко, и для обоих разделов файла Web.config можно применять один и тот же URL.

Создание разделяемого динамического ответа требует несколько большей работы. Мы добавили в проект новую веб-форму по имени NotFoundShared.aspx, которая будет использоваться для демонстрации подхода. Содержимое этого файла приведено ниже:

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeBehind="NotFoundShared.aspx.cs" Inherits="ErrorHandling.NotFoundShared" %>

<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
    <title></title>
    <style>
        body { font-family: sans-serif;}
    </style>
</head>
<body>
    <h1>Извините</h1>
    <p>В приложении возникла какая-то ошибка и мы не смогли обработать ваш запрос.</p>
    <p>(Вы задали запрос <strong><span id="errorSrc" runat="server"></span></strong>
        для страницы: <span id="requestedURL" runat="server"></span>)</p>
    <p><a href="Default.aspx">Перейти на главную?</a></p>
</body>
</html>

Эта веб-форма содержит элементы <span>, предназначенные для информирования об источнике ошибки 404 и запрошенном URL. В примере ниже устанавливается контент этих элементов <span> в файле NotFoundShared.aspx.cs:

using System;

namespace ErrorHandling {
    public partial class NotFoundShared : System.Web.UI.Page {
        protected void Page_Load(object sender, EventArgs e) 
        {
            requestedURL.InnerText = Request["aspxerrorpath"] ?? Request.RawUrl;
            errorSrc.InnerText = Request["aspxerrorpath"] == null ? "IIS" : "ASP.NET";
        }
    }
}

Этот прием полагается на то, что ошибки, генерируемые ASP.NET, имеют параметр aspxerrorpath строки запроса. То есть если этот параметр присутствует, мы предполагаем, что имеем дело с ошибкой ASP.NET, и с помощью значения параметра получаем URL, который был запрошен пользователем. Если же параметра в строке запроса нет, мы предполагаем, что имеем дело с ошибкой, поступившей из IIS, и получаем запрошенный URL из свойства HttpRequest.RawURL, доступ к которому возможен через свойство Request.

В сообщениях об ошибках мы очень конкретно описываем источник возникшей проблемы - такой уровень детализации пользователям не нужен. В реальном проекте внимание должно быть сосредоточено на действии, которое пользователь может предпринять, чтобы обойти проблему - например, ввести правильный пароль, начать с начала или возвратиться на страницу позже.

Чтобы появилась возможность работы со свойством RawURL, необходимо модифицировать способ генерации страниц ошибок сервером IIS, как показано в примере ниже, в котором представлены изменения, внесенные в файл Web.config для установки веб-формы разделяемой страницы ошибки:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <customErrors mode="On" defaultRedirect="/DynamicFailure.aspx">
      <error statusCode="404" redirect="/NotFoundShared.aspx"/>
    </customErrors>
  </system.web>

  <system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="404"/>
      <error statusCode="404" responseMode="ExecuteURL" path="/NotFoundShared.aspx"/>
    </httpErrors>
  </system.webServer>
</configuration>

Мы обновили URL в обоих разделах файла, чтобы использовалась веб-форма NotFoundShared.aspx. Кроме того, мы изменили значение атрибута responseMode в конфигурации IIS для применения режима ExecuteURL. Это позволяет визуализировать веб-форму без перенаправления клиента и означает, что детали запроса, вызвавшего ошибку, доступны через объект HttpRequest, в том числе и свойство RawURL. Результат запрашивания /DoesNotExist.aspx и /DoesNotExist.html можно видеть на рисунке ниже:

Генерация страниц ошибок с помощью разделяемой веб-формы

В этом примере отображаются сведения о том, где возникла ошибка, но пользователю такие детали предоставляться не должны. Важный аспект данного подхода заключается в том, что место возникновения ошибки известно вам. Используя эту информацию, можно создать веб-форму, которая будет применяться для генерации страниц ошибок из ASP.NET и IIS.

Указание страницы ошибки, специфичной для веб-формы

Последний способ настройки стандартного поведения предусматривает указание страницы ошибки для отдельных веб-форм с помощью атрибута ErrorPage в директиве Page. Мы нечасто используем этот прием в своих проектах, но он может быть удобен в ситуации, когда имеется небольшое количество веб-форм, требующих особого внимания, например, веб-форма аутентификации.

Несмотря на это, при создании страниц ошибок мы обычно полагаемся на коды состояния HTTP. В примере ниже демонстрируется применение атрибута ErrorPage в веб-форме Default.aspx:

<%@ Page Language="C#" AutoEventWireup="true"  
    CodeBehind="Default.aspx.cs" Inherits="ErrorHandling.Default"
    ErrorPage="~/DefaultASPXError.html" %>
...

В этом примере мы указали, что при появлении любых необработанных исключений из веб-формы Default.aspx должен отображаться файл DefaultASPXError.html. Теперь добавим в проект файл DefaultASPXError.html с использованием шаблона элемента HTML Page. Содержимое этого файла аналогично приведенным ранее страницам ошибок.

Чтобы этот прием работал, атрибут mode элемента customErrors должен быть установлен в On. Если этого не сделать, будет применяться стандартная страница ошибки для кода состояния http, равного 500.

Эта страница ошибки идентифицирует в качестве источника ошибки веб-форму Default.aspx. В реальном проекте с помощью данного средства можно предоставлять пользователю конкретные инструкции либо информацию, связанную с поддержкой. Чтобы увидеть страницу ошибки, запустите приложение, введите строку "яблоко" в одном из полей формы и щелкните на кнопке "Отправить". В качестве альтернативы можно отметить флажок "Генерировать исключение" и щелкнуть на кнопке "Отправить" - атрибут ErrorPage используется для необработанных исключений, которые возникают в элементах управления или в самой веб-форме.

Пройди тесты
Лучший чат для C# программистов