Pull to refresh

Comments 39

Если почитать апологетов REST, то для кодов ошибок надо использовать HTTP статусы, а текст ошибки отдавать в теле или в специальном заголовке.

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


{
    "error": "SMSGatewayError",
    "message": "СМС-шлюз не отвечает, попробуйте позже"
}
HTTP 400 PUT /v1/task/1 { status: 'doing' }
Body: { error_code: '12', error_message: 'Задача уже взята другим исполнителем' }

Это очень странный подход использовать 400 ошибку в данном случае, 400 — это общая ошибка бизнес-логики. Здесь отлично подходит например HTTP/409.


В целом пространства 4хх ошибок вполне достаточно для четкого и понятного описания ошибок апи, а расширенные описания ошибок также ложатся в схему тела ответа при статусе 400 например


  {
    "code": "string",
    "message": "string"
  }

и даже позволяют делать неограниченный уровень вложенности:


{
    "code": "authorization_failed",
    "subError": {
        "code": "payment_tool_rejected",
        "subError": {
            "code": "bank_card_rejected",
            "subError": {
                "code": "cvv_invalid"
            }
        }
    }
}

Так что считаю проблему несколько надуманной (ну и для тех кто использует статус HTTP/200 при отдаче ошибки приготовлен специальный отдельный котел в аду).

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

UFO landed and left these words here
400 — это общая ошибка бизнес-логики

400 означает синтаксически битый запрос, грубую ошибку клиента. Например, прислали XML вместо JSON или вообще сломали заголовки. Или тут что-то другое имелось ввиду?

я писал в контексте идеологии REST, не чистые хттп коды

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

Поэтому можно неделю потратить на обсуждение формата ошибок с коллегами споря о кошерности того или иного решения.

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

Например фейсбук использует

{
  "error": {
    "message": "Message describing the error", 
    "type": "OAuthException", 
    "code": 190,
    "error_subcode": 460,
    "error_user_title": "A title",
    "error_user_msg": "A message",
    "fbtrace_id": "EJplcsCHuLu"
  }
}


Twitter

{
  "errors": [
    {
      "parameter": "start_time",
      "details": "invalid date",
      "code": "INVALID_PARAMETER",
      "value": "",
      "message": "Expected time, got \"\" for start_time"
    }
  ],
  "request": {
    "params": {
      "account_id": "hkk5"
    }
  }
}


Ведь не от хорошей жизни они пришли к этому :)
С ошибкой 404 тоже все просто. REST энпоинты можно разделить на два типа — спичок и конечный объект, например:
/users — список пользователей
/users/123 — пользователь с ид= 123.
Чтобы соответствовать HTTP, применим правило — если в адресе эндпоинта какого-то объекта нет, то ошибка 404.
То есть:
/users — арес всегда правильный, возвращаем всегда список (или пустой список)
/users/123 — если пользователя 123 нет, то ошибка 404 иначе возвращаем объект.
Ну и понятно, что это утрировано, и дальше если обект есть но у него проблемы
то могут кидаться уже ошибки с другими кодами.
А еще бывает весело когда кто-то вешается на 404-ю ошибку что бы удалить у себя что-то связанное с объектом (ну потому что 404, значит объект был удален). А потом выясняется что что кто-то баг засунул в роутинг или настройки лоадбалансера/прокси или еще чего. Вот и получили блокер на ровном месте. Поэтому мы всегда проверяем респонс, даже когда это 404-я.
Часто api плохо описано, и без эксперимента не разобраться как оно работает. Те, кто никогда не обжигался на этом, читают такое описание и начинают додумывать — если объект был и его не стало, то наверное его удалили…
UFO landed and left these words here
В REST всегда path-часть URL это адрес какого-то ресурса (сущности).

Ресурс адресуется по всему URI (кроме fragment), включая host, path и query, а не только по его path-части. Т.е. /users/42?v=1 и /users/42?v=2могут адресовать два разных ресурса.
https://greenbytes.de/tech/webdav/rfc3986.html#query

