Как стать автором
Обновить

Комментарии 178

Мне кажется, в современных инструментах вебсокетов, одностраничных сайтов, проще реализовать ошибку через json-формат, чем разбираться с http-протоколом.
Это приводит потом к тому, что ты должен смотреть в ответ и понимать что там тебе пришло, вместо того, чтобы еще по статус коду знать, что если 2хх, то значит всё ок, если 4хх или 5хх, то что-то пошло не так.
4xx и 5xx и всё равно детально смотреть в тело ответа, что пошло не так, как минимум для логирования. Это если руководствоваться вашей логикой.
Статус 200 неинформативен. Вебсервер отработал, но как — вам не известно. Посмотрели в тело ответа, там json со статусом ОК — то значит всё хорошо, статус error — то что-то пошло не так, может там параметры невалидны. Сваливать этот статус в статус ответа вебсервера — ну такое.
еще по статус коду знать

И чем это знание поможет?


Если ошибка произошла на низком уровне (на уровне сервера, прокси, и т.д. Что-то в духе 404 или 504) — да, можно обойтись и статус кодом и на клиенте такой ответ отловить, не передавая сильно глубоко в обработку.


Если же ошибка произошла на уровне логики приложения, то в почти всех случаях помимо информации о самом факте ошибки клиенту нужно сообщить еще какие-то подробности о природе и причинах этой ошибки. И да, статус кода как правило недостаточно. Все равно будет какое-то тело ответа. И вот совершенно непонятно, что мы выигрываем, размазывая информацию об ошибке ровным слоем по телу и заголовку ответа, вместо того чтобы заключить её всю в одном месте (в теле ответа).
"Код становится уродливым", кмк, именно в таком случае. В лучшем случае приходится просто говорить клиентскому фреймворку, чтобы он игнорировал статус код ответа и передавал на уровень бизнес логики все ответы подряд. И там уже с ними работать точно так же, как и в случае статуса 200. С единственным отличием, что тип ошибки будет доставаться из статус кода, а не одного из полей джейсона.
В худшем случае это все дело придется обвешивать еще кучей костылей и уродливых хуков, потому что условный nginx, если не сможет достучаться до апстрима, вернет html страничку со статусом 504, а не джейсон. И код бизнес-логики к такому повороту скорее всего будет не готов.

Да, пожалуй. Вы, кстати, напомнили мне случай, когда один сервис отвечал json в случае 2хх, а в случае 4xx и 5xx — xml. Тоже интересное дело было.
Веселее когда сервер по соапу отвечает вроде бы корректно, первый уровень соапа вы разбираете, видите там result_status = ok, дальше видите description, где ожидаете, что будет xml|json|plain text, а там внезапно HTTP 200 OK + пачка заголовков ответа другого сервера + ещё один SOAP XML. Ок, вы пишете парсер, который разбирает такой проблемный ответ, выдирает из него только xml. Всё вроде бы хорошо, но вот вы дёрнули другой метод… а там 2 раза 200 ОК + 2 XML… И тут вы понимаете, что что-то не так в консерватории.
Кажется я подозреваю о продукте какой компании вы говорите :) Тоже имел дело с API 3-х буквенной компании, которая такие ответы возвращает.

Компания другая, букв на одну больше, точнее даже на одну цифру, но программная платформа у них от одного производителя.

Может нужно говорить о других, более внятных протоколах?
Protobuf/grpc не подойдет?
И ещё давайте разделим коммуникацию между пользователем и сайтом (или веб-приложением) и компонентами этого самого веб-приложения…
Вот уже третий проект мне приходится объяснять команде, что недопустимо отвечать на все запросы кодом 200. Все ошибки должны быть определены кодом. Почему? Простой вопрос — а почему нет? Их придумали глупые люди? Если ты знаешь и умеешь использовать только код 200 это твоя, личная проблема. Да, да, проблема, потому, что ты будучи фронтовиком не понимаешь, как работает инфраструктура, которую ты используешь.
Скажите, фронтовики, ответы с каким кодом у нас кэшируются? Не с этим ли пресловутым кэшем потом у вас идет неравная борьба, которая заканчивпется всяким бредом, типа рандомных чисел в get запросе? А кто в курсе, как работает балансировщик REST запросов? Как он узнает, что тот или иной серев перегружен? Опять лепим свой велик?
Код ответа, это унифицированное средство принятия решения на инфраструктурном уровне. Его нельзя заменить никакими json в ответах. Природа этого кода находится ЗА JavaScript.

Мне реально, очень стыдно, в прямом смысле, за тех, кто минусует попытку автора поднять этот вопрос.
Кэширование — это больше про хидеры и разницу между GET/POST и прочими запросами
Коды ответа тут ортогональны
Уверен? Прочти спецификации. Управление кэшем в хидарах нужно для ответа 200. И является РЕКОМЕНДАЦИЕЙ. Для принятия решения браузером. А скажи мне, по какоиу принципу кэширует CDN?
А CDN сюда вообще зачем замешивать? Это вообще больше про отдачу контента, в первую очередь — статического. Топиком разговора же являются веб-приложения или API
Вот отсюда и идут эти тупые споры. Даже отвечать не хочу. Мануалы в руки и учить матчасть. Вы, блин, пишите WEB-приложения. Понимаете? А как оно функционирует не знаете. Вот и выходят сайты уровня колхозного дома быта. Хотя… и тут с такими знаниями будут проблемы. Не дай Бог админ поставит кэширующий прокси-сервер… Все! Трагедия вселенского масштаба! Будете там хидерами уговаривать его отдавать вам обновленные данные… ну или любимый костыль — рандом вкорячите.
1. Я не претендую на мега-гуру по веб-разработке.
2. Это Вы тут корчите из себя мега-гуру.
3. По ходу, Вы не хотите нести светоч знаний в массы, ну, ок. Хомячки разберутся с хттп сервисами сами
Да, вы правы. Что-то я разошелся. Извините.
Все эти мануалы лежат на поверхности. Просто изучите таблицу ответов developer.mozilla.org/ru/docs/Web/HTTP/Status, к примеру, и стратегии кэширования. Проблема кэша классическая, даже на собственном сервере зачастую делается кэширование ответов бэка через собственный web сервер. И если бэк будет отдавать постоянно 200 любому клиенту, ваш сервер будет собирать все это, а не только успешны ответы. Естественно, никакой вложенный в ответ JSON не спасет. CDN, это не просто статика, тот же CloudFlare реализует сокрытие ваших серверов от конечного пользователя. И запросы, в том числе REST идут через него. На нем нужно будет настроить политики и ровно так же, никакие собственные велики тут не помогут. Далее, вам нужно сделать горячую подмену бэка, т.е. когда ваш сервер пытается отдать запрос одному бэку, а тот жутко занят… делает сейчас большую работу. И нужно передать его кому-то другому. Это тоже делается через коды ответа сервера балансировщику. И балансировщик не теряя соединение с клиентом переключает бэк на горячую. Все жти тонкости нужно знать и понимать как они это функицонирует. Иначе ты становишься заложником «шаманских плясок».
Кармоеды, вы можете сколь угодно выражать свое «недовольство», это не меняет реальности. Это лишь еще больше утверждает, лично меня во мнении бессмысленности попыток кому-то что-то донести.
Это лишь еще больше утверждает, лично меня во мнении бессмысленности попыток кому-то что-то донести.

К сожалению, так оно и есть.
И нужно передать его кому-то другому. Это тоже делается через коды ответа сервера балансировщику

я там ниже про балансировщики писал, пока мы с Вами в полемику не вступили.
CDN, это не просто статика, тот же CloudFlare реализует сокрытие ваших серверов от конечного пользователя.

да, сорри, я забыл, что его можно и как реверс-прокси и защиту от DDoS использовать. Но тогда можно расширить проблематику. Потому что многие (заслуженно!) пользуются тем же Qrator'ом и понятно, что он не должен раскрывать IP целевого сервера, иначе весь смысл в защите теряется.

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


Их придумали глупые люди?

Их придумали не для REST API. Их придумали для унификации ошибок веб сервера, а вот уже намного позже придумали REST API где решили наложить логические ошибки на ошибки веб-сервера.


К примеру сервер отвечает 404. Сервер ответил 404 потому что такого URL не существует? Или сервер нашел ресурс но ответил 404 потому что разработчик реализовал бизнес логику через 404 статус? Вот его придумали для первого случая но не для второго.


Отсюда вывод что если пришел 404 то ты обязан посмотреть тело ответа что бы убедиться что это логическая ошибка а не инфраструктурная, доменная и т.п.


В двух больших проектах именно с этим был большой геморрой. Когда появился REST API и пошел хайп, архитекторы приняли вот такое гениальное решение использовать REST API для крупного проекта и наложить статус коды для передачи ошибок между серверами.
Но так как это было распределенное приложение в нескольких странах оказалось что датацентры компании настроены таким образом что они режут тело ответа 500-тых ошибок из соображений безопасности. Потому что изначально 500 отдавали когда произошел какой то непредвиденный сбой, и что бы скрыть тело от лишних глаз, никто не предполагал что 5xx как умышленный ответ. REST API проектировался для работы с сущностями. Соответственно нам попросту перестало хватать кодов для передачи разного типа ошибок.
Дошло до обсруда что решили делать гибридные ошибки. Если пришел 4xx, 5xx то ты еще должен доуточнить чтением отдельного кода в теле что именно за ошибка была


В другом проекте для школ оказалось что многие школы работают за прокси. Соответственно школьные прокси резали/меняли статус коды ошибок кроме 200 со всеми вытекающими последствиями.


Последнее время, так как теперь уже REST API прошел волну хайпа, с этими проблемами стало получше. Но все же надо знать матчасть и историю, прежде чем осуждать других


Мне реально, очень стыдно, в прямом смысле, за тех, кто минусует попытку автора поднять этот вопрос.

Не надо переходить на личности. Так можно сказать что "мне реально стыдно за тех кому стыдно за других хотя сам не разобрался в матчасти, не знает истории, нет опыта работы в больших проектах". Если вы правы то вас поймут и без перехода на личности

Сервер ответил 404 потому что такого URL не существует? Или сервер нашел ресурс но ответил 404 потому что разработчик реализовал бизнес логику через 404 статус?

А какая разница?


Отсюда вывод что если пришел 404 то ты обязан посмотреть тело ответа что бы убедиться что это логическая ошибка а не инфраструктурная, доменная и т.п.

А если пришло 200, то ты обязан посмотреть тело ответа, чтобы убедиться, что это вообще успешный ответ. Так, да?

Разница большая:
Надо осознавать что в REST API решили наложить логические ошибки на ошибки веб сервера не потому что было правильно смешивать ошибки сервера и ошибки приложения, а по одной очень простой причине — производительность.
Намного быстрее посмотреть статус и направить обработку в разные ветки нежели парсить ответ а потом реагировать.


Но за это в сложных сценариях приходится платить свою цену:


Допустим у тебя клиентское приложение словило, например, 400.
У тебя POST запрос большой структуры данных (где то 150 полей + 5 уровней вложенности). Тебе приходит 400. СХЕМА данных в принципе неправильно собрана? (В таком случае фреймворк даст 400). Или 400 был логический ответ на валидацию ЗНАЧЕНИЙ? В первом случае ошибка в логике приложения, допустили рассинхрон данных. Значит проблему нельзя исправить на стороне пользователя. Во втором случае надо провалидировать значения и попросить пользователя исправить ошибку.


