Як REST API повинен обробляти запити PUT до частково модифікованих ресурсів?


46

Припустимо, API REST у відповідь на GETзапит HTTP повертає деякі додаткові дані в суб-об'єкт owner:

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

Зрозуміло, що ми не хочемо, щоб хтось мав можливість PUTпідтримати

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

і мати це домогтися успіху. Дійсно, ми, мабуть, не збираємось навіть реалізувати спосіб, щоб це навіть було потенційно успішним у цьому випадку.

Але це питання стосується не лише суб-об'єктів: що взагалі слід робити з даними, які не повинні бути змінені у запиті PUT?

Чи потрібно вимагати відсутності в запиті PUT?

Чи слід його мовчки відкидати?

Чи слід це перевіряти, і якщо воно відрізняється від старого значення цього атрибута, повертати код відповіді про помилку HTTP?

Або ми повинні використовувати патчі RFC 6902 JSON замість того, щоб надсилати весь JSON?


2
Все це спрацювало б. Я думаю, це залежить від ваших вимог.
Роберт Харві

Я б сказав, що принцип найменшого сюрпризу вказував би на те, що у запиті PUT він повинен бути відсутнім. Якщо це неможливо, перевірте, а якщо це інше, і поверніться з кодом помилки. Мовчазне відкидання є найгіршим (відправлення користувача очікує, що воно зміниться, і ви скажете їм "200 ОК").
Maciej Piechotka

2
@MaciejPiechotka проблема в тому, що ти не отримуєш використовувати таку ж модель на мапі, як на вкладиші або get etc, я б вважав за краще ту ж модель звикнути, і там є прості правила авторизації поля, тому якщо вони вводять значення для поле вони не повинні змінювати, вони отримують назад 403 Заборонено, і якщо пізніше буде встановлено авторизацію, щоб дозволити це, вони отримають 401 Несанкціонований, якщо вони не мають права
Джиммі Хоффа

@JimmyHoffa: Під моделлю ви маєте на увазі формат даних (як це можливо, можна повторно використовувати модель в рамках MVC Rest в залежності від її вибору, якщо така використовується - OP не згадував жодної)? Я б пішов з виявленням, якби мене не обмежували рамки, і рання помилка виявляється дещо більш відкритою / простою у здійсненні, а потім перевірка на зміни (добре - я не повинен торкатися поля XYZ). У будь-якому випадку відкидання є найгіршим.
Maciej Piechotka

Відповіді:


46

Ні в специфікаціях W3C, ні в неофіційних правилах REST немає жодного правила, яке говорить про те, що PUTнеобхідно використовувати ту саму схему / модель, що і відповідна GET.

Приємно, якщо вони схожі , але незвично PUTробити речі дещо інакше. Наприклад, я бачив безліч API, які включають якийсь ідентифікатор у вміст, повернутий GETдля зручності. Але при PUTзначенні a цей ідентифікатор визначається виключно URI і не має значення у змісті. Будь-який ідентифікатор, знайдений в тілі, буде мовчки ігноруватися.

REST та Інтернет взагалі сильно пов'язані з Принципом надійності : "Будьте консервативні у тому, що ви робите [надсилаєте], будьте ліберальні у тому, що приймаєте". Якщо ви погоджуєтесь з цим по-філософськи, то рішення очевидно: ігноруйте недійсні дані в PUTзапитах. Це стосується як незмінних даних, як у вашому прикладі, так і фактичних дурниць, наприклад, невідомих полів.

PATCHпотенційно інший варіант, але його не слід реалізовувати, PATCHякщо ви насправді не підтримуєте часткові оновлення. PATCHозначає лише оновлення конкретних атрибутів, які я включаю до вмісту ; це не означає замінити всю сутність, але виключити деякі конкретні поля . Те, про що ви насправді говорите, насправді не є частковим оновленням, це повне оновлення, idempotent і все, це лише та частина ресурсу, яка доступна лише для читання.

Приємно зробити, якщо ви виберете цей варіант, було б відправити назад у відповідь 200 (ОК) з фактично оновленим об'єктом , щоб клієнти могли чітко бачити, що поля лише для читання не були оновлені.

