Этап «давайте сначала изучим стандарты» при обучении веб‑разработке иногда сразу пропускают (или рассматривают буквально в двух словах), переходя к фреймворкам, абстракциям и решениям, которые за вас уже приняли авторы этих фреймворков. В моменте это удобно, можно быстро написать работающий код, не зная что происходит под капотом. Но потом возникают вопросы: «А где в конкретном фреймворке личное решение автора, а где следование стандартам?», «Будет ли та или иная логика аналогично реализована в другом фреймворке, если придется на него переехать?».

В этой статье мы разберем все 9 методов HTTP-запросов, опираясь на тексты документов, которыми эти методы определены (RFC 9110 и RFC 5789).

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

Если вы еще не особо понимаете, как в целом устроен обмен данными по HTTP-протоколу, то рекомендую сначала прочитать статью Андрея Созыкина "Протокол HTTP", здесь, на Хабре.

Что такое методы в протоколе HTTP и какими спецификациями они определены

Метода запроса — это основной источник семантики запроса; он указывает цель, с которой клиент сделал запрос, и что клиент ожидает получить в качестве успешного результата. — RFC 9110, секция 9.1

Метод позволяет заключить между клиентом и сервером соглашение: клиент объявляет, что хочет от сервера, сервер обязуется реагировать соответственно. Один и тот же URI при разных методах означает разные операции над ресурсом.

Метод указывается в строке запроса (request line), первой строке HTTP-запроса в формате метод URI версия-протокола.

GET /articles/42 HTTP/1.1

где GET — имя метода. По соглашению имена методов пишутся всегда заглавными буквами.

Всего определено 9 методов (8 в RFC 9110 и один, PATCH, в RFC 5789).

Метод

Описание по RFC

GET

Передать текущее представление целевого ресурса

HEAD

То же что GET, но без тела ответа

POST

Выполнить специфичную для ресурса обработку тела запроса

PUT

Создать или заменить состояние целевого ресурса телом запроса

PATCH

Применить частичные изменения к целевому ресурсу

DELETE

Отвязать целевой ресурс от его URI

CONNECT

Установить туннель к серверу, идентифицированному целевым ресурсом

OPTIONS

Описать параметры взаимодействия для целевого ресурса

TRACE

Выполнить петлевой тест сообщения по пути к целевому ресурсу

В прикладной веб-разработке регулярно используются GET, HEAD, POST, PUT, PATCH и DELETE.

Почему важно знать требования спецификаций, если есть веб-фреймворки

Фреймворки берут на себя маршрутизацию, валидацию и сериализацию, но не семантику методов. Правильные коды ответов, идемпотентность PUT, атомарность PATCH, заголовок Location при создании ресурса, всё это остаётся на стороне разработчика.

Спецификации описывают, каких правил должны придерживаться клиент и сервер при работе по HTTP. Фреймворк помогает написать сервер, но не гарантирует что он полностью соблюдает эти правила.

Свойства методов

Прежде чем разбирать каждый метод, нужно понять 3 фундаментальных свойства, которые определяет RFC 9110.

Safe (безопасный)

Метод безопасен, если его семантика по существу доступна только для чтения. Клиент, отправляя безопасный запрос, не ожидает и не запрашивает изменения состояния на сервере. Это не гарантия, сервер физически может что-то изменить (логи, счётчики), но клиент в этом не виновен.

Безопасные методы: GET, HEAD, OPTIONS, TRACE.

Idempotent (идемпотентный)

Метод идемпотентен, если несколько идентичных запросов дают один и тот же эффект на сервере. Речь идёт именно о стороне сервера, а не о содержимом ответа.

Все безопасные методы идемпотентны по определению: их ожидаемая семантика не предполагает изменения состояния ресурса по воле клиента. Дополнительно идемпотентны: PUT, DELETE.

Cacheable (кешируемый)

Ответ на запрос можно хранить в кеше и использовать повторно. Это зависит не только от метода, но и от заголовков Cache-Control, Vary, Expires и т. д.