В другом случае со школами пришлось перейти на 200 статус везде и парсить тело всегда. Заметно медленне это не стало работать. Но зато стало работать гораздо стабильнее и надежнее. И сильно сократился объем ошибок. И кроме того гораздо упростился разбор ошибок — если возникали ошибки подключения к серверу то было понятно что это гарантированно не логические ошибки приложения на сервере и/или клиенте и надо искать в инфраструктуре.

Разница большая:

Я что-то так и не увидел ответа, какая же разница, почему отдали 404.


У тебя POST запрос большой структуры данных (где то 150 полей + 5 уровней вложенности). Тебе приходит 400. СХЕМА данных в принципе неправильно собрана? (В таком случае фреймворк даст 400). Или 400 был логический ответ на валидацию ЗНАЧЕНИЙ. В первом случае ошибка в логике приложение, допустили рассинхрон данных. Значит проблему нельзя исправить на стороне пользователя. Во втором случае надо провалидировать значения и попросить пользователя исправить ошибку.

Ну так прочитайте тело ответа (вам все равно придется это делать, чтобы показать пользователю, какие ошибки исправлять), там все написано.


А вот если у вас нет пользователя, то можно ничего не читать.

Я что-то так и не увидел ответа, какая же разница, почему отдали 404.

Я уже не уверен что вы не придираетесь. Я же привел аналогичный пример про 400 который вы проигнорировали и все равно прицепились именно к 404.
404 это еще один пример.
Если url не существует, значит накосячили с URL. А в больших проектах их за 300+ и такое часто случается.
Когда API делают быстро очень часто случается лютый говнокод с API.
А в проекте решили заюзать аналог refit/retrofit что бы упростить работу с API. Может разработчик неправильно описал refit аттрибуты? Или же все правильно но просто запись не найдена? Может url переехал а приложение просто забыли обновить? А все ищут ошибку в базе данных. Это простешие проблемы связанные с ошибками с элементарных API. А когда API намного сложнее то и проблемы получаются гораздо изощеренее.


Смешивание кодов упрощает разработчику жизнь на этапе написания кода.
Но усложняет жизнь на этапе поддержки.
И чем больше проект и тем больше эти проблемы.


Посмотрите, к примеру, API практически всех крупных проектов. VK, Facebook и т.д. и т.п. Они отказались от статусов и реализовали свои коды ошибок не просто так.


Другой пример Twitter: https://developer.twitter.com/en/docs/basics/response-codes
Статус коды дополнены кодами ошибок приложения

Я же привел аналогичный пример про 400

… для которого я дал простой и понятный ответ.


Если url не существует, значит накосячили с URL.

Что такое "накосячили с URL"?


А все ищут ошибку в базе данных.

А зачем?


Но усложняет жизнь на этапе поддержки.

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


Другой пример Twitter: https://developer.twitter.com/en/docs/basics/response-codes

"The Twitter API attempts to return appropriate HTTP status codes for every request."

Ровно то, о чем я выше говорил.

Хорошо, какие аргументы Вы можете привести в пользу статус кодов если так и так ошибку приходится возвращать в теле? Зачем дублировать одну и ту же информацию дважды в двух местах? Я лично вижу только одно преимущество — производительность.


В том то и дело что иногда статус коды отменяют тело. Лет 10-15 назад с этим были большие проблемы у крупных компаний. Но постепенно настроили инфрастуктуру. Но даже сейчас не все проекты делаются для модных молодежных клиентов с современной инфраструктурой. Опять таки в приложении для школ в каких нибудь регионах/селах, админы приезжают за десятки километров и настраивают школьную сеть/прокси и т.п. У них все уже работает как надо. С фильтрами взрослых ресурсов и т.п. Ну у тебя выбор. Или перейти на 200 и добавить в приложении несколько лишних строк кода. Или ходить по школам просить купить ваш продукт и при этом требовать перестроить их прокси для работы вашего приложения в школе. Как думаете, какой выбор сделает бизнес?

Хорошо, какие аргументы Вы можете привести в пользу статус кодов если так и так ошибку приходится возвращать в теле?

Униформную обработку: если мне важно только то, успешный ли запрос (например, у меня нет никакого пользователя, который может что-то сделать), я могу воткнуть простейшее response.EnsureSuccessStatusCode() и быть уверенным, что если проверка прошла, мой запрос был успешно выполнен сервером. Мне не надо думать, какая конкретно структура у ответа, мне не надо вообще ее разбирать — одна тривиальная проверка. И точно так же униформно в трассировке: я могу воткнуть стандартный message handler, который будет писать мне успешность/неуспешность вне зависимости от того, какой с той стороны сервер, и какую очередную структуру данных он придумал.


Ну у тебя выбор. Или перейти на 200 и добавить в приложении несколько лишних строк кода. Или ходить по школам просить купить ваш продукт и при этом требовать перестроить их прокси для работы вашего приложения в школе.

… никогда не слышали словосочетания "токсичный клиент"?

я могу воткнуть простейшее response.EnsureSuccessStatusCode() и быть уверенным, что если проверка прошла, мой запрос был успешно выполнен сервером.

Вот главная причина на самом деле. Но опять таки это не аргумент. В приложении для школ мы сделали аналогичный метод расширение в духе response.IsSuccesss() который смотрел уже не на статус а на коды ошибок в теле.


Первая реализация был метод где то в 20 строк кода который позволял работать с 200 статусом ровно так же как могли работать со HTTP статус кодами. Эти 20 строк кода решило огромную бизнес проблему.
Но в случае ошибок это решение работало медленее так как приходилось второй раз десериализовывать ответ в объект с ошибкой.
А потом сделали вторую реализацию, не потому что первое решение работало медленее. Из соображений перфекционизма, где реализовали собственную логику десериализации, что бы десериализация делалась бы за один проход.
Оно было заточено конкретно под наше API Но работало так же быстро как со статус кодами. Но кода там было где то в 150-200 строк кода.

Вот главная причина на самом деле. Но опять таки это не аргумент.

Да нет, это как раз аргумент. То, что вам он не подходит — это ваше дело.


В приложении для школ мы сделали аналогичный метод расширение в духе response.IsSuccesss() который смотрел уже не на статус а на коды ошибок в теле.

И будете писать такой для каждого сервиса, с которым вы взаимодействуете, учитывая, что у каждого из них новый формат?

Да нет, это как раз аргумент. То, что вам он не подходит — это ваше дело.

Поправьте если я ошибаюсь: Проще говоря, Вы утверждаете что статус коды правильно будет смешивать с логикой, но если это нам не подходит то это уже наши проблемы?


И будете писать такой для каждого сервиса, с которым вы взаимодействуете, учитывая, что у каждого из них новый формат?

тут два варианта. Внешние сервисы которые написаны не вами. Тогда вам так и так придется пилить свое решение под каждый внешний сервис, потому как REST API в отличае от того же строго но тяжеловесного SOAP, дает очень много свободы и трактовки и каждый реализует как хочет. В таком случае так и так под каждый сервис приходится пилить свое решение.


Внутренние сервисы/микросервисы которые разрабатывают внутри компании. В таком случае у вас есть возможность договориться о едином формате (собственно рано или поздно всегда приходят к этому выводу и договориваются о том что бы придти к единому формату). В таком случае только один раз тюнится/надстраивается библиотека типа refit (C#)/ retrofit (Java) и его аналоги в разных языках, под то что договорились в компании и все команды используют эту библиотеку.


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


Допустим потребовался сервис который складывает два числа. Строгий SOAP просто не допустит разночтения.


А в REST одна команда может сделать в виде
GET /add/2/2


другая команда сделает
POST /add
{
"a": 2,
"b":2
}
Третья решит сделать еще
POST /operations
{
"add"


И т.д.


Так что в сложных задачах в REST практически всегда приходится договариваться о единых стандартах для всех команд

Проще говоря, Вы утверждаете что статус коды правильно будет смешивать с логикой

Нет, я утверждаю, что правильно отдавать статус-коды, соответствующие статусу запроса.


но если это нам не подходит то это уже наши проблемы?

Нет, если вам не подходит моя аргументация, это не повод не считать ее аргументацией.


Внешние сервисы которые написаны не вами.

Да, именно они.


Тогда вам так и так придется пилить свое решение под каждый внешний сервис

Для базовой проверки ошибок и мониторинга? Нет, если люди будут использовать стандартные коды.


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

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


В таком случае у вас есть возможность договориться о едином формате (собственно рано или поздно всегда приходят к этому выводу и договориваются о том что бы придти к единому формату). В таком случае только один раз тюнится/надстраивается библиотека типа refit

… а потом вы пытаетесь эту библиотеку запихнуть еще и в диагностический слой, который умеет собирать телеметрию по HTTP-запросам на уровне System.Diagnostics, и ваши запросы там почему-то видны, как успешные, хотя успеха-то и не было.


строго но тяжеловесного SOAP

В "строгом, но тяжеловесном SOAP" люди точно так же отдавали обратно стандартное сообщение с кодами ошибок внутри вместо soap:fault. Ничего не меняется.


Строгий SOAP просто не допустит разночтения.

Да легко допустит. Вы никогда не видели в SOAP три вложенных конверта, где операция указывалась в среднем из них?

Судя по System.Diagnostics вы, скорее всего, .NET разработчик. В .NET намного проще работать с REST API на статус кодах и сильно упрощает поддержку и разработку.


Как вы поняли, я тоже работал в нескольких проектах с REST API со статус кодами, так и с REST API на 200 статусе.


Я не говорю что статус коды однозначеное зло. Наоборот. в .NET мире гораздо проще работать с REST API на статус кодах так как и сервер ASP.NET и HttpClient заточены под это. Я всего лишь прошу не утверждать что 200 это всегда неправильно и за это надо кого то стыдить. Иногда гораздо проще делать решения на 200 тых кода как с точки зрения бизнеса так и с точки зрения поддержки легаси инфраструктуры. И обходится это гораздо дешевле чем переделывание легаси.


Но я могу понять вашу точку зрения так как, в отличае от других языков в .Net мире нет такого богатого выбора разных фремворков и клиентов.(точнее альтернативы и выбор есть но их популярность по отношению к базовым на уровне погрешности, так как базовых обычно хватает с лихвой).


Насчет примера с SOAP злоупотребления всегда зло.
Просто разница в том что в REST API по
GET /add/2/2
POST /add
нельзя сказать какое решение однозначно плохое а какое однозначно хорошее.
А в вашем примере с SOAP конечно же такое встречалось очень часто. Последний раз в прошлом году видел подобное. За редким исключением чаще всего можно было легко упростить решение. А в редких случаях использование SOAP было просто неоправдано в этом API.

Я всего лишь прошу не утверждать что 200 это всегда неправильно и за это надо кого то стыдить.

А я и не говорю, что это всегда неправильно, я говорю, что мне это всегда неудобно.


Просто разница в том что в REST API по
GET /add/2/2
POST /add
нельзя сказать какое решение однозначно плохое а какое однозначно хорошее.

Вы так говорите, как будто в SOAP можно.

я предлагаю закруглятся со спором. Или перейти в личку. Судя по всему кроме нас двоих остальной аудитории этой статьи наше обсуждение вообще не интересно. А мы уже заспамили большую ветку, что не очень вежливо :)

Хорошо, какие аргументы Вы можете привести в пользу статус кодов если так и так ошибку приходится возвращать в теле?

как минимум тот факт, что большинство 3rd-party инструментов работают со статус кодами. Например если вы логируете в какой-нибудь sentry — он сам будет отслеживать запросы на которые сервер вернул не 200. И таких инструментов и юзкейсов масса.
Изначально статус коды HTTP придумали именно как статус коды вебсервера, а не приложения как их используют в REST.

А если приложение является веб-сервером само по себе?

Особенно замечательно на фронте отлавливать ошибки с бэкенда, отправленные с кодом 200. Когда ты ожидаешь, что вызовется обработчик error (в случае jQuery) или catch (в случае с Promise), а он не вызывается! :) И начинается костылирование с if-ами на проверку поля с ошибками. В такие моменты думаешь, для кого придумали 403, 422, 500 в конце концов.