Звичайно, є деякі люди, які думають інакше - що при спробі оновити частину ресурсу, доступного лише для читання, має бути помилкою. Для цього є певне обґрунтування, насамперед виходячи з того, що ви обов'язково повернете помилку, якби весь ресурс був лише для читання, а користувач намагався його оновити. Це, безумовно, суперечить принципу надійності, але ви можете вважати, що це більше "самодокументування" для користувачів вашого API.

Для цього є дві конвенції, обидві відповідають вашим оригінальним ідеям, але я розширю їх. Перший - забороняти появі полів для читання у вмісті та повертати HTTP 400 (поганий запит), якщо вони є. API такого типу також повинні повертати HTTP 400, якщо є інші нерозпізнані / непридатні поля. Друга - вимагати, щоб поля для читання були ідентичними поточному вмісту, і повернути 409 (конфлікт), якщо значення не збігаються.

Мені дуже не подобається перевірка рівності з 409, оскільки це незмінно вимагає від клієнта зробити а GET, щоб отримати поточні дані, перш ніж мати можливість зробити це PUT. Це просто не приємно і, мабуть, десь десь призведе до низької продуктивності. Я також дійсно не як 403 (Forbidden) для цього , оскільки це означає , що весь ресурс захищений, а не тільки його частину. Отже, на мою думку, якщо ви абсолютно зобов’язані перевірити, замість того, щоб дотримуватися принципу надійності, підтвердіть всі свої запити та поверніть 400 для будь-яких, які мають додаткові або незаписані поля.

Переконайтеся, що ваш 400/409 / що містить інформацію про конкретну проблему та як її усунути.

Обидва ці підходи справедливі, але я віддаю перевагу колишньому у відповідності з принципом надійності. Якщо ви коли-небудь відчували роботу з великим API REST, ви оціните цінність зворотної сумісності. Якщо ви коли-небудь вирішите видалити існуюче поле або зробити його лише для читання, це зміни, сумісні з зворотним ходом, якщо сервер просто ігнорує ці поля, а старі клієнти все ще працюватимуть. Однак якщо ви будете проводити сувору перевірку вмісту, він вже не сумісний із зворотним процесом, і старі клієнти перестануть працювати. Перший, як правило, означає менше роботи як для технічного обслуговування API, так і для його клієнтів.


1
Хороша відповідь і підтримка. Однак я не впевнений, що я згоден з цим: "Якщо ви коли-небудь вирішите видалити існуюче поле або зробити його лише для читання, це зміна, сумісна назад, якщо сервер просто ігнорує ці поля, а старі клієнти все ще працюватимуть. " Якби клієнт покладався на це видалене / нещодавно прочитане поле, чи не все-таки це вплине на загальну поведінку програми? У випадку видалення полів я б стверджував, що, ймовірно, краще явно генерувати помилку, а не ігнорувати дані; інакше клієнт не має уявлення про те, що його раніше працююче оновлення зараз не працює.
rinogo

Ця відповідь неправильна. з 2 причин від RFC2616: 1. (розділ 9.1.2) PUT повинен бути незалежним. Покладіть багато разів, і це дасть той самий результат, що і один раз. 2. Доступ до ресурсу повинен повернути суб'єкту господарювання, якщо не було подано інших запитів про зміну ресурсу
brunoais

1
Що робити, якщо зробити перевірку рівності лише в тому випадку, якщо незмінне значення було надіслано в запиті. Я думаю, що це дає тобі найкраще з двох світів; ви не змушуєте клієнтів робити GET, і ви все одно повідомляєте їх, що щось не так, якщо вони надіслали недійсне значення для непорушного.
Ахмад Абдельгані

Дякую, саме глибоке порівняння, яке ви зробили в останніх параграфах, випливало з досвіду - саме те, що я шукав.
холод

9

Потенція ідем

Слідуючи RFC, PUT повинен був доставити повний об'єкт на ресурс. Основна причина цього полягає в тому, що PUT повинен бути безсильним. Це означає, що запит, який повторюється, повинен оцінюватися з однаковим результатом на сервері.

Якщо ви дозволите часткове оновлення, воно вже не може бути потенційним. Якщо у вас є два клієнта. Клієнт А і В, тоді може розвиватися такий сценарій:

