Pull to refresh
375
0

Разрабатываю API более 10 лет

Send message

48 полей — это уже проблема само по себе, такие объекты надо декомпозировать.
Но вообще с т.з. разработчика клиента как раз удобнее все 48 посылать, чем выбирать изменившиеся.


Хорошая статья, хочу больше.

Дык собсно https://twirl.github.io/The-API-Book/docs/API.ru.html#chapter-11

Я в главе с советами даже хотел использовать WinAPI как пример плохого дизайна


HWND hwnd = CreateWindowEx(
    0,                              // Optional window styles.
    CLASS_NAME,                     // Window class
    L"Learn to Program Windows",    // Window text
    WS_OVERLAPPEDWINDOW,            // Window style

    // Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

    NULL,       // Parent window    
    NULL,       // Menu
    hInstance,  // Instance handle
    NULL        // Additional application data
    );

Ну кто, скажите на милость, мешал размер и положение в одну структуру объединить, зачем 4 отдельных параметра? Зачем два разных window style?


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

Да, Win API, с одной стороны, хороший пример — ребята продолжают волочь этот крест уж не знаю, лет 30 поди.
С другой — само API, конечно, отвратительнейшее. Функции с 12 аргументами и прочие чудеса индусского кода.

Так ничем, я как раз и говорю, что в идеальном мире что залогировано (какая причина ошибки), то и передано должно быть в ответе (заголовком ли, боди, неважно).

А как RFC должно с этим помогать?

Например вот так:


От этой ошибки клиентам нет абсолютно никакого толку, если только в ответе не указано, какое конкретно поле имеет недопустимое значение — и вот как раз именно это стандарт и не стандартизирует!

или вот так:


Самый очевидный пример — это ошибка 401 Unauthorized: по спецификации она обязана сопровождаться заголовком WWW-Authenticate — чего, в реальности, конечно никто не делает, и по очевидным причинам, т.к. единственное разумное значение этого заголовка — Basic (да-да, это та самая логин-парольная авторизация времён Web 1.0, когда браузер диалоговое окно показывает). Более того, спецификация в этом месте расширяема, никто не мешает стандартизовать новые виды realm-ов авторизации — но всем традиционно всё равно.

На прочие ваши вопросы также есть ответы в тексте.


Отдельно хотелось бы отметить, что:


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

Тем не менее, в HTTP полно ошибок на невалидное значение одного конкретного параметра запроса — 405, 411, 414, 416, например. HTTP юзабелен?

Что изменилось-то от этого? Разнородные ошибки все равно под одним кодом.

«Заказ» = «переведи корзину из статуса драфт в статус коммит». Эта операция возможна, только если клиент обладает полной информацией о статусе ресурса «корзина».
Ну и вообще, стандарт недвусмысленно заявляет, что


This code is used in situations where the user might be
able to resolve the conflict and resubmit the request. The server
SHOULD generate a payload that includes enough information for a user
to recognize the source of the conflict.

что автоматически делает 409 дефолтным кодом для ошибок в бизнес-логике.

Это абсолютно одно и то же.
Есть ресурс «корзина» с каким-то id. В корзине лежит N предметов по определённой цене. У корзины есть ревизия, которую может менять как внутреннее, так и внешнее API.
Цена предмета изменилась → пришёл callback → ревизия корзины увеличилась. Клиент получит 409.
От того, что «ревизия» корзины меняется не по callback-у, а по запросу, смысл ошибки не меняется.
> Так что всё-таки прямое назначение HTTP — прикладное, а не транспортное.

Не могу не отметить, что сама модель OSI является сферическим конём в вакууме, описывающим какой-то выдуманный сетевой стек (достаточно сказать, что TCP/IP в него не ложится, т.к. формально и TCP, и IP являются протоколами транспортного уровня). В данном случае прикладной уровень надо делить на два — прикладной уровень протокола (HTTP) и прикладной уровень бизнес-логики.
Во-первых, 409 подходит под все описанные кейсы. Он означает буквально следующее: пока ты собирал корзину, кто-то другой модифицировал разделяемый ресурс (остаток товара, цену, количество текущих заказов), и теперь операция невозможна до разрешения конфликта.