Расскажите нам, какими http кодами различать примерно 90 типов ошибок в рамках небольшого апи на 150 эндпойнтов?
Ну для начала начать внимательно читать, что пишут. Цитирую — «Код ответа, это унифицированное средство принятия решения на инфраструктурном уровне. Его нельзя заменить никакими json в ответах. Природа этого кода находится ЗА JavaScript.»
Это значит, что ошибка, должна возвращаться как ошибка с соответствующим кодом, а содержать расширенную информацию — кто запрещает? С передачей какой ошибки у вас трудности? Давайте разберем пример. Могу свой привести — запрос не прошел валидацию — ответ Bad request 400, в тело можете запихнуть любую информацию которую посчитаете нужным.
Ну давайте-ка.
Продемонстрируйте нам широту своих когнитивных способностей.

Транзакция не прошла по причинам
  • счет получателя не найден
  • счет отправителя не найден
  • недостаточно денег на счету
  • недостаточно денег на корреспондентском счету
  • превышен лимит операций
  • не получен ответ от провайдера
  • провайдер отключен


В http коды мне это все. Дабы консистентно.
И попутно, какие решения на инфраструктурном?(lol) уровне тут будут приниматься опираясь на эти коды…
Мдя… Ну давайте, продемонстрирую. И так, у нас есть классы ошибок. Ключевые тут это:
4xx: Client Error (ошибка клиента);
5xx: Server Error (ошибка сервера);

Давайте классифицируем:
Для 4xx:
  • счет получателя не найден (400 Bad Request)
  • счет отправителя не найден (400 Bad Request)
  • недостаточно денег на счету (406 Not Acceptable)
  • недостаточно денег на корреспондентском счету (406 Not Acceptable)
  • превышен лимит операций (406 Not Acceptable)

Для 5xx:
  • не получен ответ от провайдера (504 Gateway Timeout)
  • провайдер отключен (503 Service Unavailable)


Все ответы могут содержать дополнительные сведения в JSON.

Но, если вы хотите все это запаковать в один ответ, то я бы отдавал 400 и JSON с перечислением. А вот, если бы были проблемы класса 500, отдавал бы только их.
счет получателя не найден (400 Bad Request)

Точно 400?
И почему вы решили что это ошибка клиента-то?
Может счет был, а потом его закрыли… внезапно бывает и такое…

недостаточно денег на счету (406 Not Acceptable)

406 оно вообще о другом-то…
Вы счастливый любитель такого стиля?
#define TRUE FALSE //Happy debugging suckers


недостаточно денег на корреспондентском счету (406 Not Acceptable)

А это проблемы не клиента, но шлюза…
Все еще 406?

провайдер отключен (503 Service Unavailable)

А теперь на минуточку, у нас отключен не весь наш сервис, а только один из провайдеров через которые он работает.
Вы радостно 503 шлете? Шикарно…

Вы понимаете насколько вы плаваете и насколько геморройно запаковать все в http коды?
И для мониторинга массовые 406/503 ни разу не показатель источника проблем

Что частично решило бы — так это кастомные http коды и мониторинг по ним…
Но, во-первых, мы шлем в одно место все стандарты, во-вторых, это ничуть не лучше парсинга стандартного формата ответа в системах мониторинга.

Что остается?
Ну… остается завернуть все в два-три общих кода 2хх,4хх,5хх
А настраивать мониторинг все же по специфическим внутренним(!!!) кодам ошибок, которые будут в теле ответа.

И вот владея этим сакральным знанием, ответьте…
Где же консистентность?
И зачем тогда http код, если использовать его мы не можем от слова вовсе?
Более того мы еще и маскируем ошибки уровня инфраструктуры и уровня приложения выводя ошибки уровня приложения в http коды…
Сове больно. Очень больно. Зря вы ее так. Думаю вас только жизнь и сопутствуящая боль убедит, коей у вас явно достаточно. Мой пример совершенно достаточен. Все, кому нужно сделают верные выводы.

Я бы не согласился с выбором кодов для 4хх, хотя согласен с 5хх, но только в случае если провайдер единственный или указывается в параметрах ссылки. Иначе правильнее может быть вернуть 422, несмотря на то, что проблема с сервером, по причине того, что обращение идёт к ресурсу, а сам ресурс доступен, проблемы с одним из параметров ресурса.


А по 4хх — 406 не совсем о том, я бы первые 4 сделал 422, а последний 429. Впрочем всегда подходит вариант возвращать только 400.

Что касается категоризации, она кристально прозрачна. Ниже я уже описал, не буду повторяться — habr.com/ru/post/440382/#comment_19765776.

Да, 400 универсальный. И в большинстве случаев достаточен. Если не нужно не требуется выделять класс ответов. Но я бы выделил в этом случае именно для перелимитов. Не важно каких.
422 ммм… ну в целом, вай нот?
По сути, эти коды описываются в манифесте проекта и зачастую продумываются при проектировании системы. Далее они становятся универсальным средством коммуникаций для разных направлений (разрабочик/devops/админ/клиент).
Видимо поэтому крупные компании и шлют все с одним статус-кодом (привет GraphQL).

Собрались люди с over 10000 часов опыта, т.е. профессионалы. И спорят о том как натянуть ужа на ежа. У каждого свое мнение, когда использовать 406, 422 или 429. А попробуйте объяснить все это джуну.
Если уж принято использовать статусы по полной, то джуну надо объяснять на уровне постановки задачи какое исключение или его аналог бросать в случае ошибок, а преобразование его в статус на инфраструктурный уровень возложить. А во многих случаях даже исключение будет автоматом бросаться, джуну только вызвать метод типа validate() или getById().
Ну а кому поможет это преобразование кодов?

Разработчику клиента? Ну разве что в самых простых случаях, где надо проверить булевский признак Ok / не Ok. Вот пришло нам 404. Половина клиентских библиотек для работы с HTTP бросят exception. Половина нет. А на самом деле надо допустим обработать сначала заголовок contetn-type: если html или вообще нет body — это действительно неустранимая ошибка, а если json — это ошибка логики и с ней надо работать. Получается, что мы делаем не меньше работы, а больше.

Разрвботчику сервера? Ему теперь вместо описания бизес-логики нужно холиварить с коллегами про коды HTTP и поддерживать две системы передачи ошибок.

Админу инфраструктуры? Тут высказывалось такое мнение, что на эти коды из REST будут смотреть админы и что-то там делать. Но кмк, админы скорее скажут спасибо, если мы настроим нормальное логирование и мониторинг с помощью тех же ELK. Т.е. решим задачу теми инструментами, которые под это заточены. А не будем перекладывать свои проблемы на соседний уровень абстракции
И потом в этих системах логирования придётся перечислять весь список интересуемых ошибок для фильтра или алерта вместо того, чтобы указать 1-2 кода.
И что? Может быть еще куча других вещей, которые не подпадают под HTTP Error code. И которые нужно мониторить. Это же не повод вообще отказываться от логгирования ошибок и алертинга на базе этого?
Ну graphql в общем-то не является http-only api, поэтому там и не придают значение данным кодам.

Ну и с проблемами объяснения джуну как-то не сталкивался.

Я бы рекомендовал возвращать 400 только для тех ошибок клиента, которые в корректно написанном клиенте происходить не должны вообще. Иными словами, "недостаточно денег на счету" может являться ошибкой 400 только при условии, что клиент может гарантированно знать достаточно денег или нет ещё до вызова операции (т.е. например клиент имеет возможность заблокировать счёт, выяснить текущий баланс, и только потом вызвать операцию перевода средств, после чего разблокировать счёт — причём именно такое поведение клиента является штатным и ожидаемым от корректно реализованных клиентов). В остальных случаях, когда клиент не может заранее знать корректные он передал параметры или нет — 400 тут не подходит. Причина такого подхода в том, что если мониторинг обнаруживает 400 мне хочется быть уверенным, что это означает наличие бага в клиенте (реже — в сервере), который нужно найти и пофиксить.


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

Ошибка клиента, это любая ошибка, которую он допустил при обращении к серверу. А 500 это то, что сервер допустил. Для начала, нужно разобраться в сути кода. И вопросы отпадают сразу. И мониторинг не отслеживает 400. Точнее это делает не инфраструктурный мониторинг.
Наверное шире раскрою мысль — код 400 это не баг. Это некая ошибка или неверные действия допущенные клиентом. Как к примеру 404 не баг, а просто неверный url в распространенном случае.
Код 500 это тоже не равно баг. Да, чаще всего при падении сервиса отдается 500 код, но что он значет? Сервис недоступен из-за бага? Где это указывается в спецификации? Сообщается, что сервис недоступен. Все. Да, иногда с ним приезжает трейс, ну так это уже только подтверждает верность пути — сообщай ошибку и сопровождай контентом. Кастомные коды тоже вполне рабочий вариант, но добавлять их имеет смысл только в случае необходимости принятия инфраструктурных решений. Ну например, мы как-то использовали кастомный код делегирования обработки запроса. Когда сервер отдает балансировщику ответ, типа «с этим запросом лучше справится тот-то». На его основе выстраивалась нетривиальная система коммуникаций. При этом, для клиента, это был тот же REST. В остальных случаях, прекрасно хватало стандартных кодов.

500 — это необработанная ошибка на сервере. Если ошибка обработана, в большинстве случаев это что-то из 4хх. Некоторые из 5хх можно кидать в случае обработки, семантически это будет верно, но есть вероятность, что 5хх придёт от веб сервера, поэтому клиенту придётся проверять content-type и другие вещи, чтобы различать их, поэтому в общем случае строить взаимодействие на 2хх/4хх лучше.

Откуда такая инфа? Пруф пожалуйста. Что она необработанная. Сам респонс подразумевает, что она обработанная. developer.mozilla.org/ru/docs/Web/HTTP/Status/500

Да, может прийти и от WEB-сервера 500. И что? Да, придется разбирать. А с 200 не придется? С 400?:))) Тут как говорится или штаны надень или в баню зайди :))) Напомню, что и web-сервер может отдавать тот контент 500 который ВАМ нужен. Поэтому, ваше приложение получит вменяемый ответ в любом случае. И сможет его распарсить.
400 лучше? Вы услышали корень причины, почему нужно отдавать 500? Она сигнал админам — что-то в системе не так. Чинить! Срочно! Даже мой пруф чекто говорит об этом.
Именно из-за подмен и «детских травм» (тут я не обижаю, а просто напоминаю, что 500 ошибка это первое что WEB разработчик вообще встречает, а код 200 сразу воспринимает как благость снизошедшую), многим свойственно не трогать коды 500. Но это не верно, это такой же инструмент, как и любой другой код.
сервер столкнулся с неожиданным условием, которое помешало ему выполнить запрос

Ну так по ссылке и написано. Если условие неожиданно, то и обработать его не получится, так как код обработки отсутствует. Под обработать я подразумеваю что-то большее, чем просто поймать исключение и выдать стандартную ошибку. Потому что в случае обработки скорей всего это будет либо 4хх, либо какие-то 5хх, но не 500.


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