Клієнт А отримує зображення із зображень ресурсів. Тут міститься опис зображення, який все ще діє. Клієнт B ставить нове зображення і відповідно оновить опис. Малюнок змінився. Клієнт А бачить, йому не потрібно змінювати опис, тому що це так, як він хоче, і розміщувати лише зображення.

Це призведе до невідповідності, до зображення додані неправильні метадані!

Ще більше дратує те, що будь-який посередник може повторити запит. У випадку, якщо воно вирішить якось, PUT провалився.

Значення PUT неможливо змінити (хоча ви можете неправильно використовувати його).

Інші варіанти

На щастя, є ще один варіант, це PATCH. PATCH - метод, який дозволяє частково оновити структуру. Можна просто надіслати часткову структуру. Для простих програм це добре. Цей метод не гарантовано є дієвим. Клієнт повинен надіслати запит у такій формі:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

І сервер може відповісти назад 204 (Без вмісту), щоб позначити успіх. При помилці ви не можете оновити частину структури. Метод PATCH - атомний.

Недоліком цього методу є те, що не всі браузери підтримують це, але це найбільш природний варіант у REST-сервісі.

Приклад запиту на виправлення: http://tools.ietf.org/html/rfc5789#section-2.1

Патч Json

Варіант json здається досить вичерпним та цікавим варіантом. Але це може бути важко реалізувати для третіх сторін. Ви повинні вирішити, чи може ваша база користувачів впоратися з цим.

Це також дещо перекручено, тому що вам потрібно створити невеликий інтерпретатор, який перетворює команди в часткову структуру, яку ви збираєтеся використовувати для оновлення своєї моделі. Цей інтерпретатор також повинен перевірити, чи надані команди мають сенс. Деякі команди скасовують одна одну. (написати fielda, delete fielda). Я думаю, ви хочете повідомити про це клієнту, щоб обмежити час налагодження на його стороні.

Але якщо у вас є час, це дійсно елегантне рішення. Ви все одно повинні підтвердити поля. Ви можете комбінувати це з методом PATCH, щоб залишитися у моделі REST. Але я думаю, що POST був би прийнятним тут.

Йде погано

Якщо ви вирішили перейти з опцією PUT, це дещо ризиковано. Тоді вам слід хоча б не відкидати помилку. Користувач має певну тривалість (дані будуть оновлюватися), і якщо ви порушите це, ви збираєтесь дати деяким розробникам не гарний час.

Ви можете обрати для позначення: 409 Конфлікт або 403 Заборонено. Це залежить від того, як ви дивитесь на процес оновлення. Якщо ви бачите це як набір правил (орієнтований на систему), конфлікт буде приємнішим. Щось подібне, ці поля не можна оновити. (У суперечності з правилами). Якщо ви бачите це як проблему з авторизацією (орієнтовану на користувача), вам слід повернути заборонено. З: Ви не маєте права змінювати ці поля.

Ви все одно повинні змусити користувачів надсилати всі модифіковані поля.

Доцільним варіантом застосувати це є встановлення його на підресурсі, який пропонує лише зміни даних.

Особиста думка

Особисто я б пішов (якщо вам не потрібно працювати з браузерами) для простої моделі PATCH, а потім пізніше розширити її за допомогою процесора патча JSON. Це можна зробити за допомогою диференціації за міметиками: тип mime патчу json:

додаток / json-patch

І json: application / json-patch

полегшує його реалізацію в два етапи.


3
Ваш приклад Idempotency не має сенсу. Або ви змінюєте опис, або ні. У будь-якому випадку, ви будете отримувати однаковий результат кожного разу.
Роберт Харві

1
Ви маєте рацію, я думаю, що пора лягти спати. Я не можу його редагувати. Це більше приклад про раціональність надсилання всіх даних у запиті PUT. Дякуємо за вказівник.
Едгар Клеркс

Я знаю, що це було 3 роки тому ... але чи знаєте ви, де в RFC я можу знайти більше інформації про "PUT повинен був би доставити повний об'єкт на ресурс". Я бачив це згадане в іншому місці, але хотів би побачити, як це визначено в специфікації.
CSharper

Я думаю, що знайшов? tools.ietf.org/html/rfc5789#page-3
CSharper
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.