Комментарии 178
Статус 200 неинформативен. Вебсервер отработал, но как — вам не известно. Посмотрели в тело ответа, там json со статусом ОК — то значит всё хорошо, статус error — то что-то пошло не так, может там параметры невалидны. Сваливать этот статус в статус ответа вебсервера — ну такое.
еще по статус коду знать
И чем это знание поможет?
Если ошибка произошла на низком уровне (на уровне сервера, прокси, и т.д. Что-то в духе 404 или 504) — да, можно обойтись и статус кодом и на клиенте такой ответ отловить, не передавая сильно глубоко в обработку.
Если же ошибка произошла на уровне логики приложения, то в почти всех случаях помимо информации о самом факте ошибки клиенту нужно сообщить еще какие-то подробности о природе и причинах этой ошибки. И да, статус кода как правило недостаточно. Все равно будет какое-то тело ответа. И вот совершенно непонятно, что мы выигрываем, размазывая информацию об ошибке ровным слоем по телу и заголовку ответа, вместо того чтобы заключить её всю в одном месте (в теле ответа).
"Код становится уродливым", кмк, именно в таком случае. В лучшем случае приходится просто говорить клиентскому фреймворку, чтобы он игнорировал статус код ответа и передавал на уровень бизнес логики все ответы подряд. И там уже с ними работать точно так же, как и в случае статуса 200. С единственным отличием, что тип ошибки будет доставаться из статус кода, а не одного из полей джейсона.
В худшем случае это все дело придется обвешивать еще кучей костылей и уродливых хуков, потому что условный nginx, если не сможет достучаться до апстрима, вернет html страничку со статусом 504, а не джейсон. И код бизнес-логики к такому повороту скорее всего будет не готов.
Protobuf/grpc не подойдет?
И ещё давайте разделим коммуникацию между пользователем и сайтом (или веб-приложением) и компонентами этого самого веб-приложения…
Скажите, фронтовики, ответы с каким кодом у нас кэшируются? Не с этим ли пресловутым кэшем потом у вас идет неравная борьба, которая заканчивпется всяким бредом, типа рандомных чисел в get запросе? А кто в курсе, как работает балансировщик REST запросов? Как он узнает, что тот или иной серев перегружен? Опять лепим свой велик?
Код ответа, это унифицированное средство принятия решения на инфраструктурном уровне. Его нельзя заменить никакими json в ответах. Природа этого кода находится ЗА JavaScript.
Мне реально, очень стыдно, в прямом смысле, за тех, кто минусует попытку автора поднять этот вопрос.
Коды ответа тут ортогональны
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
в конце концов.
Это значит, что ошибка, должна возвращаться как ошибка с соответствующим кодом, а содержать расширенную информацию — кто запрещает? С передачей какой ошибки у вас трудности? Давайте разберем пример. Могу свой привести — запрос не прошел валидацию — ответ 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.
Да, 400 универсальный. И в большинстве случаев достаточен. Если не нужно не требуется выделять класс ответов. Но я бы выделил в этом случае именно для перелимитов. Не важно каких.
422 ммм… ну в целом, вай нот?
По сути, эти коды описываются в манифесте проекта и зачастую продумываются при проектировании системы. Далее они становятся универсальным средством коммуникаций для разных направлений (разрабочик/devops/админ/клиент).
Собрались люди с over 10000 часов опыта, т.е. профессионалы. И спорят о том как натянуть ужа на ежа. У каждого свое мнение, когда использовать 406, 422 или 429. А попробуйте объяснить все это джуну.
Разработчику клиента? Ну разве что в самых простых случаях, где надо проверить булевский признак Ok / не Ok. Вот пришло нам 404. Половина клиентских библиотек для работы с HTTP бросят exception. Половина нет. А на самом деле надо допустим обработать сначала заголовок contetn-type: если html или вообще нет body — это действительно неустранимая ошибка, а если json — это ошибка логики и с ней надо работать. Получается, что мы делаем не меньше работы, а больше.
Разрвботчику сервера? Ему теперь вместо описания бизес-логики нужно холиварить с коллегами про коды HTTP и поддерживать две системы передачи ошибок.
Админу инфраструктуры? Тут высказывалось такое мнение, что на эти коды из REST будут смотреть админы и что-то там делать. Но кмк, админы скорее скажут спасибо, если мы настроим нормальное логирование и мониторинг с помощью тех же ELK. Т.е. решим задачу теми инструментами, которые под это заточены. А не будем перекладывать свои проблемы на соседний уровень абстракции
Ну и с проблемами объяснения джуну как-то не сталкивался.
Я бы рекомендовал возвращать 400 только для тех ошибок клиента, которые в корректно написанном клиенте происходить не должны вообще. Иными словами, "недостаточно денег на счету" может являться ошибкой 400 только при условии, что клиент может гарантированно знать достаточно денег или нет ещё до вызова операции (т.е. например клиент имеет возможность заблокировать счёт, выяснить текущий баланс, и только потом вызвать операцию перевода средств, после чего разблокировать счёт — причём именно такое поведение клиента является штатным и ожидаемым от корректно реализованных клиентов). В остальных случаях, когда клиент не может заранее знать корректные он передал параметры или нет — 400 тут не подходит. Причина такого подхода в том, что если мониторинг обнаруживает 400 мне хочется быть уверенным, что это означает наличие бага в клиенте (реже — в сервере), который нужно найти и пофиксить.
В этом весь смысл отражать внутренние коды сотен конкретных ошибок в несколько HTTP статусов — чтобы эти статусы можно было использовать для какой-то конкретной пользы в инструментах уровня HTTP (для кеширования, для обработки некоторых классов ошибок, для мониторинга, etc.).
Код 500 это тоже не равно баг. Да, чаще всего при падении сервиса отдается 500 код, но что он значет? Сервис недоступен из-за бага? Где это указывается в спецификации? Сообщается, что сервис недоступен. Все. Да, иногда с ним приезжает трейс, ну так это уже только подтверждает верность пути — сообщай ошибку и сопровождай контентом. Кастомные коды тоже вполне рабочий вариант, но добавлять их имеет смысл только в случае необходимости принятия инфраструктурных решений. Ну например, мы как-то использовали кастомный код делегирования обработки запроса. Когда сервер отдает балансировщику ответ, типа «с этим запросом лучше справится тот-то». На его основе выстраивалась нетривиальная система коммуникаций. При этом, для клиента, это был тот же REST. В остальных случаях, прекрасно хватало стандартных кодов.
500 — это необработанная ошибка на сервере. Если ошибка обработана, в большинстве случаев это что-то из 4хх. Некоторые из 5хх можно кидать в случае обработки, семантически это будет верно, но есть вероятность, что 5хх придёт от веб сервера, поэтому клиенту придётся проверять content-type и другие вещи, чтобы различать их, поэтому в общем случае строить взаимодействие на 2хх/4хх лучше.
Да, может прийти и от WEB-сервера 500. И что? Да, придется разбирать. А с 200 не придется? С 400?:))) Тут как говорится или штаны надень или в баню зайди :))) Напомню, что и web-сервер может отдавать тот контент 500 который ВАМ нужен. Поэтому, ваше приложение получит вменяемый ответ в любом случае. И сможет его распарсить.
400 лучше? Вы услышали корень причины, почему нужно отдавать 500? Она сигнал админам — что-то в системе не так. Чинить! Срочно! Даже мой пруф чекто говорит об этом.
Именно из-за подмен и «детских травм» (тут я не обижаю, а просто напоминаю, что 500 ошибка это первое что WEB разработчик вообще встречает, а код 200 сразу воспринимает как благость снизошедшую), многим свойственно не трогать коды 500. Но это не верно, это такой же инструмент, как и любой другой код.
сервер столкнулся с неожиданным условием, которое помешало ему выполнить запрос
Ну так по ссылке и написано. Если условие неожиданно, то и обработать его не получится, так как код обработки отсутствует. Под обработать я подразумеваю что-то большее, чем просто поймать исключение и выдать стандартную ошибку. Потому что в случае обработки скорей всего это будет либо 4хх, либо какие-то 5хх, но не 500.
В целом я согласен с вашей идеей, что 5хх может быть хорошим триггером для мониторинга и алертинга и я не против использования 5хх, просто считаю, что их стоит использовать несколько раз всё взвесив и убедившись, что 4хх точно не подходят.
Но на 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 как особый случай и пытаться искать баги — НЕЛЬЗЯ.
На этом, я считаю, в достаточной степени проясненной свою позицию. Перспектив найти консенсус я не вижу. За сим откланиваюсь.
Так и не надо все девяносто типов пытаться впихивать в HTTP-коды. Можно завести несколько «опорных» HTTP-кодов для ошибок (типа 400
, 422
, 403
и пр., чтобы фронтенд это дело корректно обработал), но при этом в теле ответа указывать более конкретный код ошибки. В таком случае, его и юзеру при необходимости можно будет показать.
200 это же ответ сервера, что запрос к нему дошел и он его успешно обработал. А вот результат обработки запроса это уже совсем другое.
Я почему-то всегда думал, что в 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. И тогда описанные в статье проблемы не возникнут.
Например, интересные варианты:
«202 Accepted — запрос был принят на обработку, но она не завершена.»
«401 Unauthorized — для доступа к запрашиваемому ресурсу требуется аутентификация.»
«409 Conflict — запрос не может быть выполнен из-за конфликтного обращения к ресурсу.»
«421 Misdirected Request — запрос был перенаправлен на сервер, не способный дать ответ.»
В некоторых случаях можно однозначно кодом ответа сообщить о том, что произошло с запросом на стороне api. В некоторых — стоит добавить в ответ тело с описанием ошибок\результатом запроса. Но отвечать «200, error» на мой взгляд плохая практика. В любом случае, если создаётся API которым в теории может пользоваться кто-то ещё, кроме разработчика — хорошим тоном будет создать документацию, и отдавать её по одному из адресов(например, в корне api)
Интереса ради сейчас потыкал в апи яндекса. На синтаксическую ошибку они ответили 400, на кривой токен — 401. Это не отменяет дополнительной информации в теле ответа, но даже не глядя в него я уже вижу, что не так с запросом.
Предположим что в последней версии сервера и приложения поменялось ожидаемое тело реквеста нa один из эндпоинтов, но старая версия приложения все еще шлет запрос в старом формате, а мы про это забыли…
Тут сервис контроля ошибок начинает вам алертить что ваш сервис начал слать кучу 400 респонсов на запросы на этот эндпоинт. Вы туда заходите, смотрите тело реквестов и… Семен Семеныч! Ошибка найдена и исправлена. Без лишних телодвижений.
Фишка статус кодов именно в их «унифицированности». Все знают что такое 400, 401, 403, 404, 500 и т.д. Детали, если они есть, можно и нужно отправлять в теле респонса.
REST разумеется не идеален, но по крайней мере его философия консистентна.
Ужасно когда работаешь с несколькими апи и у каждого из них свой велосипед на то как отдавать ошибки.
Какой код должен быть на ошибку "Поле "количество" должно быть больше 0"?
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).
Какому из этих пунктов соответсвует данный случай согласно букве или духу закона определения?
А почему запрос плохой? В запросе пришло неправильное название поля? Правильное. Размер большой? Вроде, ок.
Может это проблема не в запросе, а в контенте, который не относится к правилам оформления запроса?
Почитал коменты и в очередной раз убедился, что большинство работающих с 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 сервера, если что. И да, его действительно сейчас активно используют для подобных вещей, хотя его в стандарте нету.
Другой важный момент в самом наличии этих RFC и других, более «попсовых», ресурсов с документацией (например MDN и Википедия) в которых всё это задокументировано. Наличие такой публично доступной документации — очень большой плюс. Как минимум это облегчает «муки» выбора — всё уже придумано до нас, и очевидно не совсем глупыми людьми.
… хорошим тоном будет создать документацию, и отдавать её по одному из адресов(например, в корне api)полностью поддерживаю! это best practices и очень удобный, кстати
А вот результат обработки запроса это уже совсем другое.
По-моему, 4хх как раз и покрывают результаты обработки, начиная с обычного 400 (у вас кривой запрос), заканчивая 422 (ошибка валидации) или 418 (на беке чайник вместо сервера).
С другой стороны — это всё философия, по-моему спорить на эту тему, где именно кидаются такие ошибки можно бесконечно.
Ответ может придти (допустим, тот самый 200 OK), а может возникнуть исключение потому что сеть недоступна или ещё по десятку причин.
Внутри ответа бизнес-логика скажет детали ответа и там может быть json структуры, которая уже более детально и конкретно расскажет что не так. Может быть там будет пара ключ-значение (для каждого переданного значения, например id картинки — статус обработки), а может быть и что-то более нестандартное.
По поводу разрыва соединения я как-то раньше не думал.
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.
можно предположить, что статуса мало и нужно смотреть ответ и заголовки.
404 — ресурса с таким айди нет.
В случае если такого метода апи нет, возможно имеет смысл отдавать ответ 400, хорошим тоном будет так же добавить тело ответа с ссылкой на актуальную документацию по апи.
Есть ресурс /files/abc.jpg
, вы отправляете запрос и получаете 404. Что это значит, что такой урл вообще неизвестен серверу, или что запрос был принят, обработан, но файла с именем abc.jpg
не существует?
Да, собираюсь. Но вообще это не ко мне вопрос, а к DSolodukhin.
Так а решение какое? Отвергаешь — предлагай. Какоие код ошибки и тело ответ "правильные"?
Я хочу понять мотивацию тех, кто использует подход 200 + error и тех, кто не использует 200, если error.
Я хочу понять мотивацию тех, кто использует подход 200 + error и тех, кто не использует 200, если error.
с развитием API (а оно произойдет, ибо энтропия бизнеса бесконечна), кодов перестанет хватать и вы утонете в документации. Подход, при котором сначала вводится понятие системы/кодов успешности/ошибок выполнения бизнес-логики, это уже как правило практически половина всей требуемой документации. Написав эту часть, вы уже сделали большую часть работы. И чем ниже уровень квалификации фронтенда или юзера на нем, тем проще будет ваша жизнь с апи построенным по первому принципу.
тех, кто не использует 200, если error.
Я не использую 200, если ошибка, потому что это позволяет мне писать стандартные трассировщики HTTP-запросов, не разбирающие тело ответа, и все равно иметь осмысленную статистику.
- Удалось удалить — ответ 200.
- Вы не авторизованы и у вас нет прав на удаление — сервер, как бы, полностью обработал ваш запрос, но отказался его выполнять, и ответ 401 однозначно говорит в чём причина.
- Сервер ответил 421 — видимо, он принял от вас запрос и передал его дальше — но не уверен что следующий сервер, отвечающий за хранение и удаление, правильно его обработал
- 503 — сервер жив, но в данный момент не может выполнить запрос, надо попробовать чуть позже.
На самом деле эти споры — переливание из пустого в порожнее, разруливается всё максимально полной документацией АПИ.
Ну и да, никто не отменяет расширенное описание того, что пошло не так, в теле ответа. Но если уж что-то пошло не так — не надо отвечать 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 — запрос не может быть выполнен из-за конфликтного обращения к ресурсу. Такое возможно, например, когда два клиента пытаются изменить ресурс с помощью метода PUT.Появился в HTTP/1.1.
Конкретно к описанной ситуации возможно подойде 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]
Чтобы как-то поправить возникающую в комментах статистику, отмечу, что в REST(-ish) API, которые я проектирую, я стараюсь отдавать далеко не только 200 OK
, а на API, которые мне как клиенту отдают 200 OK {"ok": false}
— злюсь. Просто потому что они ломают мне униформное логирование.
Во втором случае я так и не понял, почему нельзя возвращать ошибку.
Если же у вас не REST, и ресурсы не являются ключевым элементом вашего API, то скорее всего ваши ошибки будут плохо укладываться в систему придуманную для HTTP и тогда вам действительно будет проще ограничится минимальным набором кодов ответа, а всё остальное реализовать другими средствами (хоть через кастомные HTTP заголовки).
PS: Существенная часть HTTP статусов не связана с «транспортным уровнем» или «веб-сервером». Много статусов относятся к бизнес логике.
PS: Существенная часть HTTP статусов не связана с «транспортным уровнем» или «веб-сервером». Много статусов относятся к бизнес логике.
Вот это-то и плохо! Давайте теперь все в HTTP статусы пихать! Даешь 10000 HTTP статусов!
Разделение на слои для того и придумано, чтобы не натягивать ссову на глобус.
Со стороны фронта всё равно нужно проверять как HTTP-код, так и тело ответа. Но можно структурировать проверку ответа, исходя из HTTP-кода.
Поясню на примерах:
1. Не заполнены обязательные поля (например, наименование), или у пользователя нет прав на ввод данных — 4xx.
2. Артикул (ИНН) совпадает с уже имеющимся — 2xx и описание проблемы в JSON.
3. При создании записи на бэкенде сбойнула связь с базой — 5xx.
При выборе группы для ошибки (4xx — 5xx) в первую очередь дайте ответ на вопрос "Кто виноват?".
У вас во втором пункте виноват клиент — это его ошибка, что он попытался добавить товар с существующим в базе ИНН. Следовательно сразу становится понятно, что надо выбрать код из группы 4xx.
PS: Коды со статусами < 400 формально не являются статусами описывающими ошибки.
Добавлю, что ответы 5xx нередко возвращает фронт (nginx/AWS ELB/etc.) стоящий перед веб-сервисом, а это означает что клиент должен быть готов увидеть в теле ответа либо любой HTML возвращённый фронтом, либо JSON от веб-сервиса, что дополнительно усложняет обработку ошибок.
Заголовки построенные на базе замеров выполнения реквеста не подойдут? Или там только хттп коды?
Каждый ответ содержит заголовок с указанием метрики вида «насколько загружен мой сервер» или «сколько времени ушло сверх обычного (пусть бекенд сервер сам считает свои метрики, и решает что долго, а что нет)» и так далее. А балансер пусть научится с этим работать.
Понятно, что балансеры «из коробки» такое не умеют, но такая схема выглядит более продвинутой, нежели чем отдавать в лоб 504 или 503, и позволит заранее избежать 100% нагрузки на одну из нод бекенда.
Они реально нас спасут. Но в будущем.
А если их нет или не хочется переусложнений?
А если ошибки в бизнес-слое приводят к таймаутам, то тут уж никакая балансировка не поможет, любая нода выдаст его родимого.
Псевдокод:
ответ = запрос({параметры})
если 200 >= ответ.код < 400 {
// всё хорошо
} иначе {
если 400 =< ответ.код < 500 {
// всё плохо, смотрим ответ.тело.error
} иначе если ответ.код >= 500 {
// всё совсем плохо
}
}
Против:
ответ = запрос({параметры})
если ответ.код == 200 {
если ответ.тело.ок {
// всё хорошо
} иначе {
// всё плохо, смотрим ответ.тело.error
}
} иначе {
// всё совсем плохо
}
И в чём тут разница? Только в том, что универсальные утилиты понимают коды. Зло заключено в случае, когда в одном api используется и error в 200 и коды 400 в разных методах и случаях.
Так что статья холиварная, вопрос беспредметный и без специфики проекта смысла не имеющий.
Для себя решил так. 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хх какую-то: как я выше сказал, для меня это вопрос проектных договорённостей.
Также, если вы проектируете интеграцию с оркестровкой, то будьте готовы к пробросу двойной структуры ошибок: от оркестратора и от оркестрируемых.
Разделение ресурсов между потребителями — это как раз одна из задач, которую помогают решить принципы REST и в частности HTTP, можно сказать что они создавались с учётом этого. Ваш пример отлично решается силами HTTP — с помощью условных полей заголовка (If-Match и др) + упомянутый вами статус 412, который как раз относится к этим заголовкам.
Мой пример был скорее про Task pattern, который привносит в модель ресурс с состоянием. В моей практике это обычно жизненный цикл ресурса. Буду признателен, если объясните, как решить этот вопрос с помощью If-match, так как единственное доступное мне решение, это распределённый кэш (обновляющий жизненный цикл ресурса должен оповестить подписанных на ресурс о внесённых изменениях по факту успешного изменения).
Ситуации:
* Надо отдать файл, но примонтированный девайс по неведомой причине отвалился и файл не найден. Вы отдаете 200 с ошибкой. Все штатно и по кодам ответом в системе мониторинга не понять объемы проблемы.
* Какой-то аккаунт в системе начали брутить или просто что-то пошло не так с системой авторизации, но по логам вы не увидите всплеска неуспешных попыток.
* У вас в приложении вдруг появилась ссылка на ресурс, но он недоступен для использования по правам. Без разницы — забыли выдать права, нечаянно дали ссылку куда не следует или вообще админ выставил не те права на запись в каталог. Вы все равно этого не увидите.
Еще тонна всяких ситуаций. Да, надо мониторить не отвалился ли примонтированный девайс, нужно иметь антибрут и отслеживать это, нужно мониторить систему авторизации, нужно отслеживать права и нужно еще много всего делать. Но зачем при этом целенаправленно лишать себя еще одного инструмента мониторинга и сбора статистики?
В случае батч операции аналогично. Если «принять не все данные» это успешная операция с точки зрения логики приложения — возвращаем 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-кодов, кроме банальной лени или используемых библиотек, которые их не поддерживают нормально или поддерживают в нежелательном виде (к примеру исключения на 4хх и 5хх). Но это скорее проблема выбора библиотек и легаси кода, новые проекты не стоит писать на этом же. Но таких случаях это может быть оправдано. Могу ещё понять использование нескольких протоколов, хотя это не исключает и не запрещает возможность использования http-кодов.
Даже если не углубляться, 200, 400, 401, 403, 404 — минимальный джентельменский набор, который позволит любому клиенту определить происходящее, не делая парсинг тела запроса.
В проектах приходилось использовать также 201, 204, 409, 410, 422, 429.
Для того чтобы эту тему закрыть в своих проектах мы ориентируемся на следующие моменты:
- HTTP Status — какие коды использовать в каких случаях
- HTTP BODY (json, xml — не суть важно) — формат ответа
- Подсистема мониторинга — выявление и реагирование на ошибки
- UI и взаимодействие с пользователем
- Цепочка ответственности
- Troubleshooting Guide
- Логирование
1. HTTP Status
200 — для ответов которые выполнились без ошибок.
Из этого правила есть исключения. Например:
Если сущность версионируемая и хоть обработка запроса была с ошибкой, но привела к смене версии объекта (т.е. он был изменен). В этом случае в теле ответа возвращается детальная информация что именно пошло не так и как реагировать на это вызываемой стороне.
Либо если это был batch-запрос который не покрыт единой транзакцией и часть данных этого запроса не была корректно обработана. В этом случае в теле ответа возвращается список ошибок с информацией какой именно набор подзапросов не был обработан и почему.
2. HTTP BODY
Для 200 — тело достаточно разнообразное, сообразно задаче. Если глобально поделить, то это:
- запрашиваемые данные
- для листовых результатов — обрамляющая структура в которую входит мета-данные (например с информацией о постраничном доступе к данным), сами запрашиваемые данные
- для командных запросов — либо полный набор данных по которым была команда, либо 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.
Золотая серединка есть.
1. Бизнес ошибка. Например вы пытаетесь выполнить какое-то действие с объектом, а он имеет не подходящий статус. Вполне логично вернуть код 200 с описанием ошибки согласно контракту, т.к. на стороне сервера ничего не поломалось.
2. Собственно ошибка на стороне сервера. Например deadlock на БД. Тут логично вернуть 500.
В первом случае нужно стараться по максимуму использовать семантику HTTP, в том числе делая маппинг ошибок на HTTP статусы. Во втором HTTP статусы будут показывать только проблемы с сервером, когда он не может вернуть 200, то есть или ошибки, сгенерированные веб-сервером, когда он не может связаться с апп-сервером, или таймаут или ещё что, или необработанные исключения.
200 — это хорошо или это плохо?