Comments 110
Обязательно (и об этом джуниоры не знают) в ответ возвращайте ошибки. Это семантично и правильно.
А ФБ, яндекс, вроде инстаграм и нетфликс об этом не знают и возвращают 200 на всё, наверно глупые.
Эта тема многократно обсуждалась на хабре. Если бы вы писали, например, мобильные приложения на типизированных языках, то не были бы столь категоричны. Я в своих сервисах тоже везде отвечаю 200 со стандартным телом, где есть поле isSuccess, есть код и есть описание ошибки, в случае ее наличия. Это удобнее для всего, начиная от логирования и заканчивая клиентом.
Если бы вы писали, например, мобильные приложения на типизированных языках, то не были бы столь категоричны. Я в своих сервисах тоже везде отвечаю 200 со стандартным телом, где есть поле isSuccess, есть код и есть описание ошибки, в случае ее наличия.
Пишу на swift, до этого на obj-c писал. И нет, коды ошибок http крайне важный и помогают на этапе до парсинга определить что произошла ошибка. И я всегда настаиваю при работе с бекенд-коллегами чтобы ошибки отдавались с соответствующим http кодом.
Например что-то произошло с авторизацией. Слетела она по какой-то причине. Можем послать 401 код, в любом запросе, и это будет означать что авторизация не прошла — надо локально почистить данные и выполнить специфичные для этой ошибки действия (например попросить авторизоваться).
А когда всё приходит со статусом 200 — то нужно каждый раз разбираться и парсить — ошибка тут пришла (и какая — надо просто показать сообщение или нужны действия) или объект.
P.S. вообще, то что описано очень похоже на то, как реализован REST модуль в Yii2. Одна из самых удобных, на мой взгляд, реализаций, REST, с которыми мне приходилось работать.
Потому здоровый минимум ответов бэка, например:
200 OK — сервер справился с запросом, что из этого вышло — читайте в приложенном объекте,
403 Forbidden — низзя,
404 Not Found — не туда,
500 Internal Server Error — случилось что-то очень страшное, нет смысла объяснять что именно, действуйте по ситуации.
Ну и 401 Unauthorized, если хочется на прикрытые эндпоинты достукиваться вот прям из браузера.
Остальное — в поле .error, по отсутствию которого легко определить удавшийся на 100% запрос.
Затариваться на проекте всей номенклатурой HTTP кодов, а также PUT, PATCH, DELETE и прочим сатанизмом — прямой путь к знакомству с особенностями работы корпоративных и просто кривых проксей. Упомянутые монстры соцсетизма не просто так эти дела упростили, а чтобы сэкономить на поддержке клиентов у которых «тормозит, не работает, ставлю 1 балл пока».
Другое личное ИМХО в том, что разрешать в продакшне неконтролируемые запросы от фронта (свободные фильтры с неограниченными параметрами и прочую логику построения запроса к модели, в REST форме или любой другой — дичайший моветон и грабли.
Берегите лбы, товарищи!
Собственно я такой блок ответов и использую.
200 — ок
401/403 — проблема авторизации (+ error поле)
400/500 — просто ошибка, что-то пошло не так (+ error поле)
А серверу, в свою очередь, следует использовать эти коды, если есть серьёзные основания полагать, что к нему пришёл вообще не REST-клиент, а нечто странное: опечатавшийся человек, гугловый робот и т.п.
2хх — всё удачно
4хх — Вы чет не то прислали
5хх — Ой, чет на сервере набаранили
3хх — сервисные
И даже страшно представить сколько у вас бойлерплейта на клиенте для сортировки ошибок.
404 битый url — просто мы не получим сообщение от json в body от сервера и выводим ошибку вида «что-то пошло не так, попробуйте позже или сообщить нам о проблеме»
Да, всегда одинаковая. Если нет тела, то показываем «универсальную» ошибку.
Я больше придерживаюсь принципа — если всё ОК — то код 200.
Во всех остальных случаях — любой не 2хх код и текст ошибки в теле.
А 401 и прочее — это для обработки «особых» случаев и особых ошибок.
Ну можно упороться и в тело помимо ошибки определить ещё какой-то протокол, по которому определять действия. Но это уже изврат я считаю.
А с моим подходом при любом 200 тело парсится в один и тот же объект. А все остальные ответы сразу отправляются в невалидные, потому что они возможны только при отладке или падении чего-то. Это значительно упрощает клиента.
+ бывают разные типы ошибок.
Поэтому я предпочитаю, что если успех — то это success блок, и отрабатывается по одному алгоритму.
А если ошибка — то в отдельном месте определяем что за ошибка (от сервера пришла, сетевая ошибка, сервак лёг) и обрабатываю это отдельно.
Мне кажется что чёткое разделение — вот здесь точно нет ошибки,
а здесь точно ошибка — более гибкое и меньше всяких условий получается.
Но это моё ИМХО, основанное именно на моём опыте.
Многие либы обертки для http бросают исключения при не 200, а это противоестественно когда код ветвится за счет try catch, для этого придуман if.
Задам встречный вопрос, может фб, Инстаграм и Яндекс просто знают, что многие, кто будет использовать их апи, не читают стандарты и не поймут эти коды или проигнорируют их? Или что пользователи напишут что-то вроде
raise if response.code >= 400
к примеру. И давать советы забить на http коды в общем-то только поощряют эту практику.Никто в принципе не заставляет пользователей апи использовать http коды, если удобно проверять поле error какое-нибудь. Но возвращать их — хороший подход. И писать клиент, используя их, в общем случае проще и удобнее.
от логированияесли только логируете вы в всё в один файл и пользователей у вас 1.5 человека. Фильтровать по статусу весьма удобно, когда это необходимо.
GET /articles?fields[article]=title НТТР/1.1
Через include можно наподключать смежных сущностей, а через fields задать полей, которых мы будем ждать в смежных сущностях.
Например:
GET /articles?include=authors&fields[article]=title&fields[author]=id,name
GET /articles?fields=title&include=authors&fields[author]=id,name
Но я думаю, что при составлении спецификации решили, что в данном случае единообразие интерфейса и минимизация ошибок важнее избыточности.
В вашем примере fields одноременно строка и массив. Сервер просто проигнорирует fields=title в подобном запросе.
Это зависит от реализации сервера.
RFC 3986 никак не определяет формат передачи массива через GET-параметры.
В общем случае на сервер придёт строка вся query string
.
Если сервер разберёт её по аргументам, то, скорее всего, будет вот так:
{
'fields': 'title',
'include': 'authors',
'fields[author]': 'id,name'
}
Если игнорирование и будет, то только если оно специально так настроено.
Вы правы, мое замечание относится только к PHP. Он распарсит эту строку как я написал:
array(2) {
["include"]=>
string(7) "authors"
["fields"]=>
array(2) {
["article"]=>
string(5) "title"
["author"]=>
string(7) "id,name"
}
}
http://sandbox.onlinephpfunctions.com/code/549e94093e8565c828811eb2506f80d09a2fa390
Ну, наверное, вот для этого:
GET /articles?include=author&fields[article]=title,body&fields[person]=name
Чтобы стандартный парсер Query String в любом веб-фреймворке преобразовал это в
{
include: "author",
fields: {
article: "title,body",
person: "name"
}
}
Тяжело использовать параметрические запросы с переменным числом параметров…
На самом деле, автор исходит из посылки, что изменение API — это сильно больнее сложности.
На практике, я часто вижу примеры, когда перебарщивают с универсальностью, порождая сложность. А потом оказывается, что эта универсальность не так уж и нужна. Зато сколько времени потрачено на вылизывание всяких гипотетических кейсов от тестеров, когда шлются дикие комбинации параметров, которые ломают логику, и тестеры радостно кричат «Ага!». Когда запрос четко валидируется на на уровне схемы и мапится в статику, это делает API сильно проще и надежнее. А добавить поле в ответ — ну, отрефакторим. Если нормально налажена работа в команде и бэкенд не пребывает в состоянии холодной войны с фронтендом, то никакой драмы. Ну и версионирование никто не отменял.
1. Ограничивает жадность клиентов.
2. Позволяет извлекать большие сложные графы, описание которых сложно затолкать в URL.
Вполне себе хорошая вещь.
Остались только ссылки в конце презентации на спецификации XML-RPC и JSON-RPC для дальнейшего изучения.
И еще возник вопрос допустимости использования не закодированных символов "[" и "]" в URL.
Some clients, like IE8, lack support for HTTP’s PATCH method. API servers that wish to support these clients are recommended to treat POST requests as PATCH requests if the client includes the X-HTTP-Method-Override: PATCH header. This allows clients that lack PATCH support to have their update requests honored, simply by adding the header.jsonapi.org/recommendations/#patchless-clients
На вопрос тоже нашёл ответ в спеке:
Note: The above example URI shows unencoded [ and ] characters simply for readability. In practice, these characters must be percent-encoded, per the requirements in RFC 3986.jsonapi.org/format/#fetching-sparse-fieldsets
Some clients, like IE8...
— нет, не правильно, дело не в клиенте, а в настройках корпоративного proxy-сервера которые режут все запросы клиента, кроме простейших GET/POST. Это касается любых клиентов, Хром, FF-наипоследнийший — не важно. К серверу запрос придет от прокси или не придет. Короче, экзотические типы запросов, это риск потерять часть функциональности для некоторых пользователей. Ваше замечание со ссылкой на спецификацию, тоже актуально, так как редкий сервер или бэкенд фреймворк будет конвертировать «X-HTTP-Method-Override: PATCH» в PATCH (точно не знаю, не проверял, но испытываю сильнейшие сомнения). Как результат на бэкенде мы получим POST, если запрос вообще до нас дойдет.
these characters must be percent-encoded
— это я и имел в виду, из статьи это явно не следует и велика вероятность что фронтэнд разработчик забудет закодировать такие символы
http://springbot.github.io/json-api/extensions/bulk/#deleting-multiple-resources
DELETE /articles
Content-Type: application/vnd.api+json; ext=bulk
Accept: application/vnd.api+json; ext=bulk
{
"data": [
{ "type": "articles", "id": "1" },
{ "type": "articles", "id": "2" }
]
}
Note: RFC 7231 specifies that a DELETE request may include a body, but that a server may reject the request. This spec defines the semantics of a server, and we are defining its semantics for JSON:API.
Слегка не корректный тезис. И в целом это не так. Хотя подозреваю, что может зависеть от реализации.
Например, разве это не разные запросы, которые должны давить разные ответы:
/users
/users/1
/users/2
???
А как отличие между /users/1
и /users/2
вообще связано с сессией пользователя?
/users/1
Сервер понимает, что ему нужно отдать юзера с ID = 1, лезет в БД и достает всю необходимую информацию. Так?
Теперь посмотрим вариант с сессией:
/me + куках session_id
В этом случае происходит все ровно тоже самое. Запрос содержит в себе все что нужно серверу для ответа. Сервер понимет, что ему нужно извлечь информацию для сессии с данным ID, лезет в базу или файлы, в зависимости от того, где лежат сессии и возвращает ответ.
Еще раз, ответ возвращается ровно на основе данных запроса, никаких иных состояний на сервере не используется. Хранящаяся на сервера сессия, ниче в данном случае не отличается от любых данных там же. Разве что тем, что она умеет истекать, но вообще говоря есть и другие типы данных с таким поведением и термин stateless никак не характеризует постоянство данных, которые запрашиваются и также параметров самого запроса.
Основная мысль в том, что сессия — это такая же часть запроса, которая никак не влияет та то, является ли сервер stateless. Для простоты вынесем сессию из кук в get-параметры:
/users/1 — данные пользователя с ID = 1
/users/2 — данные пользователя с ID = 2
/users/null — ошибка, потому что в данном endpoint мы ожидаем ID юзера
/me?sid=lk235lk23lk5jl2k5 — данные юзера с сессией lk235lk23lk5jl2k5
/me — ошибка, потому что в данном endpoint мы ожидаем сессию
То есть, мы видим, что это разные запросы и ответы четко зависят от того, что пришло в запросе. Сервер никак не влияет на них, не хранит никакого скрытого состояния.
То что сессия скрытая от глаз и чаще всего хранится в куках, не делает сервер stateful. Наоборот, это способ временно делегировать стейт клиенту, без его участия, чтобы сервер мог оставаться stateless, а значит RESTful. Именно это позволяет серверу не хранить этот стейт у себя до следующего запроса.
Вы упускаете из виду кеширование результатов запроса. Результаты запроса /users/1 могут быть кешированы какое-то разумное время. Результаты запроса /me + в куках session_id кешированы быть не могут...
Ну и семантика у таких запросов тоже разная. /users/1 — это данные пользователя 1, независимо от того кто их смотрит.
Я лишь говорю о том, что запрос с сессией это такой же запрос к stateless серверу, как и без неё. И именно то, что один и тот же запрос отличающийся лишь наличием или отсутствием сессии возвращает разные результаты с сервера и доказываться что сервер истинно stateless.
В случае с jwt мы можем где-то на api gateway, policy descision point, самом сервисе обработки запроса, да даже на клиенте, замаппить /me/ в /users/1 вообще без обращения к каким-либо ресурсам, что сильно упрощает инфраструктуру и логику приложения. При этом /users/1 может шардироваться между инстансами и роутится по логике mod(userId,10), кэшироваться, кэшироваться в самом браузере на основе etag и вообще подвергаться всяческим оптимизациям просто потому, что оптимизируют как правило правильные решения, а не костыльные поделки. И информации по работе с правильными решениями значительно больше
В общем случае отличается. Кука, особенно сессионная — атрибут сеанса связи, атрибут конкретного агента, получаемый от сервера. Чтобы получить куку нужно сначала сделать запрос на сервер, а перенос кук ручками в общем случае рассматривается как атака.
Кука — это средство доставки параметра запроса. Один из множества вариантов, отличающийся спецификой клиента (браузера) и тем, что она управляется с сервера.
Но это никоим образом не отменяет того, что кука приходит к запросом. Без куки, сервер не вернет сессию, то есть сам по себе не хранит состояние. То есть опять же «вся необходимая информация для ответа, приходит в запросе».
Вы смотрели последний сезон «True detective»? Там он с утра встает и слушает то, что записал с вечера для себя на диктофон, потому что он не помнит. Это хорошая аналогия, чувак stateless и в курсе этого, поэтому он записывает себе куку, чтобы оставаться stateless, но иметь возможность «вспомнить» то, что нужно при следующем запросе.
Секьюрность, особенности кук и все остальное вообще к вопросу не имеет отношения никакого. Это вообще больше специфика браузерной среды, а не REST и HTTP. В этом всем вы правы.
Stateless означает, что сервер не хранит никакого состояния о сессии клиента на стороне сервера.
Сессия клиента хранится на клиенте. Сервер stateless означает, что каждый сервер может обслуживать любого клиента в любое время, нет "близости" сессий или "липких" сессий. Соответствующая информация о сессии сохраняется на клиенте и при необходимости передается на сервер.
Это единственный способ масштабирования до миллионов одновременно работающих пользователей.
Полее подробно с картинками описано в диссертации Роя Филдинга.
Если же речь о самих данных сессии, тогда нужно уточнять дальше. Хранится на сервере где? Локально на диске — это конечно блок для масштабирования, но в БД к специальной таблице — это другой случай. В конечном итоге из этой БД может читать любой инстанс сервера, поэтому это никак не может сказаться на масштабировании (масштабирование самой БД опустим, это не предмет разговора).
Резюмируя, сама по себе сессия и даже ее хранение на севрере никак не нарушает REST, но определенные способы ее применения и хранения могут в частных случаях его нарушать.
Если не согласы, тогда расскажите как это может повлиять на масштабирование и на возможность любого другого сервера/потока/инстанса, кроме того, который авторизовал юзера, обработать любой следующий запрос клиента? А ведь это главный стопер stateful-серверов — их сложно скейлить, потому что нужно гарантировать подключение к нужному инстансу, хранящему состояние.
Если так рассуждать как вы, то тот факт, что на сервере вообще есть база и в ней лежит стейт, сразу делает его stateful. Да и на вопрос мой вы так и не ответили.
В REST-подходе данные сессии хранятся на клиенте, он отправляет их на сервер с каждым запросом, каждый запрос может быть обработан независимо, серверу для его обработки нужен доступ только к хранилищу ресурсов, если таковое вообще есть.
Нет у вас оснований для таких выводов. Таблицы маршрутизации и проблемы балансировки к REST никакого отношения не имеют.
Они имеют отношение к stateful серверам. Раз наш умозрительный сервер не нуждается в этом, значит он stateless. Уж простите, тут либо одно, либо другое. От ответа на вопрос, вы таки ушли, потому что прекрасно понимаете, что несмотря на сессию, сервер остается stateless (REST пока за скобками).
Если вы используете серверные сессии, пускай и с расшаренными между серверами данными, то вы решаете эти проблемы, но не через REST-подход.
То есть вы признаете, что сервер остается stateless, не смотря на хранение данных сессии в БД? Кроме того, где вы это прочитали в архитектуре REST? Там написано, что для обработки любого запроса, серверу нужны лишь данные из этого запроса, то есть никакой локальный для сервера стейт не нужен, чтобы выполнить запрос клиента. Как таковые сессии, способы их хранения вообще нигде не упоминаются.
В REST-подходе данные сессии хранятся на клиенте, он отправляет их на сервер с каждым запросом
Можете предоставить цитату, где в трудах о REST написано про то, что все данные сессии должны каждый раз курсировать между клиентом и сервером? Как быть когда объем этих данных выйдет за размер куки (что бывает очень часто)?
, каждый запрос может быть обработан независимо, серверу для его обработки нужен доступ только к хранилищу ресурсов, если таковое вообще есть.
Наш умозрительный сервер, также может обработать любой запроса независимо и ему для обработки нужен только доступ к хранилищу ресурсов (БД).
Описанный вами вариант REST лишь на идеалогическом уровне может хоть как-то конфликтовать с тем, что пишу я. Но идеалогия не пишет сервера, а вот практически получается что разницы нет.
Очень узкое понимание stateless. Особенно в контексте принципов REST. Чисто технически, да, сервер (демон, сервис) stateless если он ничего в оперативной памяти не держит между запросами. Часто к этому добавляется пишет ли он что-то на ФС, но тут уже спорно: ФС — может быть примонтированной сетевой шарой, томом и т. п.
> Там написано, что для обработки любого запроса, серверу нужны лишь данные из этого запроса
Там написано «No client context shall be stored on the server between requests.». Общепринято клиентский контекст (под каким юзером залогинился, какой язык выбрал для сессии и т. п.) называть сессией.
> Можете предоставить цитату, где в трудах о REST написано про то, что все данные сессии должны каждый раз курсировать между клиентом и сервером?
Не совсем так, каждый раз не должны, только когда серверу они нужны для обработки запроса клиент должен их передать, возможно получив их ранее от сервера, но не обязательно — не имеет значения откуда он их возьмёт.
> Наш умозрительный сервер, также может обработать любой запроса независимо и ему для обработки нужен только доступ к хранилищу ресурсов (БД).
Не только к хранилищу ресурсов, но и к хранилищу клиентского контекста, в котором хранится, например, под каким юзером этот конкретный клиент последний раз залогинился.
> Описанный вами вариант REST лишь на идеалогическом уровне может хоть как-то конфликтовать с тем, что пишу я.
Нет, на вполне техническом. Если сервер определяет какую запись из БД показывать (и показівать ли) для URL /me на основании того какое имя пользователя и пароль были отправлены на /login, то это различие на вполне техническом уровне: ему нужно помнить что было отправлено и(или) какой результат обработки был.
Очень узкое понимание stateless.
Нет, это просто очень практическое понимание. Любые дополнительные смыслы, которые вы накладываете не более чем идеология. Если сервер не имеет непереносимого состояния и может скейлится безболезненно на любое кол-во инстансов, значит он stateless по определению.
Общепринято клиентский контекст (под каким юзером залогинился, какой язык выбрал для сессии и т. п.) называть сессией.
Технически настройки языка должны храниться в базе. Но даже если говорить о сессии на верных примерах, данные сессии физически не хранятся на сервере. Они храняться на сервере БД. То о чем вы пишете валидно, если брать классический PHP-шный подход с сессиями на файлах. Но я даже не уверен, что кто-то его использует сейчас. Мы же не в 2000-х ей богу.
Не совсем так, каждый раз не должны, только когда серверу они нужны для обработки запроса клиент
Вы так и не ответили на вопрос, что делать когда размер куки кончится.
Не только к хранилищу ресурсов, но и к хранилищу клиентского контекста, в котором хранится, например, под каким юзером этот конкретный клиент последний раз залогинился.
Эта информация всегда хранится в БД и также является ресурсом, например для админки проекта в разделе статистики по юзерам. Сами по себе данные сессии — ничем не отличаются от любых данных сервера. Да, они часто умеют «истекать», но такое поведение характерно далеко не только для сессий.
запись из БД показывать (и показівать ли) для URL /me на основании того какое имя пользователя и пароль были отправлены на /login,
Это не так. Сервер не пытается показать запись /me исходя из /login. Он знать не знает что клиент вообще когда-то ходил на /login, потому что это происходило в другом потоке или даже на другом физическом сервере. Текущий поток действует исходя исключительно из параметров запроса — пришел ID сессии «посмотрел» в базу, взял юзера. Не пришел ID сессии — «не посмотрел». А главное совершенно не важно откуда клиент взял этот ID. Хоть сам придумал.
это различие на вполне техническом уровне: ему нужно помнить что было отправлено и(или) какой результат обработки был.
Вообще не нужно ему это помнить. Еще раз, процесс логина 100% происходил в другом потоке, который уже почил со всем своим контекстом и на N% происходил на другом инстансе/сервере. При этом поток и сервер, который в данный момент обрабатывает /me не имеет у себя никаких рудиметнов от процесса логина и вообще знаний о существовании такого процесса.
Нет, это просто очень практическое понимание. Любые дополнительные смыслы, которые вы накладываете не более чем идеология.
Практическое понимание простое: если для ожидаемого результата запроса нужно предварительно сделать ещё один запрос (типичный пример — запрос на аутентификацию), то сервер stateful в контексте REST. Второй запрос не содержит всей необходимой информации, раз первый нужно делать.
Технически настройки языка должны храниться в базе.
Никому они не должны. Базы вообще может не быть, кроме как сессионной.
Вы так и не ответили на вопрос, что делать когда размер куки кончится.
А что делать когда место на диске с базой кончится? Кто вам сказал, что в случае хранения клиентского контекста на клиенте, он или его часть должна передаваться через куки?
Это не так. Сервер не пытается показать запись /me исходя из /login. Он знать не знает что клиент вообще когда-то ходил на /login, потому что это происходило в другом потоке или даже на другом физическом сервере.
Он чтобы это узнать лезет в сессионную базу, где в каком-то виде записано, что этот клиент ходил на /login и был идентифицирован как пользователь такой-то. Другой или тот же физический сервер, другой или тот же процесс и т. п. значения не имеет.
Вообще не нужно ему это помнить.
"сервер помнит, что юзер залогинен" в данном случае означает и "сервер знает или узнаёт, что юзер залогинен на параллельном сервер".
если для ожидаемого результата запроса нужно предварительно сделать ещё один запрос (типичный пример — запрос на аутентификацию), то сервер stateful в контексте REST.
Что значит «нужно»? Вот вам надо получить данные статьи, вы ID статьи подбирать будете научным тыком? Или вам сперва придется сделать запрос на список статей /posts, оттуда получить ID статьи и потом сделать запрос на статью /posts/:id? Исходя из вашей логики второй запрос также не возможен без предварительного первого запроса. Это тоже не REST теперь?
Никто не заставляет клиент делать запрос на аутентификацию, но если ему нужно получить /me, сама бизнес-логика требует этого просто потому, что endpoint /me требует передать ID сессии в качестве параметра. Точно также как endpoint /posts/:id требует передать ID поста.
Скажите, если запись будет такой /sessions/:id для вас это вдруг станет REST'ом?
Никому они не должны. Базы вообще может не быть, кроме как сессионной.
Что это за настройки языка, если юзер вынужден выбирать их каждый раз когда сессия закончилась? Чушь какая.
Кто вам сказал, что в случае хранения клиентского контекста на клиенте, он или его часть должна передаваться через куки?
Ок, а как еще? Будете POST запросом каждый раз в body слать? А если вам нужно будет отправить запрос на создание какой-то записи и при этом нужно будет передать сессию? Вы как это себе видите?
Он чтобы это узнать лезет в сессионную базу, где в каком-то виде записано, что этот клиент ходил на /login и был идентифицирован как пользователь такой-то. Другой или тот же физический сервер, другой или тот же процесс и т. п. значения не имеет.
Во-первых, нет никакой «сессионной базы». В одной базе может лежать таблица Users и таблица UsersSessions со связью has-many или еще как-то. Во-вторых, в таблице UsersSessions не записано что юзер куда-то там ходил. Там просто либо есть запись по session ID, либо ее нет. Либо она просрочена, либо нет.
Пишете какие-то откровенные глупости. Это тоже самое что сказать, что сервер полез в таблицу Posts и там в каком-то виде записано, что клиент ходил на endpoint создания поста. Ну бред же.
«сервер знает или узнаёт, что юзер залогинен на параллельном сервер».
WAT? Каком еще параллельном сервере? Он вообще не залогинен на сервере. Залогиненым на сервере можно быть, только если вы используете, ну я не знаю, HTTP Basic Auth что ли. Пользовательский запрос, либо имеет идентификатор сессии в системе в целом, либо не имеет. Никаких логинов «на параллельном сервере» не существует.
Если я руками на клиенте пропишу другой sessionId и передам его серверу с запросом, сервер никогда не сможет понять, что это не моя сессия. Именно потому, что он не хранит состояния напрямую связанного с клиентом, а действует исходя из параметров запроса и только.
Гугл выдаст мне ссылку и я без подбора открою. Или пришлёт кто-то в слаке. А если я должен сделать запрос на /posts перед тем как открыть /posts/:id то это не REST.
> Скажите, если запись будет такой /sessions/:id для вас это вдруг станет REST'ом?
Да, если сессия будет открываться как POST /sessions и в ответ давать 201 Location: /sessions/:id. Ну и любые изменения в сессии будут осуществляться через PUT или PATCH /sessions/:id, а другие ендпоинты не будут давать 401 при отсутствии session_id в запросе
Гугл выдаст мне ссылку и я без подбора открою.
Ого, то есть вы пишете свой фронтенд через запросы в Гугл? Необычное решение… сомневаюсь что оно REST, но определенно не стандартный подход. Ваши доводы становятся все более уклончивыми.
Если серьезно, я вас спрашивал со стороны клиента. Если вы следите за конвой диалога, то мы как бы поисковые системы вообще не упоминали. Речь идет о том, откуда вы узнаете ID поста, если не из другого endpoint?
Да и про SEO нигде в REST не написано. Что там должно индексироваться поисковиками и как.
Да, если сессия будет открываться как POST /sessions и в ответ давать 201 Location: /sessions/:id. Ну и любые изменения в сессии будут осуществляться через PUT или PATCH /sessions/:id, а другие ендпоинты не будут давать 401 при отсутствии session_id в запросе
А с чего вы взяли что это не так работает? И чем таким пренципиальным отличается POST /login от POST /sessions? Итог операции один — создание новой записи в таблица БД. Никто кстати не мешает использовать для логина POST /sessions чтобы унифицировать апи. Так сессия сразу более REST стала? Ну ладно.
а другие ендпоинты не будут давать 401 при отсутствии session_id в запросе
Совершенно не связанные вещи. Бизнес-логика может требовать сколь угодного кол-ва параметров запроса, чтобы вернуть ресурс. Для некоторых ресурсов, которым нужен доступ к сессии, нужно передать session id и нет в этом никакого нарушения REST. Это лишь параметр запроса.
Опять же, если вам будет понятнее то вот это:
/me/:sessionId или /settings/:sessionId ничем не отличается от того же самого, когда sessionId лежит в куке. Только похитить его чуточку сложнее.
Да, конечно.
REST — передача состояния представления.
Естественно, представление может быть получено откуда угодно.
Аппелировать к DRY тут бесполезно, потому что никакой DRY не нарушается. Создаются лишь дополнительные endpoint's, но внутренний код, которые валидирует, форматирует и достает данные не обязательно должен дублироваться каким-либо образом. Скорее всего это будет некая композиция существующего кода под каждый роут.
Опять же супер частая ситуация: проектировщик БД умный парень, заделал все с расчетом на будущее, таблиц куча, куча ключей, индексы и все по феншую. Тупое маппирование апи на таблицы станет причиной самоубийства вашего фронтендера. Но вдруг оказывается, что не смотря на то, что условный «BlogPost» состоит из самого материала, его автора, комментариев, какой-то мета-информации, информации о тематике и разделе, а также лайках и шерах, и все это лежит в разных таблицах, но на клиенте, эти данные практически не используются раздельно. Оказывается не нужны нам все эти:
/posts?query
/comments?query
/categories?query
/etc
Достаточно выдать клиенту некий ресурс «post», который уже включает в себя всю предметную область. Фронтенд доволен, бекенд доволен, потому что отвязал REST-ресурсы от таблиц БД и теперь у него развязаны руки.
Relationships (считай join) связывает представления, которые могут быть получены откуда угодно.
Если конечно вы не делаете некий public api для неограниченного круга лиц, тогда соглашусь, надо давать больше свободы. Но мы ведь вроде не про это.
Лично мне не кажется, что есть способ написать безопасный, хорошо оптимизированный один endpoint на все случае жизни. По крайнем мере, ни у кого это сделать пока не получилось. Поэтому если я хочу, чтобы мои сервера были безопасными и быстрыми, я готов делать отдельное представление для наборов данных. Кроме того, это еще и довольно дешево. Если внутренние механизмы доступа и работы с данными реализованы грамотно, то создание такого представления занимает отсилы 1-2 дня, потому что это лишь композиция существующего функционала.
Опять же речь не о RPC, где могут существовать методы типа getStatsByCities, речь о том что в архитектуре REST ресурс !== таблица БД, а полностью универсальный сервер можно написать только, если принять это равенством. Самое забавное, что даже в этом случае написать решение на все случае жизни все равно не выйдет.
p/s Еще раз уточню, что исключение могут составлять некие публичные апи, у которых цель работы — обслуживание неопределенного кол-ва клиентов, реализующих неопределенное кол-во задач. Тут наверное, разработчикам просто приходится искать максимально универсальные решения, потому что это часть задачи.
Это не идеология, это голый прагматизм: делать новый ендпоинт или расширять имеющийся только когда фронту нужны новые данные, а не просто новое сочетание тех, которые и так ему уже доступны, пускай и немного в другом виде. При условии, конечно, что реализация универсального (в рамках архитектурной модели) API проще чем реализация 100500 отдельных ендпоинтов на каждый каприз пользователя.
Полностью универсальный сервер точно не получится сделать приняв ресурс === таблица БД, потому что табличные БД и вообще БД имеют свои ограничения.
При условии, конечно, что реализация универсального (в рамках архитектурной модели) API проще чем реализация 100500 отдельных ендпоинтов на каждый каприз пользователя.
Ключевой момент. В том то и дело, что не проще. А уж насколько он не проще в поддержке, это и представить сложно.
Полностью универсальный сервер точно не получится сделать приняв ресурс === таблица БД, потому что табличные БД и вообще БД имеют свои ограничения.
Только так и получится сделать, потому что вы фактически вынуждены маппировать апи на sql (или иной язык запросов), то есть придумываете свой язык запросов поверх http. Что и сделали GQL. Если же вы не примете что ресурс === таблица БД, то в какой-то момент необходимый клиенту запрос будет просто невозможно осуществить с помощью вашего универсального endpoint.
Выставляем наружу не реляционную структуру базы, а объектную (предположим что ООП) модель, которая где-то под капотом маппится на базу посредством ORM.
Если вас это устраивает, то можете попытать счастье в попытке сделать апи для всех случаев жизни. Из моего опыта — это не возможно.
Этот тред прекращаю, потому что он ушел вообще не туда. Хотите создавать унифицированные апи под любые цели — флаг в руки и удачи. Она понадобиться.
Лично я за максимальный контроль со стороны бекенда и за то, чтобы ресурс представлял данные так, как их «видит» клиент, а не БД.
Does JSON:API take any position on URI structure, on rules for custom endpoints, which do not fit the paradigm of GET/POST/PATCH/DELETE on the resource URI, etc.?
JSON:API has no requirements about URI structure, implementations are free to use whatever form they wish.
jsonapi.org/faq/#position-uri-structure-custom-endpoints
JSON API — это лишь схема описания ресурса и подобного вопроса она конечно же не рассматривает, то, что она не накладывает никаких ограничений на endpoint логично, но ведь концепции REST, описанные в статье — накладывают, ибо путь /articles/1/arhive уже попадает под «Здесь все неправильно относительно всех принципов REST». Ни в одной статье, попадавшейся мне на глаза, об этом не было ни слова, к сожалению.
Могу сказать только, что в репозитории спецификации это обсуждалось вот тут. И продолжает обсуждаться в связанных issues (https://github.com/json-api/json-api/issues/745).
POST метод как триггер какой-то операции, особенной асинхронной вполне допустим.
В примере по ссылке предлагается указывать дополнительные действия в объекте «links». Думаю, идеальный REST такого все-таки не прощает и по канонам требовался бы специальный тип запроса на каждое действие, но ничего лучше, судя по всему, нет и не будет.
- Есть список каких-то сущностей GET /api/entity
- Список этот можно фильтровать, например по году, или другой сущность author
- Нужно в API ответе передать разрешенные значения для фильтрация год (2020, 2015, 2012), author (другие сущности)
Единственный вариант, который я вижу это самому как-то руками засунуть данные в meta, но выглядит не красиво :(
JSON API – работаем по спецификации