Хабрахабр

[recovery mode] REST страсти по 200

Все думал — с какой стороны зайти правильнее? Давно я хотел написать эту статью. Больше всего меня удивил тот простой факт, что статью начали вбивать в минуса, хотя она даже не декларировала что-то, а скорее просто поднимала вопрос об использовании кодов ответа web-сервера в REST. Но, вдруг, недавно, на Хабре появилась подобная статья, которая вызвала бурю в стакане. А апофеозом стало то, что статья ушла в черновики… килобайты комментариев, мнений и т.д. Дебаты разгорелись жаркие. Многие стали кармо-жертвами, считай, ни за что 🙂 просто исчезли.

И я очень надеюсь, что она будет полезна и прояснит многое. В общем, именно судьба той статьи побудила меня написать эту.

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

HTTP

Первым делом нужно очень четко разделить слои. Слой транспорта — http. Ну и собственно REST. Это фундаментально важная вещь в принятии всего и “себя” в нем. Давайте сначала поговорим только о http.

И я не оговорился. Я использовал термин “слой транспорта”. Да, он базируется на tcp/ip. Все дело в том, что сам http реализует функции транспортировки запросов к серверу и контента к клиенту независимо от tcp/ip. Но нет. И вроде нужно считать именно его транспортным. это не соединение клиент-сервер. И вот почему — сокет-соединения не являются прямыми, т.е. Могут быть агрегированы или напротив декомпозированы. Как http запрос, так и http ответ могут пройти длинный путь через уйму сервисов. Могут кэшироваться, могут модифицироваться.

у http запроса как и http ответа есть свой маршрут. Т.е. Прошу на это обратить особое внимание. И он не зависит не от конечного бэка, не от конечного фронта.

Они могут быть очень сложными. Маршруты http не являются статическими. При этом, сам бэк может реализовывать собственную стратегию работы с запросами. Например, если в инфраструктуру встроен балансировщик, полученные запросы он может послать на любую из нод бэка. Так работает Интернет. Часть из них пойдет на микросервисы напрямую, часть будет обработана самим web-сервером, часть дополнена и передана кому-то еще, а часть выдана из кэша и т.п. Ничего нового.

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

Отмечу, что в ответе с кодом 503 предусмотрен заголовок Retry-After. К примеру, если балансировщик встретится с кодом ответа от бэка 503, при передаче запроса, он может принять это за основание считать, что нода временно недоступна. Причем, подобные стратегии реализуются “из коробки” web-серверами. Получив из заголовка интервал для повторного опроса, балансировщик оставит ноду в покое на указанный срок и будет работать с доступными.

Что должен сделать балансировщик? Небольшой офтопик для глубины понимания — а если нода ответила 500? И многие ответят — конечно, все 5xx основание для отключение ноды. Переключать на другую? Код 500 это код неожиданной ошибки. И будут неправы. той которая может больше никогда и не повториться. Т.е. Т.е. И главное, что переключение на другую ноду может ничего и не изменить. мы просто отключаем ноды без малейше пользы.

Локальный WEB-сервер ноды, может переводить саму ноду в статус недоступности при большом количестве ответов 500. В случае с 500 нам на помощь приходит статистика. Результат тотже, но теперь, такое решение осмысленно и исключает “ложные” срабатывания. В этом случае, балансировщик обратившись на эту ноду, получит ответ 503 и не будет ее трогать.

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

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

REST

Задам риторический вопрос — что это такое? И что вы ответили себе на него? Не буд давать ссылки на очевидные пруфы, но скорее всего не совсем то, чем он является по сути 🙂 Это лишь идеология, стиль. Некие соображения на тему — как лучше общаться с бэком. И не просто общаться, а общаться в WEB инфраструктуре. Т.е. на базе http. Со всеми теми “полезными штуками”, о которых я написал выше. Конечные решения по реализации вашего интерфейса остаются всегда за вами.