Все верно, ровно 500 код зарезервирован для случая непредвиденной ошибки. Но все же обработанной. Ваш упавший код обрабатывает… ну не знаю… PHP к примеру. Неожиданно? Да. Обработано? Да. Необработанная это просто нуль ответ.
Но на 500 же не заканчивается? Я в общем-то в примере ее и не использовал, а использовал 503 и 504. Что в данном случае, вполне себе логично.
Что касается четкого разграничения между 400 и 500, повторюсь они есть:
4xx — любая ошибка (не баг, а ошибка) клиента;
5xx — любая ошибка (не быг, а ошибка) сервера.
Например, проблемы межсерверного взаимодействия (обращение к шлюзам), это явно ошибка сервера. И уж точно не клиента.

В контексте Вашего ответа на мой комментарий речь шла не о 4xx а конкретно 400. Но моя рекомендация равно применима и к 500. А именно:


  • код 400 должен возвращаться только если сервер думает, что в клиенте есть баг — т.е. клиент прислал такой запрос, который корректный клиент присылать не должен
  • код 500 должен возвращаться только если в сервере есть баг

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


В определённых условиях некоторые другие ошибки могут иметь тот же смысл — например, если сервер предоставляет всего несколько доступных url (типично при использовании RPC), то код 404 так же будет означать баг клиента, потому что клиент должен вызывать только эти несколько url, и они все всегда должны существовать. А вот в случае REST url достаточно динамичны, они появляются и исчезают в процессе CRUD ресурсов, так что 404 превращается в самый обычный код, часто возникающий при абсолютно штатной работе клиента.


Так же есть граничные случаи, когда ошибка 500 может и не означать наличие бага в коде сервера — например, если на сервере кончилась память, это обычно приводит к падению сервера и 500. При этом это далеко не всегда вызвано багом в коде (напр. создавшим утечку памяти вызвавшую эту проблему), иногда это некорректная конфигурация сервера (ему надо предоставить больше памяти). Но суть это не меняет — в любом случае мониторинг должен среагировать на 500 уведомив разработчиков/девопсов/админов, и те должны что-то сделать на сервере чтобы решить проблему — не важно, изменить код или добавить памяти.


Предложенный подход навязывает ограничение, в каких конкретно случаях можно и нужно использовать коды 400 и 500, и это ограничение создаёт новую возможность — уведомлять команду о наличии бага в коде/конфигурации клиента/сервера посредством любого инструмента мониторинга кодов HTTP.

Ссылки на пруфы плз. Откуда вы набрались таких утверждений. Без них, разговор не имеет смысла. Я как попугай даю ссылки, поясняю а мне свое мнение продолжают впихивать. Уважайте собеседника пожалуйста.

Пруфы на что конкретно? Каких именно утверждений? Что существуют системы мониторинга, с алертингом на определённые события (напр. обнаружение HTTP ответов с кодами 400 и 500)? Что события намного проще и эффективнее обрабатывать, если их легче различать (т.е. когда код 400 означает ровно одну определённую ситуацию "баг в клиенте", а не общую группу "любая ошибка клиента, включая возникающие при штатной работе клиента")? Это вполне очевидные вещи, разве нет?


И что значит "набрался"? Я нигде ничего не "набирался", мне хватает собственных 30 лет опыта программирования и здравого смысла чтобы рекомендовать такие простые идеи. Любые пруфы — это тексты написанные ровно такими же людьми — вон я чуть ниже упоминал ересь в русской вики, причём с пруфом в виде какой-то книжки — и что, наличие "пруфа" сделало из этой ереси достоверный факт? Нет, не сделало.

Вот на эти:

  • код 400 должен возвращаться только если сервер думает, что в клиенте есть баг — т.е. клиент прислал такой запрос, который корректный клиент присылать не должен
  • код 500 должен возвращаться только если в сервере есть баг


Ясно, т.е. это Ваше ценное мнение. Ok. Не вопрос. Мое тоже оооочень ценное. Но мое ооочень ценное, подкреплено еще и пруфами. И я повторяю, код 400 это ошибка не в смысле баг. А в смысле клиент сделал что-то неправильно. И если он прислал какой-то запрос, который хотя по параметрам и провалидирован, но некорректен по сути, то это его ошибка. Т.е. код 400. Про 500 выше много написал. Ссылки там же.

P.S. Не читайте русских газет и русских вики. Есть первоисточники.

PPS Баг на клиенте кода ответа сервера вообще не может иметь по природе. Это нонсенс (выберу это слово).

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


Насколько я понимаю, Вы настаиваете на том, что по стандарту HTTP статус 400 означает любую ошибку клиента, в т.ч. такую, которая может возникать при штатной работе клиента, и не желаете ограничивать использование этого статуса только багами клиента. В принципе, я с этим пока не спорил, я говорил немного о другом: не так важно насколько Ваш подход корректен с точки зрения формальностей спецификации, гораздо важнее то, что на практике полезнее сузить соответствующие коду 400 ситуации до багов клиента. Далее я привёл аргументы, в чём польза рекомендуемого мной подхода, и от Вас жду встречных аргументов с его недостатками — ссылка на спеку таковым не является.


Кстати, о пруфах. Мало того, что Вы в качестве пруфа привели ссылку на сайт мозиллы вместо оригинальной спеки, так ещё и на его русскую версию, которую сами же только что критиковали в "P.S." Если вместо этого обратиться к первоисточнику, как Вы же сами и рекомендуете, то там написано:


The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

Заметьте, это сильно отличается от текста указанного Вами "пруфа":


Код состояния ответа "HTTP 400 Bad Request" указывает, что сервер не смог понять запрос из-за недействительного синтаксиса. Клиент не должен повторять этот запрос без изменений.

Что характерно, все упомянутые в первоисточнике примеры "ошибки клиента" — malformed request syntax, invalid request message framing, or deceptive request routing — однозначно являются именно багами клиента, при штатной работе клиент такие запросы отправлять на сервер не должен. Более того, фраза из Вашего пруфа "Клиент не должен повторять этот запрос без изменений." так же показывает, что код 400 не должен использоваться для таких ошибок клиента, которые могут возникать при его штатной работе — вроде попытки перевести больше денег чем доступно сейчас, потому что хотя сейчас параметр с суммой для перевода некорректный, через минуту денег на счету может стать больше, и при повторе этого же запроса клиентом он будет выполнен сервером — т.е. требование "не повторять запрос без изменений" теряет всякий смысл.


P.S. Не читайте русских газет и русских вики. Есть первоисточники.

Поскольку Вы это написали в ответ на мою ссылку на русскую вики в другом комментарии, то я обращаю Ваше внимание на то, что я в том же комментарии уточнил, что в английской вики той ереси нет — что как-бы должно было намекнуть Вам, что я читаю не только русскую вики.


PPS Баг на клиенте кода ответа сервера вообще не может иметь по природе.

Нда. Типа, не верь глазам своим, да? Баги на клиенте бывают разные. Такой баг клиента, который приводит к отправке некорректного запроса на сервер, при условии что сервер в состоянии определить некорректность этого запроса, вполне может иметь (и имеет) свой код ответа на сервере.

то, что на практике полезнее сузить соответствующие коду 400 ситуации до багов клиента

Да как же… я не понимаю как вообще можно об этом говорить? Вы понимаете, что код ответа это то, что отвечает сервер? Каким образом сервер может отвечать за клиента, за его баги? Как вообще это можно принять как вариант? А если баг клиента в том, что он не отослал запрос? Это тоже код 400? А если он отослал запрос и все верно, а не должен был? Это тоже 400? Ума не приложу, неужели это нужно пояснить? Это очевидно!

Заметьте, это сильно отличается от текста указанного Вами «пруфа»:

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

malformed request syntax, invalid request message framing, or deceptive request routing — однозначно являются именно багами клиента

Ничего подобного. Опять очевидная вещь, которую нужно зачем-то доказывать… ну да ладно. А если у вас валидация идет средствами бэка? Ну не делается валидация на клиенте. А если к вам ломится внешний клиент, на ваш сервер и не делает валидацию. Это баг клиента? Нет. Это нормальное, штатное поведение. Но ответ при этом — 400. Ты плохо себя ведешь клиент. Ты ошибся.
Такой баг клиента, который приводит к отправке некорректного запроса на сервер, при условии что сервер в состоянии определить некорректность этого запроса, вполне может иметь (и имеет) свой код ответа на сервере.

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

Окей. Объясняю медленно. Внимательно следите за руками, чтобы потом не обвинять в мошенничестве. :)


На клиенте может возникнуть море самых разных ошибок.
Некоторые из них являются багами клиента.
Некоторые из этих ошибок (не только багов) могут привести к отправке на сервер запросов, некорректных с точки зрения сервера.
Часть таких запросов, с точки зрения сервера, может быть послана корректно работающим клиентом, а часть — не должна посылаться корректно работающим клиентом.
В последнем случае сервер и оказывается в состоянии определить факт бага на клиенте, что даёт ему возможность вернуть 400 только в этих случаях — если это кому-то нужно (например есть желающие получать алерты от системы мониторинга о конкретно таких ситуациях).


Пара примеров для наглядности.


  • Клиент прислал REST запрос, в котором отсутствует обязательный параметр. Корректно реализованный клиент не должен присылать такие запросы ни при каких условиях. Серверу не сложно определить факт отсутствия обязательного параметра. А значит он может вернуть 400, что приведёт к моментальному информированию разработчиков об имеющемся баге в клиенте.
  • Клиент, согласно требованиям сервера, может быть обязан делать валидацию ввода юзера, а может не быть обязан её делать — это определяется требованиями конкретного проекта. Если клиент присылает запрос с некорректным значением, которое он в принципе мог провалидировать сам перед отправкой его на сервер, то реакция сервера определяется требованиями проекта: если проект требует валидацию на клиенте, то, очевидно, получение сервером некорректного значения означает баг клиента, и сервер возвращает 400, а если проект такого не требует, то сервер возвращает не 400 (как вариант — 200 или 422).

Насколько полезно вообще всем этим заморачиваться — зависит от проекта и команды. По сути код 400 даёт возможность автоматически отлавливать некоторую часть (возможно — пренебрежимо малую, а возможно — и вполне значительную, это сильно зависит от того, кто и как писал код клиента, и насколько хорошо спроектировали API сервера) ошибок клиента ценой специальной реализации этой фичи (различения и особой обработки запросов, которые корректный клиент присылать не должен в принципе) на сервере.

Я думаю дебаты нужно прекращать. Я какого-то… непонятного… читаю ваши сообщения полностью. Пытаясь найти в них смысл, вы же, явно пробегаетесь по вершкам. Чую только первые абзацы читая. Так, к сожалению, ничего путного не выйдет. Вы ни на один кейс приведенный мной ничего не противопоставили. Да и не сможете.

Ваши попытки прикрутить серверу несвойственную ему роль не то что неверны, они фатальны для проекта. Вы притягиваете к своей логике частные случаи, забывая, что фронт не что иное как условность. Любой, в любой момент может от вашего REST попросить что угодно. в любом варианте. И любой разработчик бэка не будет пытаться свалить на фронт то, что он получил невалидированный запрос. Фронт фронтом, а бэк бэком. Повторюсь еще раз — для бэка фронта нет. У него есть клиенты. И ответ 400 будет любому, кто спросит у него ерунду. Никаких иных вариантов нет. Нельзя сделать так, чтобы бэк контролировал фронт. Просто — НЕЛЬЗЯ. И выделять в ответы сервера 400 как особый случай и пытаться искать баги — НЕЛЬЗЯ.