Во-вторых, предлагаемые вами решения не решают проблему, а усугубляют. Теперь под `403` будут свалены и ошибки авторизации (кулхацкеры, которые пытаются найти дырки в безопасности) и чистая бизнес-логика (пользователю не разрешено иметь больше 3 одновременных заказов). Под `401` теперь живут и те же кулхацкеры, и протухшие токены. Ну и замена 409 на 422 ничего не меняет в постановке задачи.

> Если авторизация выполняется средствами HTTP и запрос «запрещён» именно из-за авторизации, то надо отвечать кодом 401.

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

> Код 403 из-за авторизации возвращают только тогда, когда авторизация делается не средствами HTTP. Т.е. не через заголовок Authorization или другие заголовки, URL или параметры в теле запроса. А например авторизация по IP-адресу или SSL сертификату.

Вы точно читали стандарт?


The 403 (Forbidden) status code indicates that the server understood
the request but refuses to authorize it. A server that wishes to
make public why the request has been forbidden can describe that
reason in the response payload (if any).

If authentication credentials were provided in the request, the
server considers them insufficient to grant access. The client
SHOULD NOT automatically repeat the request with the same
credentials. The client MAY repeat the request with new or different
credentials. However, a request might be forbidden for reasons
unrelated to the credentials.


В отношении 403 RFC не предписывает вообще ничего. Креденшалы могут присутствовать в любом виде.

А вот 401 как раз регламентирован другим RFC tools.ietf.org/html/rfc7235
Формально 401 можно использовать если, и только если авторизация устроена согласно этому RFC. Я, кстати, написал об этом в тексте явно, вот здесь: «Многие разработчики просто не читают спецификации ¯\_(ツ)_/¯. Самый очевидный пример — это ошибка 401 Unauthorized: по спецификации она обязана сопровождаться заголовком WWW-Authenticate — чего, в реальности, конечно никто не делает» и далее.

> Логировать важные ошибки непосредственно в самом приложении и настроить мониторинг этих логов, а не только смотреть на access-логи, например nginx-а, который стоит перед приложением. Или использовать access-логи, которые пишет само приложение — ему то ведь известны все детали и оно может добавить их в свои access-логи.

Вы сейчас пересказываете мою же статью. Я начал с того, что не существует решения, которое ошибки веб-приложения агрегирует по семантике, а не по статус-кодам, его надо писать самому. И уж если писать, то зачем исключать эти данные из HTTP-ответа? Кому станет хуже, если ошибка есть *и* в логе, *и* в заголовке ответа?
Во-первых, грань, где здесь «бизнес»-логика, а где не бизнес — весьма тонка. Тот же 403 как пример.
Во-вторых, конечно, выдавать клиенту наружу детали 500-ки почти никогда не нужно. А вот 400-ки редко лишними бывают.
В-третьих, один из пойнтов следующий: мониторинг все равно нужен, и мониторинг как-то группировать ошибки все равно должен, одних подробных логов недостаточно.
> Хотя есть и чисто технические косяки в плане понимания предназначения конкретных статусов ответа и для чего они подходят, а для чего — нет.

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

Ну, например, на 429 и на разные виды 403-х вполне можно ожидать реакции от прокси.

> Не вижу пока причин придумывать что-то совершенно новое.

Так вроде никто и не выдумывает ничего нового.

> Если тут 404 заменить на 200, то ситуация кардинально не поменяется. Поэтому в той ситуации, которую автор «разгребал», в большей степени виноват не «дизайн» кодов, а сервис, который ошибочно возвращает не те коды.

А если б 400, то поменялась бы. Проблема была в том, что разработчики не в курсе, что 400 и 404 по-разному кэшируются.

> Я за несколько лет разработки так называемого RESTful API не встретил случая, когда не получилось подобрать стандартный HTTP-статус для классификации конкретной ситуации.