Например, для websocket он есть. Вы задумывались почему не придуман отдельный транспорт для REST? Почему бы не сделать такую же для REST? Да, он тоже начинается с http, но потом, после установки соединения, это вообще отдельная песня.

Есть прекрасный, уже готовый, выверенный протокол — http. Ответ прост — а зачем? Позволяет, реализовывать сложные, выскокодоступные сервисы, способные справляться с большой нагрузкой. Он хорошо масштабируется. Все, что нужно — ввести некие концептуальные правила, чтобы разработчики друг друга понимали.

Это неотделимые сущности. Отсюда следует простой, очевидный вывод — все, что присуще http, присуще и REST. Для любого сервера REST запрос ровно такой же как и любой другой. Нет отдельного заголовка REST, нет даже намека на то, что REST это REST. REST это только то, что у нас “в голове”. Т.е.

Коды ответа http в REST

Давайте поговорим о том, каким же кодом должен отвечать ваш сервер на REST запрос? Лично мне кажется, что из всего выше написанного уже очевиден ответ, что т.к. REST не отличается от любого другого запроса, он должен быть подчинен ровно тем же правилам. Код ответа — неотъемлемая часть REST и он должен быть релевантен сути ответа. Т.е. если не найден объект по запросу, это 404, если клиент обратился с некорректным запросом 400 и т.д. Но, чаще всего, дебаты на сем не заканчиваются. Поэтому, продолжу и я.

А кто вам запретит? Можно ли отвечать на все кодом 200? Правда, в основе такого подхода лежит очень простой тезис — моя система идеальная, у нее не бывает ошибок. Пожалуйста… код 200 такой же код как и другие. Если вы человек, который может создавать такие системы — этому можно только позавидовать!

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

Т.е. И так, принимая код 200 как единственно верный, мы берем на себя обязанности на разработку целого слоя (критического слоя) системы — обработку ошибок. И начинается постройка своего “велосипеда”. труд многих людей по разработке этого слоя отправляется в утиль. Но эта мегастройка обречена на провал.

Если мы собираемся на все отвечать 200, нам самим придется обрабатывать ошибки. Начнем с кода. Каждый сегмент кода мы оборачиваем дополнительным кодом. Классическим методом является try конструкции. Например что-то кладут в лог. Обработчиками, которые что-то делают полезное. Что позволит локализовать ошибку. Что-то важное. Или если ошибка возникла в обработчике ошибки? А если ошибка возникла не там где ее ожидали? эта стратегия на уровне кода нерабочая априори. Т.е. ОС наконец. И в конце концов, обрабатывать ваши баги будет интерпретатор или платформа. Не нужно его прятать, его нужно находить и фиксить. Суть бага в том, что вы его не ждете. И более того — правильно. Поэтому, если на какие-то запросы REST ответит ошибкой 500 это нормально.

Потому, что: Давайте еще раз вернемся к вопросу — почему это правильно?

  1. Код 500 это инфраструктурный маркер, на основании которого нода на которой возникает проблема может быть отключена;
  2. Коды 5xx это то, что мониторится и если такой код возникает, любая система мониторинга тут же вас известит об этом. И служба поддержки вовремя сможет подключиться к решению проблемы;
  3. Вы не пишите дополнительный код. Не тратите на это драгоценное время. Не усложняете архитектуру. Вы не занимаетесь несвойственными вам проблемами — вы пишите прикладной код. То, что от вас хотят. За что платят.
  4. Трейс который выпадет по ошибке 500 будет куда как полезнее, чем ваши попытки его превзойти.
  5. Если REST запрос вернет 500 код, фронт уже на моменте обработки ответа будет знать, по какому алгоритму его обрабатывать. Причем, суть дела никак не изменится, вы как ничего толкового не получили и с 200, так и с 500. Но с 500 вы получили профит — осознание того, что это НЕОЖИДАННАЯ ошибка.
  6. Код 500 придет гарантированно. Независимо от того как плохо или хорошо вы написали свой код. Это ваша точка опоры.

