Здравствуйте, fddima, Вы писали:
F> Я в большей степени по этому и удивлён каким-то этим спорам, когда сравнивается одна фундаментальная вещь (RPC) с модно-конкретной (REST).
Можно считать, что REST — это паттерн. Внутри приложения тоже можно просто как попало non-remote методы вызывать, безо всяких паттернов.
F> PS: POST — есть Create. PUT — есть Update. Реализация идемпотентности к POST (а она нужна) — зависит полностью от тебя, что бы там умы не говорили.
В моей исходной цитате PUT предлагалось в качестве add to store. Хочешь сказать, что это не create?
Re[10]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, dimgel, Вы писали:
F>> PS: POST — есть Create. PUT — есть Update. Реализация идемпотентности к POST (а она нужна) — зависит полностью от тебя, что бы там умы не говорили. D>В моей исходной цитате PUT предлагалось в качестве add to store. Хочешь сказать, что это не create?
Думаю, что create — всё таки должен быть POST. Хотя PUT, в случае когда id (url) создаваемого ресурса заранее известен — наверное тоже может быть применён. Но я бы так не делал. Да и мне не встречались чужие сервисы которые бы так делали.
CRUD = PGPD (POST — GET — PUT — DELETE)
Re[11]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Cyberax, Вы писали: C>Ну вот нынче писали REST API. Ну и @!@$*&@# этот REST. Скажем, несколько объектов в одной транзакции поменять нормально нельзя.
Это да, транзакции требуют специального проектирования — вместо 2х PUT на объект account делается 1 PUT на объект transfer. C>В теории есть метод PATCH, который делает частичные обновления, но знаю про него только я.
А он вам часто нужен? Почему нельзя заэкспозить нужные части как отдельные адресуемые объекты? C>Любой надёжный метод делается через полную задницу, в два шага (получить тикет, потом сделать запрос).
Почему ваш любой надёжный метод не свёлся к PUT? Зачем вам получать тикет — на клиенте нет генератора GUID?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Cyberax, Вы писали:
S>>То есть вместо возврата непрозрачных идентификаторов, которые нужно как-то угадать куда вставлять, в ресте рекомендуется возвращать прямо честные URL, по которым можно сделать GET (ну, или другие глаголы). C>Ну вот нынче писали REST API. Ну и @!@$*&@# этот REST. Скажем, несколько объектов в одной транзакции поменять нормально нельзя. C>В теории есть метод PATCH, который делает частичные обновления, но знаю про него только я. C>Любой надёжный метод делается через полную задницу, в два шага (получить тикет, потом сделать запрос).
Ну так он и по любому RPC делается точно так же. Двухфазную фиксацию в СУБД придумали не зря. Построение транзакций со стороны клиента так, чтобы проверять в случае обрыва, прошла она или нет — тоже.
Нельзя обмануть фундаментальные физические законы, а несинхронность состояния клиента и сервера в процессе их обмена можно приравнять к таким законам, или напрямую вычислить из них. Можно только сделать средства контроля этого настолько занудным, насколько хватит.
The God is real, unless declared integer.
Re[11]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, fddima, Вы писали:
F> Думаю, что create — всё таки должен быть POST. Хотя PUT, в случае когда id (url) создаваемого ресурса заранее известен — наверное тоже может быть применён. Но я бы так не делал. Да и мне не встречались чужие сервисы которые бы так делали. F> CRUD = PGPD (POST — GET — PUT — DELETE)
Мы делаем. Нет никакой проблемы разрешить клиенту генерировать uuid, когда есть разделение прав и явное указание создать.
The God is real, unless declared integer.
Re[11]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, fddima, Вы писали: F> Думаю, что create — всё таки должен быть POST.
Только если нет другого выхода, или когда не страшно наплодить дубликатов. Например, через POST удобнее всего получать тикеты для последующих идемпотентных операций — неиспользованные тикеты ничего не стоят. F>Хотя PUT, в случае когда id (url) создаваемого ресурса заранее известен — наверное тоже может быть применён.
Именно. F>Но я бы так не делал.
А зря. Зачем мучиться, приделывая идемпотентность к POST, когда есть готовый понятный PUT? F>Да и мне не встречались чужие сервисы которые бы так делали.
Полно таких сервисов. Вот первое, что попалось: http://msdn.microsoft.com/en-us/library/windowsazure/dd179451.aspx F> CRUD = PGPD (POST — GET — PUT — DELETE)
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Sinclair, Вы писали:
F>>Хотя PUT, в случае когда id (url) создаваемого ресурса заранее известен — наверное тоже может быть применён. S>Именно.
Эм... А часто ли он известен? Тут как бы по ощущениям подразумевается, что в базе PK — GUID-ы, генерируемые как раз как номера тикетов, так?
Re[13]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
D>Здравствуйте, Sinclair, Вы писали:
F>>>Хотя PUT, в случае когда id (url) создаваемого ресурса заранее известен — наверное тоже может быть применён. S>>Именно.
D>Эм... А часто ли он известен? Тут как бы по ощущениям подразумевается, что в базе PK — GUID-ы, генерируемые как раз как номера тикетов, так?
Хотя GUID-ы клиент и сам генерить может. А если подсунет левый и что-нибудь из доступных ему на запись данных сломает, тут уж звиняйте, ССЗБ.
Re[6]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, dimgel, Вы писали: D>А почему добавление в store — PUT, а в collection — POST? Причём изменение ресурса, в т.ч. элемента коллекции — тоже PUT, то нафига тут POST вообще? Если дело в том, что PUT идемпотентный, то может у меня store допускает задвоения экземпляров?
Это трудности перевода. PUT — это не "добавление" в store, это просто запись в хранилище. В то, которое идентифицировано URL, переданным в PUT.
Например,
PUT http://dormitoryservice.nsu.ru/dorm6/314/1
...
{
Name='Vasily'
}
— на первой койке в комнате 314 теперь живёт Василий.
POST, если речь о добавлении чего-то — это "вставь куда-нибудь сюда":
PUT http://dormitoryservice.nsu.ru/dorm6/
...
{
Name='Vasily'
}
Василий будет прописан куда-то на свободное место. Надо понимать, что отсутствие идемпотентности означает, что повторный вызов приведёт к тому, что на Василия будет записано две койки (если не принимать специальных мер). Это радикально отличается от семантики PUT, и даёт нам ключ к пониманию применимости: если мы знаем адрес нового объекта, то нам достаточно PUT. Если адрес должен придумать сервис, то придётся перейти на POST. При этом сразу надо думать о том, как избежать дубликатов в случае сбоев. Обычно это достигается соглашениями типа Primary Key — т.е. добавляем в данные номер паспорта, и в случае повторов отправляем код 4xx, предоставляя клиенту разбираться с тем, почему так получилось. Это несколько усложняет клиента — в случае идемпотентной операции признак успеха — код 2хх, а 4хх означает ошибку. Теперь нам надо внимательно смотреть на код ошибки и разбираться, то ли мы чего-то накосячили и Василий остался без койки, то ли всё уже хорошо, просто в прошлый раз мы этого не поняли.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[13]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, dimgel, Вы писали:
D>Эм... А часто ли он известен? Тут как бы по ощущениям подразумевается, что в базе PK — GUID-ы, генерируемые как раз как номера тикетов, так?
Он известен ровно тогда, когда это предусмотрел разработчик протокола
То есть — одно из трёх:
— естественные ключи (рейс-дата-номер места)
— распределённо-генерируемые искусственные ключи (читай — GUID)
— централизованно-генерируемые искусственные ключи (читай — тикеты).
Случаев, когда адрес нового объекта ну совсем-совсем неизвестен, нужно избегать, т.к. они приносят слишком много геморроя.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, dimgel, Вы писали: D>Хотя GUID-ы клиент и сам генерить может. А если подсунет левый и что-нибудь из доступных ему на запись данных сломает, тут уж звиняйте, ССЗБ.
Да. Основное преимущество GUID-ов — в том, что их можно генерировать полностью локально. "Подсовывание левого" является неотъемлемым правом клиента. Если он решил всю свою коллекцию порнухи сохранить под одним и тем же гуидом, то и флаг ему в руки — как мы отличим его от легитимного "ой, нет, я лучше сюда другой фильм закачаю"?
А с правами доступа разобраться достаточно легко. Если для конкретного клиента можно выделить песочницу — так и делаем, конфликты игнорируем. Т.е. я пишу в .../sinclair/001/ одно, вы — в .../dimgel/001/ другое, и мы друг другу никак не мешаем. Если я могу дать права dimgel на запись в /sinclair, то уже нужно тщательнее следить за руками. GUID хорош тем, что случайно получить такой же, как у соседа, невозможно. То есть вы должны сначала как-то подсмотреть один из существующих GUIDов в моей песочнице, и уже потом чего-то туда писать.
Если используются не GUID, а что-то с большей вероятностью конфликтов, или если нам важно избежать случайной перезаписи чужих изменений, то в REST есть специальный паттерн "Версионирование". Т.е. читать я могу из /sinclair/001/ (получу 301 на /sinclair/001/0011, где 0011 — номер последней версии), а вот записывать надо в /sinclair/001/0012/, где 0012 — это номер новой версии. Если такая версия уже есть, то я получаю 409 Conflict — это означает, что кто-то успел вклиниться между моими чтением и записью. Обычно это повод перечитать объект, и начать принятие решения с нуля — может быть, версия 0012 от конкурента лучше, чем то, что я хотел в неё записать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Sinclair, Вы писали:
C>>Ну вот нынче писали REST API. Ну и @!@$*&@# этот REST. Скажем, несколько объектов в одной транзакции поменять нормально нельзя. S>Это да, транзакции требуют специального проектирования — вместо 2х PUT на объект account делается 1 PUT на объект transfer.
Ага, особенно если нужно штучек так 100 объектов поменять. Кстати, для множественных операций в REST тоже ничего особого нет.
C>>В теории есть метод PATCH, который делает частичные обновления, но знаю про него только я. S>А он вам часто нужен? Почему нельзя заэкспозить нужные части как отдельные адресуемые объекты?
Ну вот оказался нужен. Но почти никакие клиентские библиотеки его не поддерживают.
C>>Любой надёжный метод делается через полную задницу, в два шага (получить тикет, потом сделать запрос). S>Почему ваш любой надёжный метод не свёлся к PUT? Зачем вам получать тикет — на клиенте нет генератора GUID?
Нельзя доверять тому, что у клиента UUID'ы будут надёжными. Так что надо или делать stateful-сессии и беспокоиться об уникальности UUID'ов только внутри них, или серверные тикеты. В принципе, получается то же самое — один лишний database write на запрос.
В общем, надёжность должна быть на уровне протокола.
Sapienti sat!
Re[13]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Cyberax, Вы писали: C>Ага, особенно если нужно штучек так 100 объектов поменять. Кстати, для множественных операций в REST тоже ничего особого нет.
Да хоть 1000 — принцип тот же.
C>Ну вот оказался нужен.
А подробнее можно? C>Нельзя доверять тому, что у клиента UUID'ы будут надёжными.
Почему?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Sinclair, Вы писали:
C>>Ага, особенно если нужно штучек так 100 объектов поменять. Кстати, для множественных операций в REST тоже ничего особого нет. S>Да хоть 1000 — принцип тот же.
Ну да, то есть никакого.
C>>Ну вот оказался нужен. S>А подробнее можно?
Оказалось нужно различить PUT с дефолтными значениями и PATCH изменившихся.
C>>Нельзя доверять тому, что у клиента UUID'ы будут надёжными. S>Почему?
Делаем такую последовательность:
Здравствуйте, Cyberax, Вы писали: S>>Да хоть 1000 — принцип тот же. C>Ну да, то есть никакого.
Что значит "никакого"? Принцип — делаем PUT в объект "Transaction".
S>>А подробнее можно? C>Оказалось нужно различить PUT с дефолтными значениями и PATCH изменившихся.
Не до конца понимаю. Это же ваш протокол, что мешало внести в него понятия (reset-to-default), (clear), (leave-as-is) в дополнение к установке конкретного значения?
S>>Почему? C>Делаем такую последовательность: C>
C>Если враг может сделать так, что для create_new_user() будет конфликт UUID'ов, то есть шанс подменить user_id на контролируемый врагом.
Продолжаю не понимать. То есть вы хотите сказать, что клиент 1 (привилегированный) делает типа assign_roles(admin_role), а клиент 2 (непривилегированный) делает reset_password(), и всё это на один и тот же user_id?
Пока что я не могу себе представить, каким образом клиент 2 может предсказать GUID, сгенерированный на клиенте 1.
Меня пугает непонятное — расскажите поподробнее. (Вопрос не праздный, если сценарий, о котором вы говорите, реализуем, то существует уязвимость, актуальная для всех нынешних и будущих приложений на стандарте APS2)
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[16]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
C>>Если враг может сделать так, что для create_new_user() будет конфликт UUID'ов, то есть шанс подменить user_id на контролируемый врагом. S>Продолжаю не понимать. То есть вы хотите сказать, что клиент 1 (привилегированный) делает типа assign_roles(admin_role), а клиент 2 (непривилегированный) делает reset_password(), и всё это на один и тот же user_id? S>Пока что я не могу себе представить, каким образом клиент 2 может предсказать GUID, сгенерированный на клиенте 1.
А зачем предсказывать?? ставишь снифер клиенту1 и видишь, что тот обращается по урлу www.example.com/api/users/F889A953B7C543AB82762A2E02E299BF вот те гуид и есть. Другое дело, что если "клиент 2 (непривилегированный)" может обресетить пароль, то это вообще какая та странная безопасность. И никакими тикитами с сервера тут не защитится.
Re[16]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Sinclair, Вы писали:
S>>>Да хоть 1000 — принцип тот же. C>>Ну да, то есть никакого. S>Что значит "никакого"? Принцип — делаем PUT в объект "Transaction".
Сотню запросов с латентностью в 200-300 миллисекунд? Ну-ну.
C>>Оказалось нужно различить PUT с дефолтными значениями и PATCH изменившихся. S>Не до конца понимаю. Это же ваш протокол, что мешало внести в него понятия (reset-to-default), (clear), (leave-as-is) в дополнение к установке конкретного значения?
Код библиотеки на JS так не умеет.
C>>Если враг может сделать так, что для create_new_user() будет конфликт UUID'ов, то есть шанс подменить user_id на контролируемый врагом. S>Продолжаю не понимать. То есть вы хотите сказать, что клиент 1 (привилегированный) делает типа assign_roles(admin_role), а клиент 2 (непривилегированный) делает reset_password(), и всё это на один и тот же user_id?
Нет. Враг угадывает какой UUID будет у запроса create_new_user() от привиллегированного пользователя и использует этот UUID при создании пользователя под своим контролем.
Затем привиллегированный пользователь делает create_new_user() с этим же UUID'ом и ему попадает уже существующий пользователь, которого создал враг, так как для сервера это кажется просто повторением запроса из-за ошибки клиента.
Ну и дальнейшая эксплуатация может быть разной.
Решения:
1) Сделать клиентский токен полностью алгоритмическим, включив туда ID пользователя и секретные данные. Так поступает, например, Amazon. Недостаток: сложность реализации, не всякий клиент может иметь доступ ко всем нужным HTTP-заголовкам.
2) Серверные тикеты с проверкой авторизации, включая возможность выдать сразу N тикетов.
Sapienti sat!
Re[17]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
А вот меня кстати ещё с книжки смущает кодировать id|login|api_key текущего юзера в URL. Если мне, к примеру, не нужно между юзерами ничего шарить, т.е. они полностью изолированы друго от друга, я хочу юзать /my/contacts вместо /users/KEY/contacts (или вместо прилагательного my принято что-то другое юзать) + HTTP authorization (пишут ещё про какой-то OAuth, но мне в него лениво и наверняка он браузерами не поддерживается).
Re[12]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали: S>> Поиск не работает. S>Зачем поиск? В этой теме не так много постов.
S>> Мне интересно как на физическом уровне. Понятно, что должно где то храниться ID запроса. И насколько это накладно для всех видов запросов. S>Нет никакого ID запроса. У каждого объекта есть URL. Операции с объектом делятся на safe, idempotent, и операцию POST. Поэтому ничего "отдельно" хранить не надо.
Ну вот я отправляю письмо. Должен ему присвоить какой URL в котом содержится ID этого письма, что бы при повторной посылке POST оно не отправлялось, а получался ответ который до меня не дошел. Так же и с GET если я запрашиваю справочник по ID то согласно idempotent мне по этому ID должен возвращаться всегда одни и теже данные. Идемпотентность
Согласно этому определению
Идемпотентная операция в информатике — действие, многократное повторение которого эквивалентно однократному.
Примером такой операции могут служить GET-запросы в протоколе HTTP. По спецификации, сервер должен возвращать одни и те же ответы на идентичные запросы (при условии, что ресурс не изменился между ними по иным причинам). Такая особенность позволяет кэшировать ответы, снижая нагрузку на сеть.
Кстати часто возникают проблемы с кэшированием, когда данные изменились, а ответ возвращается один и тот же. Тогда я понимаю что бы избавиться от идемпотентности можно добавить GUID или метку времени в строку запроса?
и солнце б утром не вставало, когда бы не было меня
Re[17]: [давненько мы за REST не срались] Paradigm mismatch is unavoidable!
Здравствуйте, Jack128, Вы писали:
J>А зачем предсказывать?? ставишь снифер клиенту1 и видишь, что тот обращается по урлу www.example.com/api/users/F889A953B7C543AB82762A2E02E299BF вот те гуид и есть. Другое дело, что если "клиент 2 (непривилегированный)" может обресетить пароль, то это вообще какая та странная безопасность. И никакими тикитами с сервера тут не защитится.
Вот именно. Случай, когда клиент 2 может сбросить пароль кому угодно — это такая дыра в безопасности, что её и обсуждать не стоит.
Обсуждаемый случай — это угон GUID. Пытаться его угнать после того, как его сгенерировал клиент 1, бесполезно — разделение прав доступа не даст нам ничего сделать. Единственная возможная дыра — это предсказать GUID и "застолбить" его, пользуясь неразличимостью действий "создать" и "изменить" в REST. Поскольку клиент 1 — привилегированный, система безопасности типа даст ему назначить админские права "чужому" аккаунту.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.