Это выглядит очень странно. Для всей бизнес-логики в номенклатуре HTTP-кодов по сути есть один 409, которым приходится покрывать кучу разных кейсов. На примере банального интернет-магазина:
* заказ уже был создан — 409
* заказ нельзя создать, потому что не разрешается иметь более N заказов одновременно — 409
* заказ нельзя создать, потому что товар в корзине закончился — 409
* заказ нельзя создать, потому что цена товара изменилась — 409
Скажете, эти кейсы не надо различать?

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

> Гораздо проще в том месте где случилась ошибка, отправить подробности о ней в логи или в сервис типа Sentry, а не пытаться «угадать» ошибку по HTTP-ответу, предназначенному в первую очередь для клиента, и только во вторую — для мониторинга.

Так а как оперативный мониторинг-то строить? На примере тех же 403-х — как сделать, чтобы мониторинг начинал орать, если превышено критическое значение протухающих токенов?
> Как именно «большое количество ПО» должно трактовать какой-нибудь отдельно взятый HTTP API? Что конкретно имеется ввиду?

Имеется в виду следующее: когда вы пишете proxy_pass в конфигурации nginx — nginx начинает как-то трактовать стандарт. Он не пересылает запрос как есть. То же касается, скажем, используемого на клиенте фреймворка работы с сетью, и особенно API Gateway-ев, без которых микросервисную архитектуру не построишь.

> Не существует такого разделения. В чем именно состоит разница между REST 2000 и REST 2008?

В 2000 Фьелдинг написал абстрактно. Под «hypermedia as the engine of application» можно много чего понимать. Собственно так и вышло — каждый немедленно истрактовал REST в свою степь.
Об этом просто не подумали. Почти никто не знает, что 404 по умолчанию кэшируются.
> Никто не мешает реализовать RPC-интерфейсы поверх HTTP, получая его семантику там, где она нужна. Более того, большинство из так называемых «RESTful API» именно так и делают — они представляют собой ориентированный на данные CRUD RPC, а термин REST используется сугубо как популярный баззворд.

Абсолютно ничто не мешает, кроме того факта, что HTTP как-то знает и трактует большое количество ПО в мире, а под ваш протокол придётся написать кастомные имплементации.

> Центральная идея REST — Uniform Interface, неотъемлемой частью которого является т. н. HATEOAS: REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state. Филдинг подтверждает это в том числе в своем посте REST APIs must be hypertext-driven

Этот пост Фьелдинг написал спустя восемь лет после своего дисера — когда REST уже давно отправился в свободное плавание как концепция. Если б Фьелдинг что-то подобное написал в 2000, недалеко бы его дисер разошёлся.

REST по Фьелдингу-2000 небесспорная, но стройная концепция. REST по Фьелдингу-2008 попросту не существует.
RPC тяжело кэшировать и масштабировать. Чтобы раскидать по шардам — надо прочитать ответ и вычленить из него данные. Чтобы промежуточная прокси узнала, можно ли положить ответ в кэш — аналогично. А уж идемпотентна ли операция из сигнатуры запроса вообще никак не узнать.

Идея REST заключается строго в следующем: есть метаинформация запроса (http-коды, методы, URL, заголовки), давайте построим систему, в которой все агенты умеют их понимать и трактовать. Т.е. например если метод GET — значит, запрос немодифицирующий, можно его префетчить, как-то так.
Абсолютно ничего не мешает ровно те же данные и в заголовки записать.
Разница между тем, в какую часть ответа писать данные, заключается только в том, о чем я написал: манипулировать заголовками на уровне прокси гораздо проще и удобнее, нежели телом ответа, в т.ч. и логировать проще.
Спасибо. А опечатка уже пофикшена в коммите github.com/twirl/The-API-Book/commit/ffc186c2f483f3f6c1e004db5a0b3c37cc554edf
В реальной жизни его редко употребляют из соображений безопасности.

Information

Rating
Does not participate
Location
Россия
Registered
Activity