Отдельно забью гвоздь во все “тело” кода 200:

Даже если вы очень сильно постараетесь избежать иных кодов ответа от сервера кроме как 200 на ваши запросы, вы не сможете это сделать. 7. И вы ДОЛЖНЫ будете такой ответ обработать корректно. Вам может ответить на ваш запрос любой сервер посредник, совершенно любым кодом.

Итого, на логическом уровне борьба за код 200 бессмысленна.

Очень часто слышу мнение — код 5xx не прикладного уровня, его нельзя отдавать бэком. Теперь давайте вернемся к инфраструктурному уровню. Отдавать можно. Кхм… ну… тут есть противоречие в самом утверждении. Вот так вернее. Но код это не прикладного уровня. Для понимания этого, предлагаю рассмотреть кейс:

У вас несколько ДЦ на каждом свой канал связи к некоему приватному сервису. Вы реализуете шлюз. И есть канал коммуникации с Интернет. Ну к примеру, к платежке по VPN. Вы получаете запрос на операцию со шлюзом, но… он оказывается недоступен.

И так, что вы должны ответить? Кому? Это проблема именно инфраструктурная и именно бэк столкнулся с ней. Конечно, нужно смело отвечать 503. Эти действия приведут к тому, что нода будет отключена балансировщиком на какое-то время. При этом, балансировщик, при правильной настройке, не разрывая соединение с клиентом, отправит запрос в другую ноду. И… конечный клиент, с великой долей вероятности получил 200. А не кастомное описание ошибки, которая ему ничем не поможет.

Где и какой код использовать

Вопрос непростой. На него нет однозначного ответа. Для каждой системы проектируется транспортный слой и коды в нем могут быть специфичные.

Их можно легко найти и опять же не буду очевидные пруфы приводить. Есть принятые стандарты. Все дело в том, что обработчики кода могут вести себя по разному, в зависимости от реализации и контекста “понимания кода”. Но приведу неочевидный — developer.mozilla.org/ru/docs/Web/HTTP/Status
Почему его? А в некоторых сервисах есть свои, кастомные коды. К примеру, в браузерах есть стратегия кеширования завязанная на коды ответа. Например, CloudFlare.

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

Корни зла

Уже третий проект, в который я прихожу страдает кодом 200 в REST. Именно страдает. Другого слова нет. Если вы внимательно прочли все до текущего момента, вы уже понимаете, что как только проект начинает расти, у него появляется потребность в развитии инфраструктуры, в ее устойчивости. Код 200 убивает все эти потуги на корню. И первое, что приходится делать — ломать стереотипы.

Это, можно сказать, детская травма. Корень зла, мне кажется лежит в том, что код 500, это первое, что web-разработчик встречает в своей профессиональной деятельности. И все его старания поначалу сводятся к тому, чтобы получить код 200.

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

Но… он не умеет пользоваться логами. Далее, с развитием разработчика, у него возникают потребности в управлении багами собственного приложения. Он учится. Не умеет настраивать web-сервер. Потому, что они ему доступны и он может их быстро сделать. И рождаются те самые велики. И этот велик становится его спутником на достаточно длительный промежуток времени, пока… пока у него не появляются реально сложные, многокомпонентные задачи. Далее, на этот велик он монтирует новые колеса, усиливает раму и т.д. И тут, как говорится — вход в супермаркет с великами и на роликах запрещен.

S.: Автор упомянутой статьи восстановил ее из черновиков — habr.com/ru/post/440382, поэтому можно ознакомиться с ней тоже. P.

P. P. Я не буду отвечать на комментарии, прошу понять меня правильно. S.: Я постарался изложить все грани необходимости использования релевантных кодов ответа в REST. Огромное спасибо за то, что вам хватило терпения прочесть статью! С большим вниманием буду их читать, но добавить мне нечего.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть