Давайте обновим наше приложение, чтобы маршрут /snippet/create отвечал только на HTTP запросы, в которых используется метод POST.

Содержание статьи

Архитектура HTTP-маршрутов веб-приложения для создания новой заметки.

Премиум 👑 канал по Golang

Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎

Подписаться на канал

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК ЧАТ в Telegram

Метод Шаблон Обработчик Действие
ANY (любой метод) / home Отображает домашнюю страницу
ANY (любой метод) /snippet showSnippet Отображает определенную заметку
POST /snippet/create createSnippet Создает новую заметку

Очень важно выполнить данное изменение, потому что POST запрос к маршруту /snippet/create должен создавать новую запись в базе данных, но если будет выполнен GET запрос, то результатом будет ошибка 405 — запрещенный метод.

Создание новой заметки в базе данных является не идемпотентным действием, которое изменяет состояние нашего сервера. Поэтому мы должны придерживаться хорошей HTTP практики и ограничить маршрут, чтобы он отвечал только на POST-запросы.

Однако, главная причина, почему я хочу осветить данную тему заключается в HTTP заголовках и то, как они настраиваются.

Коды состояния HTTP в веб-приложениях

Давайте начнем с обновлением кода для функции-обработчика createSnippet(). Функция должна возвращать код состояния HTTP 405 (метод запрещен), но только в том случае, если это GET запрос. Для этого можно использовать метод w.WriteHeader() следующим образом:

Хотя данное изменение выглядит простым, в нем есть несколько нюансов, которые стоит пояснить:

  • Вызвать метод w.WriteHeader() в обработчике можно только один раз, и после возвращения кода состояния HTTP, изменить его нельзя. Если попытаться вызвать w.WriteHeader() во второй раз, Go выдаст сообщение об ошибке;
  • Если не вызывать метод w.WriteHeader() напрямую, тогда первый вызов w.Write() автоматически отправит пользователю код состояния 200 OK. Поэтому, если вы хотите вернуть другой код состояния, вызовите один раз метод w.WriteHeader() перед любым вызовом w.Write().

Давайте рассмотрим все это в действии.

Перезапустите веб-сервер, откройте второе окно терминала и выполните следующею curl-команду для создания POST-запроса на адрес http://127.0.0.1:4000/snippet/create. Вы должны получить HTTP-ответ с кодом состояния 200 OK.

Вы получите такой результат:

Однако, если вы выполните другой метод запроса: GET, PUT или DELETE — вы получите ответ с кодом состояния HTTP 405 Method Not Allowed.

К примеру:

Результатом будет:

Настройка HTTP-Заголовка

Другое улучшение, которое мы можем добавить, это заголовок Allow: POST для каждого ответа 405 «метод запрещен», чтобы пользователь знал, какие HTTP-методы поддерживаются для определенного URL.

Это можно сделать, используя метод w.Header().Set() для добавления нового заголовка к карте HTTP-заголовков. Например:

Внимание: Вы должны сперва вызвать метод w.Header().Set() и уже потом остальные методы w.WriteHeader() или w.Write() иначе ваши изменения не будут учтены.

Давайте повторим наш PUT или GET запрос на адрес /snippet/create и проверим если наш новый заголовок Allow появится в списке:

Результат:

Как вы видите, теперь ответ включает в себя строку Allow: POST.

Использование http.Error

Если требуется отправить какой-то код состояния, кроме 200 с текстом ответа (как мы это делали в коде выше), можно использовать http.Error(). Это вспомогательная функция, которая принимает текст сообщения и код состояния, а затем «за кулисами» сама вызывает методы w.WriteHeader() и w.Write().

Обновим код следующим образом:

С точки зрения функциональности, она осталась прежней. Разница в том, что теперь мы передаем ответственность вызова http.ResponseWriter другой функции, которая отправляет ответ пользователю за нас.

В Go, практика вызова http.ResponseWriter в в коде других вспомогательных функциях очень распространена, и в будущих уроках мы будем часто использовать такой подход. Важно, чтобы вы понимали, что лежит «под капотом» в других более продвинутых (и интересных!) способов отправки ответов пользователям. Ранее, мы использовали методы w.Write() и w.WriteHeader() напрямую, но на практике так делают довольно редко.

Управление HTTP заголовками в Go

В приведенном выше коде мы использовали w.Header().Set(), чтобы добавить новый заголовок в карту HTTP заголовка. Существуют такие методы как Add(), Del() и Get(), которые тоже можно использовать для добавления, удаления и чтения заголовков из карты.

HTTP заголовки по умолчанию от веб-сервера

При отправки ответа, Go автоматически установит для вас три сгенерированных системой HTTP заголовка: Date, Content-Length и Content-Type.

Заголовок Content-Type (тип контента) весьма интересен. Go попытается автоматически узнать тип контента проанализировав содержимое тела ответа с помощью функции http.DetectContentType(). Если эта функция не сможет определить тип контента, Go укажет следующее значение для заголовка Content-Type: application/octet-stream.

Функция http.DetectContentType() обычно работает довольно неплохо. Однако главная проблема веб-разработчиков, плохо знакомыми с Go, заключается в том, что данная функция не может отличить JSON от обычного текста. По умолчанию, ответы содержащие JSON будут отправляться с заголовком Content-Type: text/plain; charset=utf-8. Вы можете предотвратить это, установив правильный заголовок вручную следующим образом:

Обработка HTTP заголовка

Название заголовка не чувствительно к регистру. Когда вы используете методы Add(), Get(), Set() и Del() в карте заголовков, название заголовка всегда будет обработана с помощью функции textproto.CanonicalMIMEHeaderKey(). Эта функция преобразует первую букву и любую букву после дефиса в верхний регистр, а остальные буквы в нижний.

Если требуется избежать такое поведения, можно напрямую отредактировать базовую карту заголовков (у нее тип map[string][]string). Например:

На заметку: Если используется соединение HTTP/2, Go всегда автоматически конвертирует названия заголовков в нижний регистр в соответствии со спецификациями HTTP/2.

Удаление системных HTTP заголовков

Метод Del() не удаляет заголовки, сгенерированные системой по умолчанию. Чтобы подавить их, требуется напрямую получить доступ к базовой карте заголовков и указать значение nil для нужного вам заголовка. К примеру, если нужно подавить заголовок Date, нужно использовать следующий код:

Полный исходный код веб-приложения

Ниже опубликован код нашего веб приложения из цикла уроков по созданию сайта на go на текущий момент.