Как REST API должен обрабатывать PUT-запросы к частично модифицируемым ресурсам?

46

Предположим, что REST API в ответ на HTTP- GETзапрос возвращает некоторые дополнительные данные в подобъекте 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 OK»).
Мацей Пехотка
2
@MaciejPiechotka проблема в том, что вы не можете использовать ту же модель на путе, что и на вставке или получении и т. Д., Я бы предпочел, чтобы использовалась та же модель и были бы простые правила авторизации полей, так что если они вводят значение для поле, которое они не должны менять, они возвращают 403 Запрещено, и, если позже будет настроена авторизация, чтобы разрешить его, они получат 401 Несанкционированный, если они не авторизованы
Джимми Хоффа
@JimmyHoffa: Под моделью вы подразумеваете формат данных (так как может быть возможность повторно использовать модель в среде MVC Rest в зависимости от выбора, если она используется - OP не упомянул)? Я бы пошел с возможностью обнаружения, если бы я не был ограничен рамками, а ранняя ошибка немного более обнаружима / проста для реализации, чем проверка на изменение (хорошо - я не должен касаться поля XYZ). В любом случае выбрасывание - это хуже всего.
Мацей Печотка

Ответы:

46

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

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

REST и сеть в целом тесно связаны с принципом надежности : «Будьте консервативны в том, что вы делаете [отправляйте], будьте либеральными в том, что вы принимаете». Если вы философски согласны с этим, то решение очевидно: игнорируйте любые неверные данные в PUTзапросах. Это относится как к неизменным данным, как в вашем примере, так и к действительной бессмыслице, например, к неизвестным полям.

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

Хорошо, если вы выберете эту опцию, то отправите обратно 200 (ОК) с действительным обновленным объектом в ответе, чтобы клиенты могли четко видеть, что поля только для чтения не были обновлены.

Есть, конечно, некоторые люди, которые думают иначе - попытка обновить доступную только для чтения часть ресурса должна быть ошибкой. Для этого есть некоторые основания, прежде всего на том основании, что вы обязательно вернете ошибку, если весь ресурс доступен только для чтения и пользователь попытается его обновить. Это определенно идет вразрез с принципом надежности, но вы можете считать его более «самодокументированным» для пользователей вашего API.

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

Мне действительно не нравится проверка на равенство с 409, потому что она неизменно требует, чтобы клиент сделал a GET, чтобы получить текущие данные, прежде чем он сможет сделать a PUT. Это просто нехорошо и, вероятно, приведет к снижению производительности, для кого-то, где-нибудь. Мне также действительно не нравится 403 (Запрещено) для этого, поскольку это означает, что защищен весь ресурс, а не только его часть. Поэтому я считаю, что если вам абсолютно необходимо выполнить проверку вместо того, чтобы следовать принципу надежности, проверьте все ваши запросы и верните 400 для всех, которые имеют дополнительные или недоступные для записи поля.

Убедитесь, что ваш 400/409 / что-либо включает в себя информацию о том, что конкретная проблема и как ее исправить.

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

Aaronaught
источник
1
Хороший ответ и проголосовал. Однако я не уверен, что согласен с этим: «Если вы когда-нибудь решите удалить существующее поле или сделать его доступным только для чтения, это будет обратно совместимым изменением, если сервер просто игнорирует эти поля, и старые клиенты все еще будут работать. " Если клиент использует это удаленное / только для чтения поле, разве это не повлияет на общее поведение приложения? В случае удаления полей, я бы сказал, что, вероятно, лучше явно генерировать ошибку, а не игнорировать данные; в противном случае клиент не знает, что его ранее работающее обновление теперь не работает.
Риного
Этот ответ неверен. по двум причинам из RFC2616: 1. (раздел 9.1.2) PUT должен быть независимым. Положите много раз, и это даст тот же результат, что и положите только один раз. 2. При получении ресурса должен возвращаться объект пут, если не было сделано никаких других запросов на изменение ресурса
августа,
1
Что делать, если вы выполняете проверку на равенство только в том случае, если неизменное значение было отправлено в запросе. Я думаю, что это дает вам лучшее из двух миров; вы не заставляете клиентов делать GET, и вы все равно уведомляете их о том, что что-то не так, если они отправили недопустимое значение для неизменяемого.
Ахмад
Спасибо, глубокое сравнение, которое вы провели в последних параграфах, исходя из опыта, это именно то, что я искал.
dhill
9

То же самое

Следуя RFC, PUT должен будет доставить полный объект к ресурсу. Основная причина этого заключается в том, что PUT должен быть идемпотентом. Это означает, что запрос, который повторяется, должен приводить к тому же результату на сервере.

Если вы разрешаете частичное обновление, оно не может быть более эффективным. Если у вас есть два клиента. Клиент A и B, затем может развиваться следующий сценарий:

Клиент А получает картинку из ресурсов изображений. Это содержит описание изображения, которое все еще действует. Клиент B помещает новое изображение и обновляет описание соответствующим образом. Картина изменилась. Клиент А видит, ему не нужно менять описание, потому что это так, как он хочет, и ставить только изображение.

Это приведет к несогласованности, к изображению прикреплены неверные метаданные!

Еще более раздражает то, что любой посредник может повторить запрос. В случае, если он решит, что PUT не удалось.

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

Другие опции

К счастью, есть еще один вариант, это патч. 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, удалите fielda). Я думаю, что вы хотите сообщить об этом клиенту, чтобы ограничить время отладки на его / ее стороне.

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

Плохо

Если вы решили пойти с опцией PUT, что несколько рискованно. Тогда вам следует хотя бы не сбрасывать ошибку. У пользователя есть определенное ожидание (данные будут обновлены), и если вы нарушите это, вы дадите некоторое время разработчикам.

Вы можете выбрать для возврата: 409 Конфликт или 403 Запрещено. Это зависит от того, как вы смотрите на процесс обновления. Если вы видите это как набор правил (ориентированных на систему), тогда конфликт будет лучше. Как-то так, эти поля не обновляются. (Противоречит правилам). Если вы видите это как проблему авторизации (ориентированную на пользователя), вы должны вернуть запрещенный. С: вы не авторизованы для изменения этих полей.

Вы все еще должны заставить пользователей отправлять все изменяемые поля.

Разумный вариант для обеспечения этого - установить для него подресурс, который предлагает только изменяемые данные.

Личное мнение

Лично я бы пошел (если вам не нужно работать с браузерами) для простой модели PATCH, а затем позже расширил бы ее с помощью процессора исправлений JSON. Это может быть сделано путем дифференцирования по mimetypes: mime-тип json patch:

применение / JSON-патч

И JSON: приложение / JSON-патч

позволяет легко реализовать его в два этапа.

Эдгар Клеркс
источник
3
Ваш пример идемпотентности не имеет смысла. Либо вы меняете описание, либо нет. В любом случае, вы получите один и тот же результат, каждый раз.
Роберт Харви
1
Вы правы, я думаю, что пора идти спать. Я не могу редактировать это. Это скорее пример рациональной отправки всех данных в запросе PUT. Спасибо за указатель.
Эдгар Клеркс
Я знаю, что это было 3 года назад ... но знаете ли вы, где в RFC я могу найти больше информации о "PUT должен будет доставить полный объект на ресурс". Я видел это упомянутое в другом месте, но хотел бы увидеть, как это определено в спецификации.
CSharper
Я думаю, что нашел это? tools.ietf.org/html/rfc5789#page-3
CSharper