Прежде всего, некоторые определения:
PUT определен в разделе 9.6 RFC 2616 :
Метод PUT запрашивает, чтобы вложенный объект был сохранен под предоставленным Request-URI. Если Request-URI ссылается на уже существующий ресурс, вложенный объект СЛЕДУЕТ рассматривать как модифицированную версию, находящуюся на исходном сервере . Если Request-URI не указывает на существующий ресурс, и этот URI может быть определен как новый ресурс запрашивающим пользовательским агентом, сервер происхождения может создать ресурс с этим URI.
PATCH определен в RFC 5789 :
Метод PATCH запрашивает, чтобы набор изменений, описанный в объекте запроса, был применен к ресурсу, идентифицированному Request-URI.
Также согласно RFC 2616, раздел 9.1.2. PUT является идемпотентным, а PATCH - нет.
Теперь давайте посмотрим на реальный пример. Когда я выполняю POST для /users
данных {username: 'skwee357', email: 'skwee357@domain.com'}
и сервер способен создать ресурс, он отвечает 201 и размещает ресурс (предположим /users/1
), и любой следующий вызов GET /users/1
вернется {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}
.
Теперь позвольте мне сказать, что я хочу изменить свою электронную почту. Модификация электронной почты считается «набором изменений», и поэтому мне следует патчить /users/1
с « патчем документа ». В моем случае это был бы документ в формате JSON: {email: 'skwee357@newdomain.com'}
. Затем сервер возвращает 200 (при условии, что разрешение в порядке). Это подводит меня к первому вопросу:
- Патч не идемпотент. Об этом говорится в RFC 2616 и RFC 5789. Однако, если я выполню один и тот же запрос PATCH (с моим новым адресом электронной почты), я получу то же состояние ресурса (с изменением моего адреса электронной почты до запрошенного значения). Почему патч не идемпотентен?
PATCH - это относительно новый глагол (RFC введен в марте 2010 года), и он решает проблему «исправления» или изменения набора полей. До появления PATCH все использовали PUT для обновления ресурсов. Но после появления PATCH я не могу понять, для чего используется PUT. И это подводит меня ко второму (и главному) вопросу:
- В чем реальная разница между PUT и PATCH? Я где-то читал, что PUT может использоваться для замены всей сущности под конкретным ресурсом, поэтому следует отправить полную сущность (вместо набора атрибутов, как с PATCH). Каково реальное практическое использование для такого случая? Когда вы хотите заменить / перезаписать объект в определенном URI ресурса, и почему такая операция не считается обновлением / исправлением объекта? Единственный практический вариант использования PUT, который я вижу, - это выдача PUT для коллекции, т.е.
/users
для замены всей коллекции. Выдача PUT для конкретной сущности не имеет смысла после появления PATCH. Я ошибаюсь?
Ответы:
ПРИМЕЧАНИЕ : Когда я впервые потратил время на чтение о REST, идемпотентность была непонятной концепцией, чтобы попытаться получить правильные результаты. Я все еще не совсем понял в своем первоначальном ответе, как показали дальнейшие комментарии (и ответ Джейсона Хетгера ). Некоторое время я сопротивлялся обновлению этого ответа, чтобы избежать эффективного плагиата Джейсона, но сейчас я его редактирую, потому что, ну, меня попросили (в комментариях).
Прочитав мой ответ, я предлагаю вам также прочитать превосходный ответ Джейсона Хетгера на этот вопрос, и я постараюсь сделать свой ответ лучше, не просто крадя у Джейсона.
Почему PUT идемпотент?
Как вы отметили в цитате RFC 2616, PUT считается идемпотентным. Когда вы кладете ресурс, в игру вступают два следующих предположения:
Вы имеете в виду сущность, а не коллекцию.
Предоставляемая вами сущность завершена ( вся сущность).
Давайте посмотрим на один из ваших примеров.
Если вы отправите этот документ
/users
, как вы предлагаете, то вы можете получить обратно такой объект, какЕсли вы хотите изменить эту сущность позже, вы выбираете между PUT и PATCH. PUT может выглядеть так:
Вы можете сделать то же самое, используя PATCH. Это может выглядеть так:
Вы сразу заметите разницу между этими двумя. PUT включал все параметры этого пользователя, но PATCH включал только тот, который был изменен (
email
).При использовании PUT предполагается, что вы отправляете полный объект, и этот полный объект заменяет любой существующий объект с этим URI. В приведенном выше примере PUT и PATCH выполняют одну и ту же цель: они оба меняют адрес электронной почты этого пользователя. Но PUT обрабатывает это, заменяя весь объект, в то время как PATCH обновляет только предоставленные поля, оставляя остальные в покое.
Поскольку запросы PUT включают всю сущность, если вы выполняете один и тот же запрос несколько раз, он всегда должен иметь одинаковый результат (отправленные вами данные теперь являются полными данными сущности). Поэтому PUT является идемпотентом.
Неправильное использование PUT
Что произойдет, если вы используете вышеуказанные данные PATCH в запросе PUT?
(Я предполагаю, что для целей этого вопроса сервер не имеет каких-либо конкретных обязательных полей и позволил бы этому случиться ... что в действительности может быть не так).
Так как мы использовали PUT, но только поставляли
email
, теперь это единственная вещь в этой сущности. Это привело к потере данных.Этот пример здесь для иллюстративных целей - никогда не делайте этого на самом деле. Этот запрос PUT технически идемпотентен, но это не значит, что это не ужасная, сломанная идея.
Как PATCH может быть идемпотентом?
В приведенном выше примере PATCH был идемпотентным. Вы внесли изменение, но если вы вносите одно и то же изменение снова и снова, оно всегда будет возвращать один и тот же результат: вы изменили адрес электронной почты на новое значение.
Мой оригинальный пример, исправленный на точность
Первоначально у меня были примеры, которые, как мне казалось, демонстрировали неидемпотентность, но они вводили в заблуждение / неверны. Я собираюсь сохранить примеры, но использую их для иллюстрации другой вещи: то, что несколько документов PATCH против одной и той же сущности, изменяя различные атрибуты, не делают PATCH неидемпотентными.
Допустим, что в свое время пользователь был добавлен. Это состояние, с которого вы начинаете.
После патча у вас есть измененный объект:
Если вы затем несколько раз примените свой PATCH, вы продолжите получать тот же результат: электронное письмо было изменено на новое значение. А входит, А выходит, поэтому это идемпотент.
Через час, после того как вы пошли приготовить кофе и сделать перерыв, кто-то еще приходит вместе со своим патчем. Кажется, Почта вносит некоторые изменения.
Так как этот патч из почтового отделения не касается электронной почты, он использует только почтовый индекс, если он применяется повторно, он также получит тот же результат: почтовому индексу присвоено новое значение. А входит, А выходит, поэтому это тоже идемпотент.
На следующий день вы решили отправить свой патч снова.
Ваш патч имеет тот же эффект, что и вчера: он установил адрес электронной почты. А вошел, А вышел, поэтому это тоже идемпотент.
Что я ошибся в своем первоначальном ответе
Я хочу провести важное различие (что-то не так в моем первоначальном ответе). Многие серверы будут отвечать на ваши запросы REST, отправляя обратно новое состояние объекта с вашими изменениями (если таковые имеются). Таким образом, когда вы получаете этот ответ обратно, он отличается от того, который вы получили вчера , потому что почтовый индекс не тот, который вы получили в прошлый раз. Однако ваш запрос касался не почтового индекса, а только электронной почты. Таким образом, ваш документ PATCH все еще идемпотентен - электронное письмо, которое вы отправили в PATCH, теперь является адресом электронной почты объекта.
Так когда же патч не идемпотентен?
Для полного рассмотрения этого вопроса я снова отсылаю вас к ответу Джейсона Хетгера . Я просто собираюсь на этом остановиться, потому что, честно говоря, не думаю, что смогу ответить на этот вопрос лучше, чем он уже сказал.
источник
GET /users/1
доGET /users/1
того, как почтовое отделение обновит почтовый индекс, а затем снова сделает тот же запрос после обновления почтового отделения, вы получите два разных ответа (разные почтовые индексы). Идет та же буква «А» (запрос GET), но вы получаете разные результаты. И все же GET все еще идемпотентен.Хотя превосходный ответ Дэна Лоу очень подробно ответил на вопрос OP о разнице между PUT и PATCH, его ответ на вопрос, почему PATCH не идемпотентен, не совсем корректен.
Чтобы показать, почему PATCH не идемпотентен, полезно начать с определения идемпотентности (из Википедии ):
На более доступном языке идемпотентный PATCH может быть определен следующим образом: После PATCHing ресурса с документом исправления все последующие вызовы PATCH к одному и тому же ресурсу с тем же документом исправления не изменят ресурс.
И наоборот, неидемпотентная операция - это операция, в которой f (f (x))! = F (x), которая для PATCH может быть записана как: после PATCHing ресурса с документом исправления, последующие вызовы PATCH обращаются к тому же ресурсу с тот же документ патча действительно изменяет ресурс.
Чтобы проиллюстрировать неидемпотентный PATCH, предположим, что существует ресурс / users, и предположим, что вызов
GET /users
возвращает список пользователей, в настоящее время:Предположим, что вместо PATCHing / users / {id}, как в примере с OP, сервер разрешает PATCHing / users. Давайте выполним этот запрос PATCH:
Наш патч-документ предписывает серверу добавить нового пользователя
newuser
в список пользователей. После вызова этого в первый раз,GET /users
вернется:Теперь, если мы выдадим тот же самый запрос PATCH, что и выше, что произойдет? (Для примера рассмотрим, что ресурс / users допускает дублирование имен пользователей.) «Op» - это «add», поэтому новый пользователь добавляется в список, а затем
GET /users
возвращается:Ресурс / users снова изменился , хотя мы выдавали одинаковый PATCH для той же конечной точки. Если наш PATCH равен f (x), f (f (x)) не совпадает с f (x), и, следовательно, этот конкретный PATCH не идемпотентен .
Хотя PATCH не гарантированно является идемпотентным, в спецификации PATCH нет ничего, что мешало бы вам выполнять все операции PATCH на вашем конкретном сервере. RFC 5789 даже ожидает преимущества от идемпотентных запросов PATCH:
В примере Дэна его операция PATCH фактически идемпотентна. В этом примере сущность / users / 1 изменилась между нашими запросами PATCH, но не из-за наших запросов PATCH; это был фактически другой патч Почтового отделения, который вызвал изменение почтового индекса. Другой PATCH Почты - это другая операция; если наш PATCH - f (x), то PATCH почтового отделения - g (x). Идемпотентность утверждает это
f(f(f(x))) = f(x)
, но не дает никаких гарантийf(g(f(x)))
.источник
/users
, это также делает PUT неидемпотентным. Все это сводится к тому, как сервер предназначен для обработки запросов./users
), любой запрос PUT должен заменить содержимое этой коллекции. Так что PUT/users
должен ожидать коллекцию пользователей и удалить всех остальных. Это идемпотент. Маловероятно, что вы сделали бы такую вещь в конечной точке / users. Но что-то похожее/users/1/emails
может быть коллекцией, и вполне может быть допустимо заменить всю коллекцию новой.op
действием, которое запускает определенную логику на стороне сервера. Для этого серверу и клиенту необходимо знать конкретные значения, передаваемыеop
полю для запуска рабочих процессов на стороне сервера. В более простых сценариях REST этот типop
функциональности является плохой практикой и, вероятно, должен обрабатываться непосредственно через HTTP-глаголы.Мне тоже было интересно об этом и я нашел несколько интересных статей. Я не могу ответить на ваш вопрос в полной мере, но это, по крайней мере, дает дополнительную информацию.
http://restful-api-design.readthedocs.org/en/latest/methods.html
Учитывая это, PUT должен отправить весь объект. Например,
Это будет эффективно обновлять электронную почту. Причина, по которой PUT может быть не слишком эффективной, заключается в том, что ваше единственное действительное изменение одного поля, включая имя пользователя, является бесполезным. Следующий пример показывает разницу.
Теперь, если PUT был разработан в соответствии со спецификацией, тогда PUT установит имя пользователя в null, и вы получите следующее.
Когда вы используете PATCH, вы обновляете только указанное вами поле, а остальные оставляете в покое, как в вашем примере.
Следующий взгляд на патч немного отличается от того, что я никогда раньше не видел.
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Вы более или менее рассматриваете PATCH как способ обновления поля. Таким образом, вместо отправки частичного объекта вы отправляете операцию. т.е. заменить электронную почту на значение.
Статья заканчивается этим.
Теперь я не знаю, согласен ли я со статьей, как отмечают многие комментаторы. Отправка по частичному представлению может легко быть описанием изменений.
Для меня я использую PATCH. По большей части я буду относиться к PUT как к PATCH, поскольку единственное реальное отличие, которое я заметил до сих пор, это то, что PUT «должен» установить отсутствующие значения на ноль. Возможно, это не самый правильный способ сделать это, но удачное кодирование идеально.
источник
Разница между PUT и PATCH заключается в том, что:
Для PATCH требуется некоторый «язык патчей», чтобы сообщить серверу, как модифицировать ресурс. Вызывающий и сервер должны определить некоторые «операции», такие как «добавить», «заменить», «удалить». Например:
Вместо того, чтобы использовать явные поля «операции», язык патчей может сделать его неявным, определив такие соглашения, как:
в теле запроса PATCH:
С вышеупомянутым соглашением PATCH в примере может принять следующую форму:
Который выглядит более лаконичным и удобным. Но пользователи должны знать о базовом соглашении.
С операциями, которые я упомянул выше, PATCH все еще идемпотентен. Но если вы определите такие операции, как: "increment" или "append", вы можете легко увидеть, что это больше не будет идемпотентом.
источник
TLDR - Dumbed Down Version
PUT => Установить все новые атрибуты для существующего ресурса.
PATCH => Частично обновить существующий ресурс (требуются не все атрибуты).
источник
Позвольте мне процитировать и прокомментировать более подробно RFC 7231 раздел 4.2.2 , который уже упоминался в предыдущих комментариях:
Итак, что должно быть «таким же» после повторного запроса идемпотентного метода? Не состояние сервера и не ответ сервера, а ожидаемый эффект . В частности, метод должен быть идемпотентным «с точки зрения клиента». Теперь я думаю, что эта точка зрения показывает, что последний пример ответа Дэна Лоу , который я не хочу здесь описывать, действительно показывает, что запрос PATCH может быть неидемпотентным (более естественным образом, чем пример в Ответ Джейсона Хетгера ).
Действительно, давайте сделаем пример немного более точным, сделав одно возможное намерение для первого клиента. Допустим, этот клиент просматривает список пользователей проекта, чтобы проверить их электронные письма и почтовые индексы. Он начинает с пользователя 1, замечает, что почтовый индекс верен, а адрес электронной почты неправильный. Он решает исправить это с помощью запроса PATCH, который является полностью законным, и отправляет только
так как это единственное исправление. Теперь запрос не выполняется из-за проблем в сети, и он автоматически отправляется через пару часов. В то же время другой клиент (ошибочно) изменил почтовый индекс пользователя 1. Затем, отправка того же самого запроса PATCH во второй раз не приводит к ожидаемому эффекту клиента, поскольку в результате мы получаем неправильный почтовый индекс. Следовательно, метод не идемпотентен в смысле RFC.
Если вместо этого клиент использует запрос PUT для исправления электронной почты, отправляя на сервер все свойства пользователя 1 вместе с электронной почтой, его ожидаемый эффект будет достигнут, даже если запрос должен быть повторно отправлен позже, а пользователь 1 был изменен. тем временем --- поскольку второй запрос PUT перезапишет все изменения, начиная с первого запроса.
источник
По моему скромному мнению, идемпотентность означает:
Итак, я отправляю определение конкурирующего ресурса - итоговое состояние ресурса точно соответствует параметру PUT. Каждый раз, когда я обновляю ресурс теми же параметрами PUT - результирующее состояние точно такое же.
Я отправил только часть определения ресурса, так что может случиться так, что другие пользователи в то же время обновляют ДРУГИЕ параметры этого ресурса. Следовательно - последовательные исправления с одинаковыми параметрами и их значениями могут привести к разному состоянию ресурса. Например:
Предположим, объект определен следующим образом:
АВТОМОБИЛЬ: - цвет: черный, - тип: седан, - мест: 5
Я исправляю это:
{красный цвет'}
Полученный объект:
АВТОМОБИЛЬ: - цвет: красный, - тип: седан, - мест: 5
Затем некоторые другие пользователи исправляют этот автомобиль с помощью:
{тип: 'хэтчбек'}
Итак, результирующий объект:
АВТОМОБИЛЬ: - цвет: красный, - тип: хэтчбек, - количество мест: 5
Теперь, если я исправлю этот объект снова:
{красный цвет'}
результирующий объект:
АВТОМОБИЛЬ: - цвет: красный, - тип: хэтчбек, - количество мест: 5
Чем отличается то, что я получил ранее!
Вот почему PATCH не идемпотентен, а PUT идемпотентен.
источник
Чтобы завершить обсуждение идемпотентности, я должен отметить, что идемпотентность в контексте REST можно определить двумя способами. Давайте сначала формализуем несколько вещей:
Ресурс является функцией с его кообластью является классом строк. Другими словами, ресурс - это подмножество
String × Any
, в котором все ключи уникальны. Давайте назовем класс ресурсовRes
.REST-операция над ресурсами, это функция
f(x: Res, y: Res): Res
. Два примера операций REST:PUT(x: Res, y: Res): Res = x
, а такжеPATCH(x: Res, y: Res): Res
, который работает какPATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}
.(Это определение специально разработано, чтобы спорить о,
PUT
иPOST
, например, не имеет особого смыслаGET
иPOST
, поскольку не заботится о постоянстве).Теперь, исправляя
x: Res
(информативно говоря, используя карринг),PUT(x: Res)
иPATCH(x: Res)
получим одномерные функции типаRes → Res
.Функция
g: Res → Res
называется глобально идемпотентна , когдаg ○ g == g
, то есть для любогоy: Res
,g(g(y)) = g(y)
.Пусть
x: Res
ресурс, аk = x.keys
. Функцияg = f(x)
называется левым идемпотентом , когда для каждогоy: Res
мы имеемg(g(y))|ₖ == g(y)|ₖ
. Это в основном означает, что результат должен быть таким же, если мы посмотрим на примененные ключи.Таким образом,
PATCH(x)
не является идемпотентом в глобальном масштабе, но остается идемпотентом. И здесь важна левая идемпотентность: если мы исправляем несколько ключей ресурса, мы хотим, чтобы эти ключи были такими же, если мы исправляем его снова, и нам нет дела до остальной части ресурса.И когда RFC говорит о том, что PATCH не идемпотентен, он говорит о глобальной идемпотентности. Хорошо, что это не глобально идемпотент, иначе это была бы сломанная операция.
Теперь, ответ Джейсона Хетгера пытается продемонстрировать, что PATCH даже не остается идемпотентным, но он ломает слишком много вещей, чтобы сделать это:
t: Set<T> → Map<T, Boolean>
определенный с помощьюx in A iff t(A)(x) == True
. Используя это определение, патчирование остается идемпотентным.{id: 1, email: "me@site.com"}
должна совпадать с{email: "me@site.com"}
, в противном случае программа всегда не работает, и PATCH не может быть возможно патч). Если идентификатор сгенерирован перед проверкой на соответствие, снова программа не работает.Можно привести примеры того, что PUT неидемпотентен, если сломать половину того, что сломано в этом примере:
PUT /user/12 {email: "me@site.com"}
результаты в{email: "...", version: 1}
первый раз и{email: "...", version: 2}
во второй раз.Все приведенные выше примеры являются естественными примерами, с которыми можно столкнуться.
В заключение я хочу сказать, что PATCH не должен быть глобально идемпотентным , иначе он не даст желаемого эффекта. Вы хотите изменить адрес электронной почты своего пользователя, не касаясь остальной информации, и не хотите перезаписывать изменения другой стороны, получающей доступ к тому же ресурсу.
источник
Одна дополнительная информация, которую я только хочу добавить, состоит в том, что запрос PATCH использует меньшую полосу пропускания по сравнению с запросом PUT, поскольку отправляется только часть данных, а не весь объект. Поэтому просто используйте запрос PATCH для обновления определенных записей, таких как (1-3 записи), а запрос PUT для обновления большего объема данных. Вот и все, не думай слишком много и не беспокойся об этом слишком много.
источник