На этом, я считаю, в достаточной степени проясненной свою позицию. Перспектив найти консенсус я не вижу. За сим откланиваюсь.
Про баг на клиенте: в команде может быть принято соглашение, что 400-е (или конкретно 400) ошибки сервер возвращает только в случае неправильно сформированных запросов, которые клиент не должен делать согласно API. А на некорректные данные (ошибки валидации и т. п.) возвращается 200.
Вот… нати здрасти, давайте по новой :))) Не, пасиб, все выше написано. Я утомлен. Всем пока.

Позже напишу все же статью по этой теме. А то каждый раз, в очередном проекте приходится проходить путь «самурая».

Крамоедам — привет;)
Ах… да… про инфраструктурный уровень. Значит, группа «диких» админов, завидя в своей системе мониторинга ответы с 500 кодом (на которые у них стойка с рождения) резко оживляется. Видит значит 504 и 503 коды, смотрит в них и начинают все чинить. Если конечно им это по силам, а если нет — передают проблему. Но главное, что не нужно делать никаких великов очередных по уведомлению админов о проблемах. Профит.

Так и не надо все девяносто типов пытаться впихивать в HTTP-коды. Можно завести несколько «опорных» HTTP-кодов для ошибок (типа 400, 422, 403 и пр., чтобы фронтенд это дело корректно обработал), но при этом в теле ответа указывать более конкретный код ошибки. В таком случае, его и юзеру при необходимости можно будет показать.

Я про это и писал — что можно сделать опорные…

Но выглядит это мягко говоря неконсистентно. Хотя и может быть чуть удобнее для мониторинга по «группам», впрочем rpiontik у нас с Венеры, у него мир особый…
в вакууме.
Особенно учитывая факт, что HTTP ошибоньки далеко не все случаи покрывают, а так же использовать один универсальный код тоже ничего хорошего не даёт.

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

НЛО прилетело и опубликовало эту надпись здесь
Я пытаюсь понять аргументы по обе стороны. Аргументы за то, чтобы использовать 200 только когда всё прошло ок и аргументы тех, кто за то, что 200 это просто 200, а дальше дело приложения, что отвечать.
НЛО прилетело и опубликовало эту надпись здесь
Да, есть люди, которые которые с вами согласны. Они считают, что HTTP протокол отдельно, а приложение и его бизнес логика отдельно.

Я почему-то всегда думал, что в REST API мы должны полагаться на HTTP status code и не только полагаться, но и сами правильно возвращать нужный код со стороны backend.

Корни этой проблемы в том, что REST в какой-то момент стал настолько модным, что на нём начали делать абсолютно всё.


Да, в случае REST мы должны ошибки возвращать соответствующими статусами HTTP, но REST предназначен для управления "ресурсами", над которыми выполняется ограниченный набор CRUD-операций с жёстко определённой семантикой. Статусы HTTP вполне подходят для этих операций, и необходимости возвращать дополнительные подробности ошибок через JSON в теле ответа для этих операций обычно не возникает.


Но запихнуть функционал всех веб-сервисов в рамки CRUD-операций над "ресурсами" не так просто — именно по этой причине и существует RPC. (Кстати говоря, в текущей версии русской википедии про REST написана ересь "REST является альтернативой RPC[1]." со ссылкой на какую-то русскую книжку 2012 года — в английской версии статьи таких глупостей нет.)
REST это не альтернатива RPC. REST это очень ограниченное подмножество RPC, ценное тем, что добавленные ограничения создали новые возможности: возможность кеширования некоторых ответов существующими прокси-серверами, возможность автоматически повторять безопасные/идемпотентные запросы, возможность получать доступ к некоторым ресурсам через обычный браузер…


В результате во многих проектах складывается ситуация, когда они выбрали использовать REST для реализации API, в то время как им на самом деле нужен RPC. И вот в этот момент и возникают описанные в статье ситуации, с пакетными запросами, с возвратом ошибок в JSON со статусом 200 OK, etc. RPC штука более универсальная чем REST, и для возможных ошибок RPC существующих статусов HTTP уже не хватает — отсюда и потребность возвращать ошибки в JSON.


Что касается выбора статуса 200 для возврата ошибок — здесь нет хорошего решения. Оба варианта плохи: при использовании 200 нарушается семантика и могут быть проблемы на уровне HTTP, а при использовании других кодов логика возврата и обработки ошибок размазывается между слоем приложения и HTTP. Правильное решение этой проблемы обычно уже не является приемлемым после релиза: использовать RPC (например — JSON RPC 2.0) вместо REST, раз уж функционал этого сервиса не укладывается в ограничения REST.


Мораль сей басни: используйте REST если фичи вашего веб-сервиса ограничиваются CRUD и вам реально нужны те возможности (кеширование, идемпотентность, etc.), которые даст REST — а в остальных случаях используйте RPC. И тогда описанные в статье проблемы не возникнут.

Есть множество кодов, которые означают что при обработке что-то пошло не так. Мир не ограничивается кодами 200, 404 и 500.
Например, интересные варианты:
«202 Accepted — запрос был принят на обработку, но она не завершена.»
«401 Unauthorized — для доступа к запрашиваемому ресурсу требуется аутентификация.»
«409 Conflict — запрос не может быть выполнен из-за конфликтного обращения к ресурсу.»
«421 Misdirected Request — запрос был перенаправлен на сервер, не способный дать ответ.»

В некоторых случаях можно однозначно кодом ответа сообщить о том, что произошло с запросом на стороне api. В некоторых — стоит добавить в ответ тело с описанием ошибок\результатом запроса. Но отвечать «200, error» на мой взгляд плохая практика. В любом случае, если создаётся API которым в теории может пользоваться кто-то ещё, кроме разработчика — хорошим тоном будет создать документацию, и отдавать её по одному из адресов(например, в корне api)
Ну опять же, разделение ошибок приложения и протокола — хорошо. Но если у вас с протоколом всё хорошо, отработали корректно, но не прошли валидацию данных на уровне приложения — 200 + тело ответа со статусом, описанием и т.д. Приложение то отработало, протокол отработал, почему должно быть не 200?
А что тогда должно произойти, чтобы вернулось 400? Я пытаюсь понять ваш подход.
Оборванный на середине json в запросе
Например, можно отдать 400 если входные данные не прошли валидацию парсера на предмет попыток сломать систему (иньекции и прочее). Сервис же может быть и публичным, верно?
Проблемы с сервером, как правило, относятся к группе 5xx. Т.е. все ошибки 4xx — это ошибки приложения. Способ дать понять оппоненту, что запрос не привёл к желаемому результату. Понятно, что на все варианты кодов не напасёшься, но они позволяют экономить трафик(малоактуально, но всё же) и время разработчиков которые будут пытаться понять что вы им ответили. В целом, если я стучусь на стороннее апи с какой-то целью, 200 это сигнал что всё хорошо, и в тело ответа можно не смотреть.

Интереса ради сейчас потыкал в апи яндекса. На синтаксическую ошибку они ответили 400, на кривой токен — 401. Это не отменяет дополнительной информации в теле ответа, но даже не глядя в него я уже вижу, что не так с запросом.
Вот вам пример. Скажем у нас есть REST апи, мобильное приложение которое это апи использует и сервис контроля ошибок типа prismic (sentry, loggly, не важно..)
Предположим что в последней версии сервера и приложения поменялось ожидаемое тело реквеста нa один из эндпоинтов, но старая версия приложения все еще шлет запрос в старом формате, а мы про это забыли…
Тут сервис контроля ошибок начинает вам алертить что ваш сервис начал слать кучу 400 респонсов на запросы на этот эндпоинт. Вы туда заходите, смотрите тело реквестов и… Семен Семеныч! Ошибка найдена и исправлена. Без лишних телодвижений.

Фишка статус кодов именно в их «унифицированности». Все знают что такое 400, 401, 403, 404, 500 и т.д. Детали, если они есть, можно и нужно отправлять в теле респонса.
REST разумеется не идеален, но по крайней мере его философия консистентна.
Ужасно когда работаешь с несколькими апи и у каждого из них свой велосипед на то как отдавать ошибки.

Какой код должен быть на ошибку "Поле "количество" должно быть больше 0"?

400. Bad request.
400 Bad request. The server cannot or will not process the request due to an apparent client error (e.g., malformed request syntax, size too large, invalid request message framing, or deceptive request routing).

Какому из этих пунктов соответсвует данный случай согласно букве или духу закона определения?
А почему запрос плохой? В запросе пришло неправильное название поля? Правильное. Размер большой? Вроде, ок.
Может это проблема не в запросе, а в контенте, который не относится к правилам оформления запроса?

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

Как это не может? Может. Вот проверил, не упал. Не разделил на 0 без проверки, а ответил "сорян, тут нельзя такое".

в стандарте даны примеры, которые не являются исчерпывающим списком.
Обычно отправляют 400 Bad Request и детали ошибки где-то в теле ответа. Но я за то, чтобы отправлять всегда 200 + код ошибки с деталями в теле. Еще приложение может отвечать кодом 500, когда совсем все плохо и нельзя корректно обработать запрос (ошибка в коде, отвалилась база данных и т.д., если мы это не обрабатываем в приложении). Ну и 50x коды может отдавать сам веб-сервер, если упало само приложение.

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


Правильный статус на такую ошибку — 422 Unprocessable Entity.


The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions.

Статус 400 (Bad Request) означает, что в теле запроса находится абсолютно не то что ожидает сервер. Например вы сообщили ему в Content-Type что передаёте JSON, а в тело запихнули XML или просто не валидный JSON.

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

Правильный статус на такую ошибку — 422 Unprocessable Entity.

Это довольно забавно звучит, учитывая то, что 422 код — не входит в RFC и не является специфицированным (sic!) кодом ответа. Вместо того, чтобы тыкать носом «большинство не читающих RFC» — можно было начать с самого себя :D

P.S. 422 — это код из спецификации WebDav сервера, если что. И да, его действительно сейчас активно используют для подобных вещей, хотя его в стандарте нету.
НЛО прилетело и опубликовало эту надпись здесь
Я тоже могу позанудничать: <zanuda-mode-enabled>Это не HTTP RFC, про который автор комментария говорит и который, судя по всему, нужно читать и знать наизусть всем. Какой он там последний и самый современный RFC 7231?</zanuda-mode-enabled>
Да, я согласен что это RFC для расширения HTTP, и я видимо не совсем в этом плане был корректен. Но в целом не так важно в каком RFC это находится, важнее то что все эти RFC делаются и контролируются примерно в одном месте. И значит завтра не появится RFC на HTTP 4.0 в котором будет код 422 и он будет означать что-то другое.
Другой важный момент в самом наличии этих RFC и других, более «попсовых», ресурсов с документацией (например MDN и Википедия) в которых всё это задокументировано. Наличие такой публично доступной документации — очень большой плюс. Как минимум это облегчает «муки» выбора — всё уже придумано до нас, и очевидно не совсем глупыми людьми.
… хорошим тоном будет создать документацию, и отдавать её по одному из адресов(например, в корне api)
полностью поддерживаю! это best practices и очень удобный, кстати
А вот результат обработки запроса это уже совсем другое.


По-моему, 4хх как раз и покрывают результаты обработки, начиная с обычного 400 (у вас кривой запрос), заканчивая 422 (ошибка валидации) или 418 (на беке чайник вместо сервера).
Всё-таки 4xx покрывают скорее ошибки валидации (приема) запроса, а не результаты обработки.
Ныне 422 для семантики используется, мол: «всё с запросом в полном порядке, но значение Х должно содержать, например строчку в формате [a-z0-9]+».