Кешируемые по умолчанию: GET, HEAD.

POST и PATCH только при явных заголовках кешируемости (Cache-Control + Content-Location).

Метод GET

GET запрашивает передачу текущего выбранного представления целевого ресурса.

Источник

GET выступает первичным механизмом получения информации по протоколу HTTP. Если вы открываете страницу в браузере, переходите по ссылке, вбиваете URL в адресную строку, браузер отправляет запрос с методом GET.

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

Объем запрашиваемого GET-запросом ресурса можно уменьшить, отправив в запросе заголовок Range с указанием требуемого диапазона байт.

Ответ на GET-запрос кешируем. Кешированный ответ может использоваться для последующих HEAD-запросов на тот же URI.

Статусы ответов

Основной код 200 OK. Он возвращается при успешной передаче ресура (например, HTML-страницы для отображения в браузере).

Два дополнительных кода:

  • 206 Partial Content — ответ на Range-запрос. Клиент, к примеру, передал заголовок Range: bytes=0-1023, сервер вернул только запрошенный фрагмент. Используется при докачке файлов и потоковом видео. Сервер обязан включить заголовок Content-Range с указанием отданного диапазона и полного размера ресурса

  • 304 Not Modified — ответ на условный GET-запрос. Клиент передал заголовок If-None-Match с кешированным значением ETag или If-Modified-Since с датой последнего полученного ответа. Если ресурс не изменился, сервер возвращает 304 без тела. Клиент использует закешированную копию. Это основной механизм валидации кеша по RFC.

Оба кода не являются ошибками — это успешные ответы с оптимизированной передачей данных. 304 формально относится к классу 3xx, но семантически означает "твоя копия актуальна", а не редирект.

Справочно:

  • ETag — заголовок ответа сервера, который содержит уникальный идентификатор текущей версии ресурса. Как правило это хеш содержимого: изменился ресурс, изменился ETag. Клиент сохраняет это значение и при следующем запросе передаёт его в заголовке If-None-Match;

  • Last-Modified — заголовок ответа сервера, который содержит дату и время последнего изменения ресурса. Клиент сохраняет это значение и при следующем запросе передаёт его в заголовке If-Modified-Since;

  • If-None-Match — заголовок запроса клиента, в котором клиент передаёт сохранённое значение ETag. Сервер сравнивает его с текущим ETag ресурса.

Примеры

GET /articles/42 HTTP/1.1
Host: example.com
Accept: application/json
# Через curl
curl -i https://example.com/articles/42

# Частичный запрос (Range)
curl -i -H "Range: bytes=0-1023" https://example.com/files/big.pdf

Метод HEAD

"Метод HEAD идентичен GET, за исключением того, что сервер НЕ ДОЛЖЕН отправлять тело в ответе."

Источник

HEAD предназначен для получения метаданных о ресурсе (заголовков ответа) без необходимости получать от сервера тело ответа.

В RFC приводятся типичные кейсы использования HEAD-запросов:

  • проверка гиперссылок (существует ли ресурс)

  • обнаружение свежих изменений (валидация кеша).

Сервер должен возвращать те же заголовки, что и при GET. Допустимое исключение: заголовки, значение которых определяется только в процессе генерации тела.

К примеру, у сервера есть динамический эндпоинт, где значение для заголовка Content-Length вычисляется только в процессе генерации тела. Сервер буферизует ответ, пока не накопит минимальный объём данных, и только тогда знает его размер.

При GET это работает нормально. При HEAD сервер стоит перед выбором:

  • сгенерировать тело целиком, узнать точное значение для Content-Length, отправить только заголовки, тело отбросить.

  • не генерировать тело вовсе, но тогда Content-Length в ответе на HEAD-запрос будет отсутствовать.

По RFC второй вариант предпочтительнее, потому что HEAD запрашивается ради эффективности. Генерировать тело только ради того, чтобы узнать его длину и сразу же отбросить, противоречит самому смыслу HEAD.

