Если вы не специалист по REST, то, вероятно, в своих ответах постоянно используете одни и те же HTTP-коды, в основном 200, 404 и 500. Если применяется аутентификация, то, возможно, добавляются 401 и 403; если есть переадресации, то 301 и 302, но на этом, скорее всего, список заканчивается. Но спектр возможных кодов состояний гораздо шире и он может сильно улучшить семантику. Хотя во многих обсуждениях REST упор делается на сущностях и методах, применение подходящих кодов ответов о состояниях может повысить удобство вашего API.

201: Created

Многие приложения позволяют создавать сущности: аккаунты, заказы и так далее. В общем случае применяется HTTP-код состояния 200, и этого вполне достаточно. Однако код 201 более конкретен и подходит лучше:

HTTP-код ответа 201 Created об успешном состоянии показывает, что запрос выполнен успешно и привёл к созданию ресурса. По сути, новый ресурс был создан до отправки этого ответа, а сам новый ресурс возвращается в теле сообщения, его местоположением становится или URL запроса, или содержимое заголовка Location.

Веб-документация MDN

205: Reset Content

Аутентификация при помощи форм может быть успешной или неудачной. При неудачном выполнении обычно повторно отображается форма с очищенными полями.

И как раз для этого предназначен код состояния 205:

HTTP-код состояния 205 Reset Content сообщает клиенту, что нужно сбросить визуализацию документа, то есть, например, очистить содержимое формы, сбросить состояние canvas или обновить UI.

Веб-документация MDN

428: Precondition Required

При использовании Optimistic Locking валидация при обновлении может быть неудачной, потому что данные уже были обновлены кем-то ещё. По умолчанию фреймворки (например, Hibernate) в таком случае выбрасывают исключение. В свою очередь, разработчики могут перехватывать его и отображать удобное информационное окно, просящее перезагрузить страницу и ввести данные повторно.

Давайте проверим код состояния 428:

Исходный сервер требует, чтобы запрос был условным. Это нужно для устранения проблемы «утерянного обновления», когда клиент при помощи GET получает состояние ресурса, изменяет его и помещает при помощи PUT обратно на сервер, в то время как третья сторона изменила состояние на сервере, что приводит к конфликту.

Веб-документация MDN

Этот код чётко описывает случай конфликта при optimistic locking!

В RFC 6585 упоминается термин условный и показывается пример использования заголовка If-Match. Однако в нём не показано, как конкретно достичь этого условия.

409: Conflict

Любопытно, что по поводу кода 409 написано следующее:

HTTP-код ответа состояния 409 Conflict говорит о конфликте запроса с текущим состоянием сервера.

Веб-документация MDN

Он также применим в предыдущем случае, но более обобщённый. Например, типичным примером использования будет обновление уже удалённого ресурса.

410: Gone

Чаще всего, когда вы пытаетесь получить при помощи GET ненайденный ресурс, сервер возвращает код 404. Но что, если ресурс существовал ранее, но теперь его нет? Интересно, что для конкретно этого случая есть альтернатива: об этом может сообщить семантика возвращаемого HTTP-кода. И именно поэтому используется 410.

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

Если неизвестно, временное это состояние или постоянное, то нужно использовать код состояния 404.

Веб-документация MDN

300: Multiple choices

Это может показаться натянутым выбором, но спецификация IETF соответствует данному случаю.

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

Например, вот какой ответ возникает при доступе к Spring Boot Actuator:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/manage",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8080/manage/beans",
      "templated": false
    },
    "health": {
      "href": "http://localhost:8080/manage/health",
      "templated": false
    },
    "metrics": {
      "href": "http://localhost:8080/manage/metrics",
      "templated": false
    },
  }
}

В этом местоположении отсутствует обычный ресурс. Сервер предоставляет множество ресурсов, каждый из которых имеет свой идентификатор. Это подходит для кода состояния 300:

[…​ ] сервер ДОЛЖЕН генерировать полезную нагрузку в ответе 300, содержащую список метаданных описания и ссылок на URI, из которого пользователь или агент пользователя может выбрать наиболее подходящий ему.

IETF HTTP 1.1: Semantics and Content

Заключение

В общем случае конкретные состояния HTTP имеют смысл при наличии REST-бэкенда, доступ к которому выполняет JavaScript-фронтенд. Например, сброс формы (205) не имеет смысла, если страницу генерирует сервер.

Проблема этих кодов связана с семантикой: интерпретировать их можно по-разному. Зачем выбирать 409 вместо 428? В конечном итоге, это может быть вопросом интерпретации.

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

Дальнейшее чтение

Комментарии (21)


  1. VYudachev
    23.05.2023 11:54
    +3

    Если уж речь зашла про Created, то стоит упомянуть и 204: No Content. Уместен, например, как результат успешного запроса PUT.


    1. PrinceKorwin
      23.05.2023 11:54
      +7

      Или как результат DELETE операции


  1. poxvuibr
    23.05.2023 11:54
    +11

    Вот было бы круто, если бы кто-то написал не о том, что нужно использовать все коды возврата, а рассказал бы зачем это надо делать )) . И в каких случаях какие коды использовать. Я бы сказал спасибо даже за статью в которой рассказывается в каких случаях надо вернуть 404. За статью, полностью посвящённую только одному этому http коду.


    1. atygaev
      23.05.2023 11:54

      какое-то время назад я начинал разговор на эту тему, может вас заинтересует

      https://habr.com/ru/articles/440382/


    1. domix32
      23.05.2023 11:54

      Скажем, потому что недостаток прав при доступе страницы не является её отсутсвием. Github например так приватные репы отдаёт если доступа не было. Логичнее было бы отдавать 403 или 401. Хотя тут ещё нюансы с приватностью есть.


  1. arty
    23.05.2023 11:54
    +5

    Надеюсь, что всё же никто не будет использовать 205: Reset Content если при заполнении формы авторизации пользователь опечатался в емейле


  1. rozhnev
    23.05.2023 11:54
    -1

    Я пользуюсь https://http.cat/


    1. PaulIsh
      23.05.2023 11:54
      +2

      И в чем информативность ваших котов?


      1. gmtd
        23.05.2023 11:54

        Не сильно ниже и противоречивей HTTP кодов


        1. PaulIsh
          23.05.2023 11:54

          Весь этот старый набор кодов давно следует пересмотреть. Часть пометить как deprecated, для остальных расписать когда использовать.

          В grpc вообще обошлись порядка 15 кодов состояний (если мне не изменяет память).


          1. gmtd
            23.05.2023 11:54
            +3

            И есть отличный JSON-RPC, в котором коды/ошибки транспортного уровня четко отделены от кодов/ошибок уровня приложения. Что убрало весь этот зоопарк с чайниками и котами.


  1. PavelBelyaev
    23.05.2023 11:54
    +1

    Еще есть код 418 I’m a teapot («я — чайник»);


    1. vikarti
      23.05.2023 11:54

      По нему даже книги пишут — https://www.amazon.com/418-Am-Teapot-Edgar-Scott-ebook/dp/B08ZL6G1D7 :)
      А еще есть 451 и вот этот код — на практике поддерживается то что него не все клиентское ПО поддерживает — оказывается проблемой этого ПО


  1. Aytuar
    23.05.2023 11:54

    Думаю важны только http коды API, которые может обработать клиент.


  1. Krouler7
    23.05.2023 11:54
    +1

    Часто в инструкциях или советах опытных web разработчиков можно узнать информацию совершенно обратную. Чтобы не слить(случайно или не-) внутрянку системы используется код 404 и только он. Если не прав, поправьте пожалуйста.

    К слову, в посте не обнаружил обратной аргументации.


    1. NickyX3
      23.05.2023 11:54

      В случае API коды ответа правильные часто используют, чтоб клиент не разбирал данные из ответа, ибо будет и так понятно, что что-то пошло не так. 404 вместо 401/403 все же применяют для скрытия того, что условно может быть доступно определенному кругу пользователей прямо в браузере.


  1. domix32
    23.05.2023 11:54
    +4

    200 Ok
    {"code": 404}


  1. Tangeman
    23.05.2023 11:54
    +3

    Из практики. Когда-то давно реализовали мы REST-like API для одного сервиса, задокументировали и раздали пользователям (их тогда было несколько сотен).

    Поскольку стандартных HTTP-кодов было недостаточно, каждый ответ (если он шёл от приложения) содержал развернутый индикацию успешности или отчёт об ошибке (или сразу о нескольких, если их было больше одной).

    А когда пошли первые жалобы мы столкнулись с одним интересным явлением - пользователи начисто игнорировали тело если получали что-то кроме 200 в ответ, и совершенно искренне не могли понять зачем читать тело если есть код.

    Годы (буквально) разъяснения, примеры в документации - ничего не помогало, на каждую жалобу уходило несколько итераций службы поддержки чтобы получить тело с подробным отчётом.

    По этой причине следующая версия API была построена по простому принципу - 200 - запрос дошёл до приложения, >= 300 - не дошёл. И дело пошло - с тех пор проблем и вопросов у пользователей сильно поубавилось, потому что детальные отчеты в теле были достаточно красноречивы, и самое главное - их стали читать и парсить.

    С тех пор у меня есть чёткое убеждение - не надо смешивать транспорт (HTTP) и приложение (JSON/XML/etc), а также не надо пытаться впихнуть в куцые варианты кодов HTTP варианты ответов приложения - их банально недостаточно и они практически бесполезны в сложных API. А если API доступно не только через HTTP (да, есть и другие транспорты), то отсутствие привязки только упрощает разработку.

    К тому же, если уж заставлять пользователя читать тело вне зависимости от кода - сами коды имеют мало смысла, их основное назначение - индикация того дошёл ли запрос до приложения или нет, и на этом всё. В самом крайнем случае можно использовать серию 5xx для индикации того что запрос не был обработан (т.е. безопасно его повторить), но не более того.

    Разумеется, в очень простых API, где всё влезает в HTTP-коды их можно использовать (хотя и остаётся неоднозначность с 404), но это частный случай.

    По поводу кэширования - есть же вполне себе стандартизованный Cache-Control, который решает проблемы с прокси и прочими посредниками, а если прокси его не понимает или пытается "умничать" - то грош ему цена.


    1. 16tomatotonns
      23.05.2023 11:54
      +1

      На стороне клиентов тоже иногда очень весело обрабатывать миллиард вариантов ответов от сервера:

      "Если код равен 200 или код равен 201 или код равен 202 или код равен ....."
      (то ответ как бы пришёл и его можно обрабатывать одинаково).
      "Если код равен 300 или код равен 301 или код равен 302 или код равен ....."
      (то ответ как бы перемещён, но и его тоже в целом можно обрабатывать одинаково).

      И для каждого вызова апишечки предлагается писать такую телегу. И это не учитывая что внутри json'а в ответе будут свои отдельные статусы, и их как-то тоже надо сопоставлять, от чего деревья if'ов разрастаются, поддержка-отладка затрудняется и так далее.

      Единственные ответы для которых как бы имеет значение именно код - 4xx, так как там можно запросить повторно, совершив некоторые телодвижения на клиенте, и получить нужный результат. Во всех остальных случаях, от транспорта как правило требуется только одно: "Оно отработало?" или "Оно не отработало?", особенно когда в теле сообщеньки будут дополнительные статусы.


    1. powerman
      23.05.2023 11:54

      Не всё так хорошо в этом подходе.

      Во-первых, бесит получать ошибки с кодом 200.

      Во-вторых, помимо пользователей есть промежуточные edge proxy, которые штатно выдают полезные метрики, но эти метрики не рассчитаны на ошибки с кодом 200. Да и помимо них хватает сторонних инструментов (напр. для нагрузочного тестирования), которые так же ориентируются на стандартные коды и не распознают ошибки с кодом 200.

      В-третьих, возврат вообще всех ответов с кодом 200 не избавляет клиентов от поддержки других кодов - потому что да, есть тот самый edge/ingress proxy, который отлично умеет возвращать 502/504 если микросервис за ним не работает и временами другие ошибки тоже.

      В-четвёртых, нередко в приложении используется какой-то фреймворк (напр. swagger/openapi) или банальная сторонняя библиотека для роутинга входящих запросов, который на некорректные запросы или запросы к неизвестным роутам всё-равно будет возвращать далеко не 200.


  1. Urub
    23.05.2023 11:54

    Возврат кода 201: Created разве не говорит что создан "ресурс-ссылка" см. (https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201) и если создается только запись в БД без новой ссылки, то и надо возвращать 200 ?

    Поддержу логику что нужно отдавать кодом 200 с указанием кода ошибки и описания в json например. Другие http коды применять при явной их необходимости.