С другой стороны — это всё философия, по-моему спорить на эту тему, где именно кидаются такие ошибки можно бесконечно.
Мне кажется, это все еще ошибка валидации. Т.е. это семантика, но семантика запроса, а не БЛ.
Но её выкидывает уже поднятое ПО, а не сам сервак. Ну, например, «логин должен быть уникальным». И прочее в этом роде.
Да, но логически это валидация запроса.
Ну это может быть и состояние, например: «Вы не можете отредактировать сообщение, т.к. аккаунт заблокирован». Считается ли это всё ещё валидацией запроса?

P.S. С другой стороны — это уже 403 ошибка, но всё равно 4xx.
Имху, надо разделять уровень транспорта и уровень бизнес-логики.

Ответ может придти (допустим, тот самый 200 OK), а может возникнуть исключение потому что сеть недоступна или ещё по десятку причин.

Внутри ответа бизнес-логика скажет детали ответа и там может быть json структуры, которая уже более детально и конкретно расскажет что не так. Может быть там будет пара ключ-значение (для каждого переданного значения, например id картинки — статус обработки), а может быть и что-то более нестандартное.
Возможно что-то смешалось в моей голове, но я думал всегда, что когда мы проектируем REST API, то мы и статусы должны использовать в соответствии с тем, что у нас произошло. Если мы не смогли обработать запрос, то это 4хх или 5хх.

По поводу разрыва соединения я как-то раньше не думал.
4xx — запрос принят, но при обработке произошла какая-то ошибка. Надо смотреть код, тело ответа если есть, отправленные данные и заголовки. 5xx — ошибка сервера, тут вообще как правило нет смысла искать подробности в теле ответа, но по коду можно понять, что не так с сервером, и стоит ли повторять запрос сразу или, например, подождать.
Ну давайте рассмотрим такой кейс. Есть ресурс /api/task/{id}, вы отправляете запрос и получаете 404. Что это значит, что такой урл вообще неизвестен серверу или что запрос был принят, обработан, но task с таким id не существует?
Решил я сходить почитать про 404.

6.5.4. 404 Not Found

The 404 (Not Found) status code indicates that the origin server did
not find a current representation for the target resource or is not
willing to disclose that one exists. A 404 status code does not
indicate whether this lack of representation is temporary or
permanent; the 410 (Gone) status code is preferred over 404 if the
origin server knows, presumably through some configurable means, that
the condition is likely to be permanent.

В совокупности с вот этим stackoverflow.com/a/9930716
404 is just the HTTP response code. On top of that, you can provide a response body and/or other headers with a more meaningful error message that developers will see.

можно предположить, что статуса мало и нужно смотреть ответ и заголовки.
401 — авторизуйтесь чтобы просматривать ресурс.
404 — ресурса с таким айди нет.
В случае если такого метода апи нет, возможно имеет смысл отдавать ответ 400, хорошим тоном будет так же добавить тело ответа с ссылкой на актуальную документацию по апи.

Есть ресурс /files/abc.jpg, вы отправляете запрос и получаете 404. Что это значит, что такой урл вообще неизвестен серверу, или что запрос был принят, обработан, но файла с именем abc.jpg не существует?

А вы собираетесь как то по разному обрабатывать случай, когда нет файла или когда такой урл не известен? В данном случае, это одно и тоже.

Да, собираюсь. Но вообще это не ко мне вопрос, а к DSolodukhin.

НЛО прилетело и опубликовало эту надпись здесь

Так а решение какое? Отвергаешь — предлагай. Какоие код ошибки и тело ответ "правильные"?

Я не пытаюсь отвергать или предлагать. Я сказал, что возможно я чего-то не понимаю. И возможно не я один такой.
Я хочу понять мотивацию тех, кто использует подход 200 + error и тех, кто не использует 200, если error.
Я хочу понять мотивацию тех, кто использует подход 200 + error и тех, кто не использует 200, если error.

с развитием API (а оно произойдет, ибо энтропия бизнеса бесконечна), кодов перестанет хватать и вы утонете в документации. Подход, при котором сначала вводится понятие системы/кодов успешности/ошибок выполнения бизнес-логики, это уже как правило практически половина всей требуемой документации. Написав эту часть, вы уже сделали большую часть работы. И чем ниже уровень квалификации фронтенда или юзера на нем, тем проще будет ваша жизнь с апи построенным по первому принципу.
тех, кто не использует 200, если error.

Я не использую 200, если ошибка, потому что это позволяет мне писать стандартные трассировщики HTTP-запросов, не разбирающие тело ответа, и все равно иметь осмысленную статистику.

Когда-то очень давно умные люди придумали разделять логику на уровни и стараться не смешивать их между собой.Например транспортный уровень и уровень бизнес логики. Как по мне, так очень удобно обрабатывать их в разных местах и на разных уровнях. 200 — транспортный уровень сработал.
Это было очень давно и умным людям не надо было решать ту проблему, которую приходится решать сейчас. Поэтому, это так себе аргумент.
Очень абстрактно выразился, имелась в виду ресурсо-ориентированная архитектура
200 ОК это ответ веб-сервера, не API, имхо смешивать статусы ответов API и статусы ответа веб-сервера — некорректно. Если криво написан API и скрипт/софт падает при обработке запроса от вебсервера — то тогда да, 5xx. В остальных случаях — 200 + json|xml со статусом выполнения запроса API.
Яркий пример — вы отправляете запрос DELETE.
  • Удалось удалить — ответ 200.
  • Вы не авторизованы и у вас нет прав на удаление — сервер, как бы, полностью обработал ваш запрос, но отказался его выполнять, и ответ 401 однозначно говорит в чём причина.
  • Сервер ответил 421 — видимо, он принял от вас запрос и передал его дальше — но не уверен что следующий сервер, отвечающий за хранение и удаление, правильно его обработал
  • 503 — сервер жив, но в данный момент не может выполнить запрос, надо попробовать чуть позже.
Пятый вариант, вы авторизованы, у вас есть права на удаление, более того само удаление запустилось, но словили исключение, например на уровне базы, зависимости есть, блокирующие удаление. 200 + детали в ответе — почему нет? Это не 500, серверу не плохо, не 401 и иже с ним, да возможно 421 использовать, но мы же получили ответ от БД и исключение обработали, выдав, что была ошибка, более того, ещё и транзакцию по удалению откатили.
На самом деле эти споры — переливание из пустого в порожнее, разруливается всё максимально полной документацией АПИ.
«422 Unprocessable Entity — сервер успешно принял запрос, может работать с указанным видом данных, однако имеется какая-то логическая ошибка, из-за которой невозможно произвести операцию над ресурсом.»

Ну и да, никто не отменяет расширенное описание того, что пошло не так, в теле ответа. Но если уж что-то пошло не так — не надо отвечать 200 OK.

Однозначно 500 Internal Server Error


The 500 (Internal Server Error) status code indicates that the server encountered an unexpected condition that prevented it from fulfilling the request.

Клиенту абсолютно всё равно, что у вас там внутри — база данных, текстовые файлы или просто бага в коде. Для клиента ваше приложение это "чёрный ящик". И если внутри "ящика" происходит что-то неожиданное, что не предусмотрено бизнес-логикой — это "внутренняя ошибка сервера".


PS: статусы семейства 4xx — это статусы описывающие ошибку клиента, они не подходят. Если конечно вы не хотите обвинить клиента в том, что у вас база данных упала.

Ох, надо внимательнее читать.
Я решил что вы про простую, неожиданную ошибку при работе с базой данных, а не про конфликтный доступ к ресурсу, который предусмотрен бизнес-логикой. В последнем случае действительно надо использовать статус 409.
409 Conflict — запрос не может быть выполнен из-за конфликтного обращения к ресурсу.

409 будет верным ответом только в случае если формально клиент допустил ошибку сделав запрос, который не может быть выполнен по причине определённых бизнес-требований вызывающих конфликт (например "нельзя удалить файл если он в данный момент кем-то открыт")


409 Conflict — запрос не может быть выполнен из-за конфликтного обращения к ресурсу. Такое возможно, например, когда два клиента пытаются изменить ресурс с помощью метода PUT.Появился в HTTP/1.1.
Вот этим и плохи коды. Вам сразу предложили минимум три корректных варианта. И об этом нужно каждый раз думать расширяя или модифицируя апи и обрабатывая ошибки на клиентской стороне.
Хуже, когда при запросу к API ничего не найдено и вместо 200-ки и JSON-а с пустым массивом сервер кидает 404 ошибку. Ну прям бесит!
Не найден объект или запрашиваемая коллекция объектов пуста?
Вообще кодов много en.wikipedia.org/wiki/List_of_HTTP_status_codes
Конкретно к описанной ситуации возможно подойде 207
207 Multi-Status (WebDAV; RFC 4918)
The message body that follows is by default an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.[17]
Через боль и страдания полученные с 2004 года, пришел к паттерну, что хттп код != 200 может быть отдан только в случае некорректного формата запроса. В остальных случаях надо отдавать 200 и в теле ответа передавать код успешности выполнения бизнес логики (можно с внятной расшифровкой если требуется). Ну 401 и 403 еще исключение из этого правила. А в остальном, это работает более предсказуемо, нежели чем получить 404 на запрос свободных «слотов» в каком нить скедулере (реальный случай из практики)

Чтобы как-то поправить возникающую в комментах статистику, отмечу, что в REST(-ish) API, которые я проектирую, я стараюсь отдавать далеко не только 200 OK, а на API, которые мне как клиенту отдают 200 OK {"ok": false} — злюсь. Просто потому что они ломают мне униформное логирование.

В первом случае, в идеале, возвращать надо не 200, а 202 — запрос принят, обработан, а вот результат обработки возвращается уже в JSON.

Во втором случае я так и не понял, почему нельзя возвращать ошибку.
Хотя, прошу прощения, в первом случае, пожалуй, надо 207, а не 202.
Если вы хотите писать приложение соблюдая принципы REST и с API поверх HTTP, то грех не воспользоваться хорошо продуманным решением построенным на тех же принципах и для которого (и это очень важно) есть документация в виде RFC и кучи статей в интернетах. В этом случае вам не придётся изобретать велосипед и придумывать свою систему ошибок, не придётся детально описывать все нюансы принятых вами решений, «новичкам» будет проще ориентироваться в вашем API т.к. они скорее всего уже знают про HTTP и его «систему ошибок». В настоящий RESTful API очень легко и гармонично вписываются HTTP ошибки.

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

PS: Существенная часть HTTP статусов не связана с «транспортным уровнем» или «веб-сервером». Много статусов относятся к бизнес логике.
PS: Существенная часть HTTP статусов не связана с «транспортным уровнем» или «веб-сервером». Много статусов относятся к бизнес логике.

Вот это-то и плохо! Давайте теперь все в HTTP статусы пихать! Даешь 10000 HTTP статусов!
Разделение на слои для того и придумано, чтобы не натягивать ссову на глобус.
Я, пожалуй, для себя так формулирую: вот есть у нас описание API. Если запрос ему не соответствует, то нужно вернуть 4xx, если соответствует, но операция не может быть выполнена (т.е. проблема на уровне бизнес-логики), то 2xx. Ну и если у нас непонятное исключение внезапно возникло, то 5xx. Это со стороны бэкенда.

Со стороны фронта всё равно нужно проверять как HTTP-код, так и тело ответа. Но можно структурировать проверку ответа, исходя из HTTP-кода.

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