HEAD-ответ кешируем. Более того, он может инвалидировать ранее кешированный GET-ответ, если метаданные ответа изменились.

RFC требует поддержки HEAD на любом URI, где поддерживается GET.

Замечание о реализации этого требования в конкретных фреймворках. Starlette в Python автоматически добавляет HEAD к любому GET-маршруту. FastAPI, хоть и опирается на Starlette, но переопределяет множество методов в классе APIRoute и эту логику не наследует, HEAD нужно добавлять явно через @app.api_route("/path", methods=["GET", "HEAD"]).

Статусы ответов

Основной код 200 OK.

304 Not Modified — ответ на условный HEAD-запрос (по аналогии с GET).

Примеры

HEAD /files/report.pdf HTTP/1.1
Host: example.com
# curl -I отправляет именно HEAD
curl -I https://example.com/files/report.pdf

# Ответ содержит заголовки, но не тело:
# HTTP/1.1 200 OK
# Content-Type: application/pdf
# Content-Length: 204800
# ETag: "abc123"

Метод POST

"POST запрашивает, чтобы целевой ресурс обработал представление, вложенное в запрос, в соответствии с собственной семантикой ресурса."

Источник

POST — метод с наиболее широкой семантикой. RFC перечисляет типичные варианты применения:

  • передача данных обработчику (например, данных из HTML-формы);

  • публикация в блоге, форуме, новостной ленте;

  • создание нового ресурса, URI которого ещё не известен клиенту;

  • добавление данных к существующему ресурсу.

POST не идемпотентен и не безопасен.

При успешном создании ресурса сервер должен вернуть статус 201 Created с заголовком Location, указывающим URI созданного ресурса.

Ответ кешируем только при наличии явных заголовков свежести и заголовка Content-Location со значением, совпадающим с URI запроса.

Статусы ответов

Явные предписания только два:

  • 201 Created + заголовок Location при успешном создании ресурса

  • 303 See Other + заголовок Location: сервер обработал POST и перенаправляет клиента на GET к результату (зачем это см. PRG-паттерн).

Три кода которые никогда не могут быть ответом на POST:

  • 206 Partial Content — частичный контент, специфичен для Range-запросов, POST их не делает

  • 304 Not Modified — механизм условных запросов, к POST неприменим

  • 416 Range Not Satisfiable — неудовлетворимый Range, по той же причине что и 206.

Примеры

POST /articles HTTP/1.1
Host: example.com
Content-Type: application/json

{"title": "HTTP-методы", "body": "..."}
curl -i -X POST https://example.com/articles \
  -H "Content-Type: application/json" \
  -d '{"title": "HTTP-методы"}'

# Ожидаемый ответ:
# HTTP/1.1 201 Created
# Location: /articles/43

Метод PUT

"PUT запрашивает, чтобы состояние целевого ресурса было создано или заменено состоянием, определённым представлением, вложенным в тело запроса"

Источник

PUT означает: "установи состояние ресурса именно таким". Успешный PUT подразумевает, что последующий GET на тот же URI вернёт эквивалентное представление.

Критерием для разграничения PUT и POST выступает то, известен ли изначально клиенту точный URI ресурса:

  • если да, то используется PUT. PUT /users/42 означает "установи состояние ресурса по этому адресу вот таким". Если ресурса не было, то сервер создаст, если был, то полностью обновит.

  • если точного URI ресурса нет, то используется метод POST. В этом случае URI нового ресурса определяет сервер. POST /users означает "создай пользователя, адрес придумай сам". Сервер вернёт точный URI созданного ресурса (например, /users/42) в заголовке ответа Location.

Частая ошибка — использовать PUT для частичного обновления. PUT заменяет ресурс целиком. Если нужно изменить только одно поле, то нужно выбрать метод PATCH (о нем ниже).

PUT идемпотентен: несколько одинаковых запросов дают тот же результат, что и один.

Ответы не кешируются. Успешный PUT инвалидирует кешированные ответы на тот же URI.

Сервер вправе вернуть ETag / Last-Modified в ответе на PUT только если сохранил данные без каких-либо трансформаций (включая, к примеру, нормализацию).

Статусы ответов

Если ресурс не существовал и создан, сервер обязан вернуть статус 201 Created.

Если ресурс существовал и изменён, сервер обязан вернуть 200 OK или 204 No Content. 204 используют когда нет смысла возвращать тело, например при обновлении конфига или замене файла. 200 уместен, когда сервер возвращает обновлённое представление ресурса.

Примеры

PUT /articles/42 HTTP/1.1
Host: example.com
Content-Type: application/json

{"title": "Обновлённый заголовок", "body": "..."}
curl -i -X PUT https://example.com/articles/42 \
  -H "Content-Type: application/json" \
  -d '{"title": "Обновлённый заголовок", "body": "..."}'

Метод PATCH

PATCH зафиксирован в отдельном документе RFC 5789.

"PATCH запрашивает, чтобы набор изменений, описанных в теле запроса, был применён к ресурсу, идентифицированному URI запроса."

Ключевое слово тут "набор изменений". В отличие от PUT, который заменяет ресурс целиком, PATCH передаёт инструкции по модификации в формате, определяемом в заголовке Content-Type запроса. Этот формат называется "патч" (patch document).

Разница между PUT и PATCH формулируется в RFC так:

  • в PUT тело запроса — новое состояние ресурса, которое нужно сохранить.

  • в PATCH тело запроса — описание того, как изменить текущее состояние ресурса.

Характеристики PATCH:

  • не безопасен

  • не идемпотентен

  • не кешируется (если обратное не указано явно)

PATCH не идемпотентен, потому что результат зависит от текущего состояния ресурс�� в момент применения патча. Инструкция "удалить строку 5" даст разный результат в зависимости от того, что там сейчас находится. Повторный запрос может либо сломать ресурс, либо вернуть ошибку. Вместе с тем PATCH может быть идемпотентным. RFC указывает, что некоторые форматы патчей не зависят от базового состояния, например, добавление строки в лог-файл или вставка неколлизионных строк в таблицу. В таких случаях идемпотентность возможна, но она в зоне ответственности клиента и формата патча, а не гарантия метода.

RFC устанавливает требование атомарности патча. Патч применяется полностью или не применяется вовсе. Частичное применение запрещено.

Форматы патчей

Единого формата нет. Сервер принимает те форматы, которые сам поддерживает для конкретного ресурса. Клиент указывает формат через заголовок Content-Type. Два наиболее распространённых формата:

  • application/json-patch+json (определен в RFC 6902) — список операций над JSON-документом: add, remove, replace, move, copy, test.

  • application/merge-patch+json (определен в RFC 7396) — слияние объектов: поля с null удаляются, остальные обновляются или добавляются, отсутствующие в патче остаются нетронутыми.

Конкурентные изменения и заголовок If-Match

Поскольку PATCH применяется к конкретному состоянию ресурса, два параллельных запроса опаснее чем два параллельных PUT. PUT просто перезаписывает, PATCH сильнее зависит от текущего состояния ресурса. RFC рекомендует использовать условные запросы: клиент передаёт заголовок If-Match с текущим значением заголовка ETag ресурса, и сервер применяет патч только если состояние не изменилось. Если кто-то успел изменить ресурс между GET и PATCH, сервер вернёт 412 Precondition Failed.

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

RFC 5789 (разд. 2.2) перечисляет коды ошибок и ситуации, в которых их следует возвращать:

Ситуация

Код

Некорректный формат patch document

400 Bad Request

Формат не поддерживается сервером

415 Unsupported Media Type + Accept-Patch в ответе

Формат понятен, но применить нельзя (например, XML перестанет быть well-formed)

422 Unprocessable Entity

Ресурс не существует, и формат не может быть применён к null

404 Not Found

Конфликт состояния (структура, которую нужно изменить, не существует)

409 Conflict

Precondition не выполнен (If-Match / If-Unmodified-Since)

412 Precondition Failed

Сервер не может обработать конкурентные запросы

409 Conflict

Примеры

PATCH /articles/42 HTTP/1.1
Host: example.com
Content-Type: application/json-patch+json
If-Match: "e0023aa4e"

[
  {"op": "replace", "path": "/title", "value": "Новый заголовок"},
  {"op": "remove", "path": "/draft"}
]
PATCH /articles/42 HTTP/1.1
Host: example.com
Content-Type: application/merge-patch+json

{"title": "Новый заголовок", "draft": null}
curl -i -X PATCH https://example.com/articles/42 \
  -H "Content-Type: application/json-patch+json" \
  -H 'If-Match: "e0023aa4e"' \
  -d '[{"op": "replace", "path": "/title", "value": "Новый заголовок"}]'

Метод DELETE

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

Источник

DELETE, как указывает RFC, похож на rm в UNIX. Он удаляет связь между URI и ресурсом, но не обязательно сам ресурс. Реальное удаление данных на сервере является деталью реализации, спецификация её не регламентирует. DELETE уместен там где физическое удаление действительно предусмотрено: файл в хранилище, статья в блоге, запись в справочнике.

Коды ответа по RFC:

Код

Значение

202 Accepted

Действие будет выполнено, но ещё не выполнено

204 No Content

Выполнено, тело ответа отсутствует

200 OK

Выполнено, тело содержит описание результата

DELETE идемпотентен: повторный DELETE не должен менять итоговое состояние сервера; на практике серверы часто отвечают 404 Not Found или 410 Gone, но это уже вопрос конкретной реализации.

Ответы не кешируются. Успешный DELETE инвалидирует кешированные ответы.

Примеры

DELETE /articles/42 HTTP/1.1
Host: example.com
curl -i -X DELETE https://example.com/articles/42

# Типичные ответы:
# HTTP/1.1 204 No Content
# HTTP/1.1 404 Not Found  (уже удалён)

Метод OPTIONS

"OPTIONS запрашивает информацию о параметрах взаимодействия, доступных для целевого ресурса."

Источник

OPTIONS позволяет клиенту узнать, какие методы и параметры доступны для ресурса без выполнения каких-либо действий над ним.

Два режима работы:

OPTIONS * HTTP/1.1             # Запрос к серверу в целом
OPTIONS /articles/42 HTTP/1.1  # Запрос к конкретному ресурсу

OPTIONS * — запрос к серверу в целом; полезен как «ping» для проверки совместимости с HTTP/1.1. Сервер при успешном ответе на OPTIONS обычно возвращает заголовки, описывающие доступные возможности ресурса, например Allow.

Главный кейс сегодня CORS preflight. Браузер автоматически отправляет OPTIONS перед "небезопасным" cross-origin запросом, чтобы проверить, разрешает ли сервер операцию.

Метод CONNECT

"CONNECT запрашивает, чтобы получатель установил туннель к целевому серверу, идентифицированному в запросе."

Источник

CONNECT — специальный метод для создания сквозного туннеля, как правило через прокси. Используется прежде всего для HTTPS через HTTP-прокси (туннель под TLS). Бэкендеры обычно с ним не работают. Метод нужен тем, кто пишет или конфигурирует HTTP-прокси (nginx в режиме прокси, корпоративные шлюзы). Если пишете бизнес-логику на FastAPI или Django, этот раздел для общей картины.

Метод TRACE

"TRACE запрашивает, чтобы получатель вернул полученное сообщение обратно отправителю."

Источник

Сервер (или первый получатель с Max-Forwards: 0) отражает полученный запрос обратно в теле ответа 200 OK. Это диагностический инструмент: клиент видит, что реально пришло на другой конец цепочки. На практике большинство серверов и CDN отключают TRACE из соображений безопасности.

Сравнительная таблица свойств основных методов HTTP-запросов согласно RFC

Метод

Безопасность

Идемпотентность

Кешируемость

GET

HEAD

POST

⚠️ только явно

PUT

PATCH

⚠️ только явно

DELETE