UFO landed and left these words here
UFO landed and left these words here
UFO landed and left these words here
UFO landed and left these words here

Недопонимание есть, а как правильно то?

UFO landed and left these words here

Все эти best practic в которых описаны CRUD операции для простых ресурсов (имеется в виду простые одноуровневые объекты) разлетаются в щепки на первых 5 минутах разработки реального проекта отличного от TODOAPP.

UFO landed and left these words here

А зачем вообще использовать 4ХХ рендж? почему не 500 + детали в теле? Это мне кажется более логичным. Хотя сам не использую HTTP коды для бизнес логики, те всегда 200 код если отработал скрипт и сформировал ответ, исключения: если надо клиенту указать определенные дествия предписанные HTTP протоколом, типа 301/302/401 и т.п.

Потому что:
2xx — успех
3xx — перенаправление
4xx — ошибка клиента
5xx — ошибка сервера

6хх — ошибки бизнеса
(кажется нам этого нехватает)

UFO landed and left these words here
Как раз похоже что 400 статусы — это всякие ошибки валидации/авторизации/актуальности (отсутствия протухших кэшей), проверки сумм и всякое такое. И это больше сервисное чем бизнесовое. Поэтому зачастую попытки уместить ошибки в существующие статусы становятся похожи на натягивание совы на глобус и разработчики просто решают отдать 200 со статусом в отдельном поле, полезной нагрузкой и указанием ошибки (если требуется)
UFO landed and left these words here
Под валидацией я понимаю корректность запроса (форматы дат, буквы в string полях — представляющие цифры, отсутствие необходимых полей, наличие взаимоисключающих полей и прочей лабуды). По идее для хорошего тест-дизайна неплохо разделять кейсы валидации от бизнес кейсов (например попытка указать скидку выше 100% в параметре или дату позже даты окончания действия какой-нибудь задачи)

Про привычку — думаю это рационально. Как раз с переходом на gRPC может оправдать себя.
UFO landed and left these words here
Не могу утверждать этого. Просто заметил растущую популярность его использования.
UFO landed and left these words here
UFO landed and left these words here
UFO landed and left these words here

Не стоит использовать 5xx в штатных сценариях, т.к. часто на эти коды вешают какой нить высокоуровневый алертинг по умолчанию. Ops-ы вас проклянут за такое.

UFO landed and left these words here
Иногда Ops-ы вешают мониторинг 5xx ошибок на корневом балансере в попытке быстро понимать, когда что-то перестало нормально отвечать. Этакий ультимативный способ понимать, что что-то случилось, когда нет более гранулярного способа детектировать проблемы на уровне самих обработчиков запросов (особенно когда за балансером стоит их целый каскад). Если разработчики введут использование 5xx кодов как нечто нормальное, то Ops-ы будут часто тригериться на эти false positive. В прекрасном будущем, когда везде будет devops и плоская иерархия команд, отвечающая за прод и никакого верхнеуровневого мониторинга не потребуется, разработчики наверно смогут использовать любые коды, которые им понравятся. Но даже тогда если вы захотите реализовать внешний «планетарный мониторинг» вашего сервиса, то специализированные сервисы, которые будут «обзванивать» ваш прод, вряд ли оценят отдачу 5xx вместо 2xx, когда проблем у сервиса нет.
От комбинаторного взрыва начинает конкретно пухнуть башка!

Если на клиенте ответ с ошибкой (например все статусы >= 400) превратить в строку вида:
<status_code>.<error_name_from_body>
то комбинаторный взрыв пропадает и нет необходимости делать большую вложенность if-чиков. А если error_name_from_body — это уникальная строка, то можно вообще на статус не смотреть, пока это не требуется.
И при этом сохраняется возможность простой группировки ошибок на основе статусов. Например на все статусы 5xx просто показывать пользователю текст из тела ошибки и больше ничего не делать. И для мониторинга как правило не придётся писать кастомную парсилку — хватит статусов.


Это началась DDOS атака, или кто-то из разработчиков выбрал неудачный статус?

У нас проблемы с выбором правильного статуса решается с помощью просвещения молодых разработчиков (в первую очередь посылаем их на страницу википедии с описанием всех статусов) и ревью пул-реквестов.


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

Это сильно зависит от того, что технически означает "взять задачу" и как это будет реализовано в API. И скорее всего проблема с выбором статуса ошибки возникает из-за неподходящей для этой задачи реализации API. Вот пару примеров реализации и подходящего статуса:


  • Все юзеры видят все задачи и могут сделать UPDATE задачи, которая ещё не занята (прописать в неё свой user_id). В таком случае можно запретить делать UPDATE задачи всем, кроме её "владельца" — статус ответа 403. PS: В целом это может быть кривая реализация, т.к. есть возможность передать чужой user_id и это надо дополнительно обрабатывать.
  • У юзеров есть список "своих" задач. "Взять задачу" означает добавить задачу из общего списка в свой список, передав в запросе её ID. В данном случае ошибка — это ошибка валидации ID задачи (клиент передал ID занятой задачи). Статус для неё — 422.
  • и т.п.

Если не делать "сову" из API поверх HTTP, то не придётся натягивать её на "глобус". Чаще всего проблемы возникают из-за недостаточно хорошего понимания принципов заложенных в HTTP и как его использовать в соответствии с задумкой его авторов. В моём опыте, при подходящей реализации API, не было случаев, что бы не получилось выбрать подходящий HTTP-статус для ошибки. А если такое и случалось, то это было верным признаком, что я выбрал не правильную реализацию и надо конкретный API сделать по другому.
И я не могу вспомнить, что бы в рамках одного ресурса один и тот же статус обозначал бы разные ситуации и реально требовалось бы смотреть на некий "error_name_from_body". Для разных ресурсов один и тот же статус мог иметь немного отличный смысл (и это логично — ресурсы ведь разные), но он всё равно оставался в рамках того для чего этот статус был предназначен в HTTP. Могу только представить, что статус 403 может потребовать дополнительного уточнения конкретной причины запрета доступа (надо войти под другим юзером или в принципе запрещён доступ для всех). И то это скорее всего редкий случай.

Идея вцелом хорошоая. Только я бы все же оставил несколько статусов которые на граинце приложения.транспорта. Это например статусы связанные с авторизацией и аутентификацией 401, 403, 419, 429. И разделил отдельно выделил уровень ошибок валидации входных параметров (наличие обязаьельных полей, их тип и формат, вохождение в диапазоны допутимых значени) то что как бы дублирует работу валидацию которая на фронтенде должна проходить, но мы часто ее дулируем для верности на бэкенде. Мотивация такая что эта валидация дет иметь достаточно однообразный для всех случаев тип: список полей, текст с пояснением ошибки кторый возможно рна фронтенде будет поазан радом с ошибочным полем. В то же время ошибка например что у клиента недостаточно денег на счете если сама сумма входит в заданные рамки это уже ошибка приложения, или номкер телефона или почта прошли формальну валидацию при регистрации но клиент с такими значениями уже зарегистирован в системе.

tools.ietf.org/html/rfc7807

Придумали стандарт и было бы неплохо им пользоваться. По сути автор пришел к чему-то похожему с общим гарантированным форматом ошибки.

Я бы рекомендовала использовать и обрабатывать коды следующим образом:
2хх — ответ успешный, читаем ожидаемый ответ
4хх — ответ не успешный, и сервис гарантирует, что ретраи будут возвращать тоже самое:
ошибки валидации, ошибки доступа, ошибки роутинга
5хх — ответ не успешный, но клиенту возможно имеет смысл ретрайнуть запрос пару раз через некоторое время: отвалилась бд, недоступен зависимый сервис етк

Как видите:
1. Нет привязки к конкретным цифрам кода кроме последней.
2. Ошибка скорее всего в теле будет содержать подробности, описанные форматом по ссылке выше. Можно матчить по типу и обрабатывать (если хочется), а можно просто показать пользователю Details.
Only those users with full accounts are able to leave comments. Log in, please.