1. Не заполнены обязательные поля (например, наименование), или у пользователя нет прав на ввод данных — 4xx.
2. Артикул (ИНН) совпадает с уже имеющимся — 2xx и описание проблемы в JSON.
3. При создании записи на бэкенде сбойнула связь с базой — 5xx.
По идее, второй пункт в примере укладывается в логику 409 кода (конфликт запроса с текущим состоянием сервера).
И соглашусь и не соглашусь… Это намеренно огрубленный пример. Хотя можно и 409.

При выборе группы для ошибки (4xx — 5xx) в первую очередь дайте ответ на вопрос "Кто виноват?".


У вас во втором пункте виноват клиент — это его ошибка, что он попытался добавить товар с существующим в базе ИНН. Следовательно сразу становится понятно, что надо выбрать код из группы 4xx.


PS: Коды со статусами < 400 формально не являются статусами описывающими ошибки.

ИМХО это худший вариант, смешивается два подхода. Каждый разработчик модифицируя данный api будет выбирать один из двух подходов самостоятельно исходя из своих соображений, в итоге очень быстро получим кашу
И хотелось бы напомнить, что любой код ответа не запрещает нам отдавать в теле разъяснение причины, хоть в JSON, хоть в XML, хоть еще как-то. Т.е. вопрос не стоит так: отдаем ли мы подробности? Вопрос исключительно в том, используем ли мы код ответа перед отдачей подробностей?
Это сильно усложняет обработку ошибок. Так у вас нужно обработать только вложенную ошибку, а так код и вложенную ошибку. Код применим тогда, когда детали ошибки не нужно обрабатывать, максимум показать пользователю.

Добавлю, что ответы 5xx нередко возвращает фронт (nginx/AWS ELB/etc.) стоящий перед веб-сервисом, а это означает что клиент должен быть готов увидеть в теле ответа либо любой HTML возвращённый фронтом, либо JSON от веб-сервиса, что дополнительно усложняет обработку ошибок.

Фронт и 4хх может ответить, тот же 413 Request entity too large.
История с кодами ответа сейчас нужна в первую очередь для балансировщика. Чтобы перенаправить трафик с узла, который нагрузку уже не держит (по любой причине) — на другой. Давайте обсудим эту историю.

Заголовки построенные на базе замеров выполнения реквеста не подойдут? Или там только хттп коды?

Заголовков очевидно будет недостаточно, т.к. запрос может долго обрабатываться и давать корректный ответ. И, напротив, валиться быстро с 4** и 5**. И, ес-но, желателен ретрай на клиентской стороне, но не всегда это возможно
Неее, я немного про другое:

Каждый ответ содержит заголовок с указанием метрики вида «насколько загружен мой сервер» или «сколько времени ушло сверх обычного (пусть бекенд сервер сам считает свои метрики, и решает что долго, а что нет)» и так далее. А балансер пусть научится с этим работать.

Понятно, что балансеры «из коробки» такое не умеют, но такая схема выглядит более продвинутой, нежели чем отдавать в лоб 504 или 503, и позволит заранее избежать 100% нагрузки на одну из нод бекенда.
Если так рассуждать, то нужно переходить на сервис-меши с интегрированным трейсингом.
Они реально нас спасут. Но в будущем.
А если их нет или не хочется переусложнений?
Если исходить из предположения, что на таймаут мы повлиять не можем, то 5хх придется отрабатывать по полной программе. Но ведь ошибки бизнес-слоя прокинутые наверх, к веб-слою апи, не должны же приводить к таймаутам? Значит парадигма «отдать 200 если с транспортом все окей и внутри рассказать, что плохого случилось в результате выполнения реквеста» — это вполне себе рабочий паттерн.
А если ошибки в бизнес-слое приводят к таймаутам, то тут уж никакая балансировка не поможет, любая нода выдаст его родимого.
Не могу назвать себя фанатом или ненавистником rest-подхода. При должной фантазии, любую жизненную ситуацию можно вписать в прокрустово ложе http-кодов. Вопрос только в том, как этим api будут пользоваться люди.

Псевдокод:
ответ = запрос({параметры})
если 200 >= ответ.код < 400 {
    // всё хорошо
} иначе {
  если 400 =< ответ.код < 500 {
    // всё плохо, смотрим ответ.тело.error
  } иначе если ответ.код >= 500 {
   // всё совсем плохо
  }
}


Против:
ответ = запрос({параметры})
если ответ.код == 200 {
  если ответ.тело.ок {
    // всё хорошо
  } иначе {
    // всё плохо, смотрим ответ.тело.error
  }
} иначе {
  // всё совсем плохо
}


И в чём тут разница? Только в том, что универсальные утилиты понимают коды. Зло заключено в случае, когда в одном api используется и error в 200 и коды 400 в разных методах и случаях.
HTTP — прикладной протокол, не транспортный, несмотря на его неоднозначную аббревиатуру. Это значит, что это именно протокол уровня приложения, протокол взаимодействия пользователя за клиентским терминалом с серверным вычислителем в мейнфрейме в терминологии архитектуры (архитектурной парадигмы) REST, который налагает определённые ограничения на семантику взаимодействия клиента и сервера — они обмениваются не командами управления, а выполняют строго ограниченный набор операций над некими представлениями ресурсов, каждому из которых обязан соответствовать свой уникальный URL. И коды ошибок исчерпывающе описывают все возможные проблемы синхронизации представлений ресурсов, включая по возможности диагностику инфраструктуры «вглубь» — состояние канала связи и системные проблемы самого обработчика запроса. Эти коды соответствуют и неким математическим соображениям о возможных состояниях консистентности представлений ресурса в распределённых системах, так и практическим набитым шишкам за годы эксплуатации и развития WWW. И если вы проектируете протокол взаимодействия своего приложения так же в парадигме REST, то имеет смысл воспользоваться готовым набором ошибок и вообще протоколом HTTP в полной мере, не изобретая велосипед. Но если вы архитектуру своего приложения никак не синхронизируете с формализмами и ограничениями REST, а взаимодействие с сервером представляете скорее не как управление представлениями ресурсов, а как запрос к БД (GraphQL) или как инициирование операций на сервере (RPC), то, конечно, особой выгоды от использования ошибок кодов HTTP может и не быть, может хватить и груза кода 200 с трупом телом ошибки.

Так что статья холиварная, вопрос беспредметный и без специфики проекта смысла не имеющий.
Просто http коды походят для идеального rest приложения, которое со всеми url работает как с ресурсами и действительно полностью лишено состояния в самом ортодоксально смысле( т.е. состояния нет вообще, а не приложение не хранит состояние ). Но имхо таких приложение в природе, за исключение систем хранения, не встречается. Всегда, у нас stateless, но есть один нюанс.
В формализмах REST можно выразить, в общем, любую распределённую информационную систему, чисто математически, но вопрос только в стоимости поддержки такой парадигмы проектирования и получаемых от этого плюсов или решения конкретных проблем в борьбе со сложностью разжиревшего проекта. А так же, что многие стыдливо оставляют за скобками, нужно учитывать уровень команды, REST не так уж прост в понимании и практическом освоении, иногда дешевле запилить API в PRC-стиле просто потому, что так понятнее всем в команде. По-настоящему тонкости REST начинают ощутимо помогать только в больших проектах, с несколькими подпроектами и независимыми подгруппами, тогда Верховный Архитектор, дорого и надёжно проектирующий и поддерживающий REST-API, экономит незаметные триллионы часов каждой подгруппе, разрабатывающей свои сервисы в рамках ограничений, накладываемых этим API, интуитивно предоставляя им некий архитектурный каркас, «рассказывая», как и с какими сущностями должен производить работу тот или иной сервис. Иными словами, REST — это матан, но часто хватает и арифметики.

Смешно это ваше "формализм", "матан".
REST скорее ближе к натуральным языкам — убогое, сирое, но в некоторых вещах работает лучше остального (stateless), а еще его все понимают (индустриальный стандарт).
Не стоит натягивать REST везде

Тема холиварная.
Для себя решил так. http статусы это статусы соответствующего протоколу уровня, транспортного. 200 означает, что сервер приложений получил запрос и корректно его обработал. Корректно подразумевает, что при обработке запроса не возникло не предусмотренных исключений( логических, валидации, технических ), в идеальном мире не предусмотренных исключений быть не должно.
Пример:
а) http сервер выполняет авторизацию пользователя, пользователь не авторизован, возвращаем 401, запрос до сервера приложений не дошел.
б) авторизацию пользователя выполняет приложение. Пользователь не авторизован. Возвращаем 200 и ошибку в json. Т.е. http сервер свою функцию выполнил, запрос получил, передал его приложению, приложение не упало.
в) авторизацию пользователя выполняет приложение. Разработчик не предусмотрел, что база может отвалиться по таймауту. Возвращаем 500, пишем в лог.
Дальше анализируем лог и решаем, в данном конкретном месте нужно отрефакторить потому как подобная ошибка может быть периодически и значит нужно ее обработать и вернуть 200 и ошибку в json или это ситуация из разряда столкновения кометы с землей и есть задачи более приоритетные.

Тут еще нужно учитывать, что json ответ идет напрямую приложению, а http код может обрабатываться промежуточной библиотекой, которая запросто может на все не 200 кидать не информативный websocketexception.

В первую очередь, это вопрос договорённости. Основная проблема, на мой взгляд, заключается в дисбалансе между структурой ошибок реального бизнеса и REST/HTTP в вакууме. CRUD ресурсов это только малая часть айсберга, ещё есть обработка бизнес-ошибок и состояния ресурса. Да, REST-модель декларирует себя, как stateless, но в реальном бизнесе практически всегда возникает вопрос разделения ресурса между потребителями. Банальный пример: ресурс Заявка, вы апдейтите её из Назначена в Закрыта (думая, что она, допустим, открыта) и вам возвращают ошибку в json, с указанием деталей ошибки: некорректное изменение состояния ресурса transition not allowed.


В целом, считаю, что корректно выделить следующие группы HTTP ошибок: интеграционные, CRUD, бизнес. Первые две группы худо-бедно покрываются HTTP-кодами, на бизнес ошибки можно и сотни кодов легко не напастись. Действительно, необязательно возвращать 200 ОК, структуру бизнес-ошибки можно и в 412 precondition failed завернуть и в 5хх какую-то: как я выше сказал, для меня это вопрос проектных договорённостей.


Также, если вы проектируете интеграцию с оркестровкой, то будьте готовы к пробросу двойной структуры ошибок: от оркестратора и от оркестрируемых.

Я правильно понял, что ваш пример с Заявкой — это пример который, как вы считаете, плохо укладывается в HTTP (или REST)?
Разделение ресурсов между потребителями — это как раз одна из задач, которую помогают решить принципы REST и в частности HTTP, можно сказать что они создавались с учётом этого. Ваш пример отлично решается силами HTTP — с помощью условных полей заголовка (If-Match и др) + упомянутый вами статус 412, который как раз относится к этим заголовкам.

Мой пример был скорее про Task pattern, который привносит в модель ресурс с состоянием. В моей практике это обычно жизненный цикл ресурса. Буду признателен, если объясните, как решить этот вопрос с помощью If-match, так как единственное доступное мне решение, это распределённый кэш (обновляющий жизненный цикл ресурса должен оповестить подписанных на ресурс о внесённых изменениях по факту успешного изменения).

