Веб-разработка на Go
Теория: Обработка ошибок в веб-приложении
Представим, что мы запустили проект, в котором серверная часть написана на Golang. Все идет хорошо: количество пользователей растет и бизнес развивается. Но в какой-то момент мы замечаем, что наше веб-приложение выключается без причины. Пользователи сталкиваются с ошибками, спрашивают о проблеме, но мы не можем определить причину.
В этом уроке мы рассмотрим самые популярные проблемы Go веб-приложений и способы их решения. Также узнаем, можно ли заранее обезопасить себя от неожиданных сбоев.
Пишем устойчивое веб-приложение
Допустим, у нас есть веб-приложение, которое позволяет рассчитать время между двумя датами. Код приложения выглядит следующим образом:
Все работает отлично, пока в один день мы не внесли изменение в код приложения, которое отправляет HTTP-запросы. Из-за небольшой ошибки в коде, приложение стало отправлять некорректный JSON-формат в теле HTTP-запроса.
Воспроизведем такую ситуацию. Запускаем веб-приложение и отправляем HTTP-запрос с некорректным JSON-форматом в теле:
Получаем сообщение, что ответа не было:
Ошибка в формате тела HTTP-запроса привела к тому, что наше веб-приложение перестало работать. В логах веб-приложения мы видим сообщение о панике:
Восстановление после паники
Паника — это критическая ошибка, которая останавливает работу всего Go-приложения. Она возникает в запущенном приложении, поэтому ее нельзя обнаружить во время компиляции.
Из-за того, что паника возникает в работающем приложении и приводит к полной остановке, следует максимально подготовить наше приложение к таким ситуациям. Для этого в Go есть механизм восстановление от паники.
В веб-приложениях принято устанавливать посредника для восстановления после паник. Добавим такого посредника в наше веб-приложение:
Запускаем веб-приложение и пытаемся снова воспроизвести ситуацию с некорректным JSON-телом в HTTP-запросе. На этот раз получаем следующий ответ:
Мы получили сообщение об ошибке, но веб-приложение продолжило работу. Это произошло благодаря посреднику recover.New(), который восстановил веб-приложение после паники. Если мы попробуем отправить еще десять таких запросов, то веб-приложение все равно продолжит работать.
Наше веб-приложение стало устойчиво к паникам в коде, однако пользователи до сих пор получают ошибки с непонятным текстом. При этом мы ничего не видим в своих логах. Исправим обработку ошибок в приложении.
Обработка ошибок
В любом работающем веб-приложении всегда могут возникнуть ошибки. Это обусловлено тем, что веб-приложения — это сложные системы, в которых невозможно учесть все возможные сценарии. Поэтому Go-разработчикам важно предусмотреть возникновение как можно большего количества ошибок.
В Go ошибки представлены явным типом error, и золотое правило Go-разработчика звучит как: Обрабатываем абсолютно все ошибки в коде приложения. Даже если кажется, что ошибка в какой-то части кода никогда не возникнет, мы все равно должны ее обработать. Код приложения будет часто меняться, и то, что невозможно сегодня, может произойти завтра.
Добавим обработку всех ошибок в нашем веб-приложении:
Запускаем веб-приложение и отправляем HTTP-запрос с невалидным JSON в теле:
В ответ получаем сообщение об ошибке:
Теперь отправителю HTTP-запроса понятно, в чем заключается ошибка.
Попробуем отправить корректный JSON в теле HTTP-запроса, но передать невалидные даты:
Получаем в ответ сообщение:
Отправитель HTTP-запроса поймет, что неправильно передал значение в поле from.
Проверим, что наше веб-приложение работает при корректном запросе:
В ответ приходит:
Мы сделали веб-приложение более устойчивым к ошибкам в коде и неправильным запросам. В случае паники, веб-приложение будет само восстанавливаться и продолжать работу. При неправильном HTTP-запросе отправитель получит понятное сообщение об ошибке, а мы увидим в логах подробную информацию о том, что произошло.
Мы уже сделали много работы, но осталась еще одна потенциальная проблема, которую стоит учитывать при разработке веб-приложений. У нас отсутствуют таймауты при обработке HTTP-запросов.
Таймаут при обработке HTTP-запроса
Представим, что наш конкурент решил воспользоваться противозаконным методом и нанял команду хакеров, чтобы они остановили наше веб-приложение. Допустим, они узнали, что у нас нет таймаутов на обработку HTTP-запросов. Тогда они написали программу, которая отправляла в веб-приложение HTTP-запрос, но делало это очень медленно — по одному байту в секунду.
Получилась ситуация, когда количество соединений к нашему веб-приложению бесконечно растет, а обработчики не могут сработать, потому что тело HTTP-запроса получено не полностью. Спустя какое-то время, наше приложение перестает отвечать на HTTP-запросы, и весь сервер может выйти из строя.
Чтобы избежать такой ситуации, нам стоит настроить таймауты при обработке HTTP-запросов. Эта настройка укажет веб-приложению закрывать соединения спустя заданное время, если оно не получило полностью тело HTTP-запроса.
Добавим настройку таймаутов в наш код:
Таймауты настраиваются через передачу параметров в конструктор fiber.New(). Мы указали таймаут на чтение HTTP-запросов и запись HTTP-ответов в три секунды. Если спустя заданное время не произойдет ни одного действия, то соединение будет закрыто.
С помощью настройки таймаутов на обработку HTTP-запросов мы обезопасили веб-приложение от еще одной уязвимости, которая может вывести веб-приложение из строя.
Выводы
- В Go веб-приложениях может возникать состояние критической ошибки — паника, и важно всегда иметь посредника, который будет восстанавливать веб-приложение
- Все ошибки, которые возвращаются из функций, должны быть обработаны. Так разработчики и пользователи будут понимать, что пошло не так
- В Go веб-приложениях нужно настроить таймауты на обработку HTTP-запросов. Это поможет избежать уязвимости, которая может вывести веб-приложение из строя