А не надо ограничиваться только разработкой. Надо вспомнить что инфраструктуру надо мониторить (Grafana, ELK и вот это вот всё). И нужен способ быстро понять род ошибки — проблема железа, или баг в ПО. Лучше всего инструменты мониторинга работают по http-статусам. И если ошибки валидации и ошибки бизнес-логики очень удобно отдавать по статусу 200, то тогда админ будет реагировать на 5хх, что все 5хх — это его проблема.
Бизнес-процесс может состоять из нескольких запросов. И даже в случае того, что один отвалился по дороге, но ретрайнулся и прошел — да, и пофиг на ошибку. И что тогда делать? Эксепшены же надо ловить с другой стороны — из логов приложения, его метрик. А не из кодов возврата HTTP
Очень интересно читать про мониторинг и столь жесткое разделение на черное и белое.
Ситуации:
* Надо отдать файл, но примонтированный девайс по неведомой причине отвалился и файл не найден. Вы отдаете 200 с ошибкой. Все штатно и по кодам ответом в системе мониторинга не понять объемы проблемы.
* Какой-то аккаунт в системе начали брутить или просто что-то пошло не так с системой авторизации, но по логам вы не увидите всплеска неуспешных попыток.
* У вас в приложении вдруг появилась ссылка на ресурс, но он недоступен для использования по правам. Без разницы — забыли выдать права, нечаянно дали ссылку куда не следует или вообще админ выставил не те права на запись в каталог. Вы все равно этого не увидите.

Еще тонна всяких ситуаций. Да, надо мониторить не отвалился ли примонтированный девайс, нужно иметь антибрут и отслеживать это, нужно мониторить систему авторизации, нужно отслеживать права и нужно еще много всего делать. Но зачем при этом целенаправленно лишать себя еще одного инструмента мониторинга и сбора статистики?
Что возвращать зависит от логики приложения. Если неавторизованный пользователь это нормальная ситуация — возвращаем 200. Например, выдача одного контента авторизованным пользователям, а неавторизованным другого. Тогда при проверке авторизации вполне допустим возврат 200. Если логика приложения иная, скажем запретить доступ неавторизованным пользователям — то 401.

В случае батч операции аналогично. Если «принять не все данные» это успешная операция с точки зрения логики приложения — возвращаем 200. Если это недопустимо — код ошибки + описание. В смысле «не дошел один файл из многих — операция не успешна». Т.е. на нее надо дополнительно реагировать. Поэтому код никак не может быть 200.

P.S. У моих апи все возвраты, если они есть, завернуты в Envelope. В смысле вообще все. Т.е. есть ошибка или нет — можно обрабатывать всегда одинаково. Либо есть 200 и поле result либо код ошибки + заполнено поле error. Унификация респонсов отличная вещь если можете себе позволить.

Данный подход называют {Json:api}


Из плюсов:


  • Не нужно завязываться на REST — механизм передачи и содержимое запроса полностью независимы.
  • Все ошибки, предупреждения и данные передаются в теле запроса, в формате JSON
  • Используется лишь один код ответа, чтобы подтвердить успешную передачу, обычно это 200 ОК
  • Механизм передачи и содержимое ответа полностью независимы.
  • Гораздо проще дебажить, ведь все данные находятся в одном месте в легко-читаемом формате JSON
  • Легко перенести на любой канал связи, например, HTTP/S, WebSockets, XMPP, telnet, SFTP, SCP, or SSH

JSON:API это немного из другой оперы. По факту это странная муть, особенно если у вас фронт написан не на Ember и/или в проекте есть одна из тех типовых проблем, с решением которых никак не могут определиться на форуме JSON:API. А вот если проект на Ember и в нём нет вышеупомянутых типовых проблем — тогда это классная штука чтобы всё заработало само собой. В общем, они молодцы, что пытаются превратить своё решение в общий стандарт, но в реальности им ещё работать и работать над этим, чтобы оно стало привлекательнее традиционных REST/RPC.

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

Я слабо понимаю причину полного отказа от использования http-кодов, кроме банальной лени или используемых библиотек, которые их не поддерживают нормально или поддерживают в нежелательном виде (к примеру исключения на 4хх и 5хх). Но это скорее проблема выбора библиотек и легаси кода, новые проекты не стоит писать на этом же. Но таких случаях это может быть оправдано. Могу ещё понять использование нескольких протоколов, хотя это не исключает и не запрещает возможность использования http-кодов.

Даже если не углубляться, 200, 400, 401, 403, 404 — минимальный джентельменский набор, который позволит любому клиенту определить происходящее, не делая парсинг тела запроса.

В проектах приходилось использовать также 201, 204, 409, 410, 422, 429.
Хорошая тема поднята, интересная. К сожалению ни одна известная мне спецификация не закрывает все поднятые в статье вопросы.
Для того чтобы эту тему закрыть в своих проектах мы ориентируемся на следующие моменты:
  1. HTTP Status — какие коды использовать в каких случаях
  2. HTTP BODY (json, xml — не суть важно) — формат ответа
  3. Подсистема мониторинга — выявление и реагирование на ошибки
  4. UI и взаимодействие с пользователем
  5. Цепочка ответственности
  6. Troubleshooting Guide
  7. Логирование


1. HTTP Status
200 — для ответов которые выполнились без ошибок.
Из этого правила есть исключения. Например:
Если сущность версионируемая и хоть обработка запроса была с ошибкой, но привела к смене версии объекта (т.е. он был изменен). В этом случае в теле ответа возвращается детальная информация что именно пошло не так и как реагировать на это вызываемой стороне.

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

2. HTTP BODY
Для 200 — тело достаточно разнообразное, сообразно задаче. Если глобально поделить, то это:
  1. запрашиваемые данные
  2. для листовых результатов — обрамляющая структура в которую входит мета-данные (например с информацией о постраничном доступе к данным), сами запрашиваемые данные
  3. для командных запросов — либо полный набор данных по которым была команда, либо DIFF (только тот набор данных, которые были реально изменены этой командой)


В случае ошибок стараемся придерживаться вот этого описания:
jsonapi.org/examples/#error-objects

3. Подсистема мониторинга
Т.к. наличие такой подсистемы сильно облегчаешь жизнь на продакшене и не только, то лучше ей не мешать работать, но помогать.
Если на все ответы отдавать 200-й код и «скрывать» реальную ситуацию (хорошо / ошибка) в теле ответа, то обычно подсистема мониторинга будет бессильна вам помочь и это плохо.

Поэтому совет простой — если произошла ошибка на которую нужно среагировать (не обязательно прямо в момент ее появления, но и отложено, ретроспективно )- то не используйте 200-й код для этого. Люди, которые буду сопровождать систему на проде будут вам благодарны.

4. UI и взаимодействие с пользователем
Мы ведь все не только разработчики, но и пользователи информационных систем. И понимаем, что выгружать на пользователя стек ошибки исполнения, детальную информацию о падении и прочим не хорошо. Но если что-то пошло не так, то до пользователя нужно эту информацию донести. Формат jsonapi.org/examples/#error-objects очень не плохо с этим помогает.
Тем более в ситуации когда UI должен поддерживать различные локали (i18n).

А, учитывая, что при успешном исполнении запроса что-то могло пойти не так, но система с этим справилась — тоже бывает важно показать пользователю. Пример:
Заказ проведен, но бронь не удалось сделать. Если бронь не прошла (не доступен был сервис), то это не повод не попытаться взять денег с пользователя :)

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

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

6. Troubleshooting Guide
Ну с этим, думаю все понятно. С одной стороны мы должны выдать минимум информации (код ошибки), с другой стороны по этому коду ошибки должны получить максимум информации чтобы среагировать на неё.
Обычно для этого вводят коды ошибок. Для упрощения восприятия их можно объединять в группы, пример:
0000 — это ОК, все хорошо
0001 — 1999 — набор валидационных ошибок
2000 — 4999 — группа бизнес ошибок
5000 — 7999 — интеграционные ошибки и ошибки доступа к данным
Вариантов группировок — на вкус и цвет :)

7. Логирование
Сама по себе большая и не однозначная тема. Но ошибки надо легировать — это факт.
И наличие в них кода ошибки упростит их поиск. Для того, чтобы вычленить всю цепочку лог-записей приведших к ошибке используется трассировка записей (например по x-request-id).

-----
Прошу прощения если получилось несколько сумбурно. В целом каждый пункт, это по факту не маленький документ являющийся технической спецификацией от которой отталкиваются разработчики при работе над Проектом. По которому создаются интеграционные тесты и прочее.
Сумбурно?! Вы же читали статью, вот там сумбурно! А тут коротко и по существу. ИМХО, автор топика просто поделился болью от нехватки опыта и избытка проблем.
Как и любые абстракции, эти (статусы ответов, REST, HTTP-методы) по определению всегда будут неидеально накладываться на реальный мир. Любая абстракция — это компромисс между сложностью вертикальной и сложностью горизонтальной. Любая асбтракция неизбежно столкнётся с неидеальностью мира, и придётся отдельно рассматривать исключения и особые случаи.
Так вот, компромисс должен быть эффективным. Не надо лезть в бутылку крайностей. В этой статье автор «полез» в одну сторону, но я отлично себе представляю и адептов другой крайности, которые с радостью готовы использовать 500 ошибку в бизнес-логике своего API.
Золотая серединка есть.
Ошибки на стороне сервера можно разделить (как минимум) на 2 типа:
1. Бизнес ошибка. Например вы пытаетесь выполнить какое-то действие с объектом, а он имеет не подходящий статус. Вполне логично вернуть код 200 с описанием ошибки согласно контракту, т.к. на стороне сервера ничего не поломалось.
2. Собственно ошибка на стороне сервера. Например deadlock на БД. Тут логично вернуть 500.
Как по мне, то ответ на вопрос зависит от принятого на старте решения: пишем мы REST HTTP API или REST API over HTTP.

В первом случае нужно стараться по максимуму использовать семантику HTTP, в том числе делая маппинг ошибок на HTTP статусы. Во втором HTTP статусы будут показывать только проблемы с сервером, когда он не может вернуть 200, то есть или ошибки, сгенерированные веб-сервером, когда он не может связаться с апп-сервером, или таймаут или ещё что, или необработанные исключения.
А у меня такой вопрос к сторонникам rest-подхода — допустим вы не можете использовать http по причинам слабой производительности и есть необходимость использовать вебсокеты для браузеров (либо tcp для мобильных клиентов) — будете ли вы кодировать в передаваемых сообщениях схему http протокола и дальше в своем привычном режиме организовывать взаимодействие клиента с сервером согласно вашим представлениям rest-а или может вы решите не кодировать в сообщениях всю спецификацию http а ограничить себя каким-то подмножеством или может даже изменить какие-то моменты, или может вообще не будете смотреть на спецификацию http-протокола и закодируете в сообщениях формат общения удобный клиенту и серверу?
Всегда используется подмножество возможностей REST (чем проект больше масштабируется — тем больше пригождается), хоть в виде HTTP, хоть в виде идей для реализации собственного протокола, оверинжинерить не нужно. И переиспользовать ту же таблицу в своём протоколе кодов довольно удобно — готовая документация и типовые приёмы обработки разных типов ошибок идут бонусом. REST (Representational State Transfer) — это прежде всего парадигма взаимодействия сервера и клиента через абстракцию «представление ресурса», из которой вытекают удобные ограничения, чтобы итоговая архитектура конечного разрабатываемого приложения получалась менее противоречивой и более масштабируемой (по всем видам ресурсов и на всех этапах разработки, развёртывания и эксплуатации ПО).
Мне кажется что в статье не хватает опроса, кто за 200 + error, а кто за 4xx/5xx + error.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории