Как правильно рассматривать идемпотентность с точки зрения HTTP DELETE?

Недавно я провел много времени, читая спецификацию HTTP 1.1 и связывая ее с REST. Я обнаружил, что существует две интерпретации метода HTTP DELETE в отношении его «идемпотентности» и безопасности. Вот два лагеря:

  1. Если вы удаляете ресурс с помощью HTTP DELETE, и он завершается успешно (200 ОК), а затем вы пытаетесь удалить этот ресурс N раз, вы должны получить обратно сообщение об успешном завершении (200 ОК) для каждого из этих вызовов удаления. . В этом его «идемпотентность».

  2. Если вы удалите ресурс с помощью HTTP DELETE, и это будет выполнено успешно (200 OK), а затем вы попытаетесь удалить этот ресурс снова, вы должны получить обратно сообщение об ошибке (410 Gone), поскольку ресурс был удален.

Спецификация говорит, что DELETE, конечно, идемпотентна, но она также говорит, что последовательности идемпотентных событий могут вызывать побочные эффекты. Я действительно чувствую, что второй лагерь прав, а первый вводит в заблуждение. Какую «безопасность» мы ввели, позволив клиентам думать, что они были причиной удаления ранее удаленного ресурса?

К первому лагерю относится МНОГО людей, в том числе несколько авторов по этой теме, поэтому я хотел проверить, есть ли какая-то веская причина, кроме эмоций, которые приводят людей в первый лагерь.


person Daniel Crenna    schedule 12.04.2009    source источник
comment
С вашим № 2, я думаю, вы должны удалить недавно, недавность удаления не должна иметь ничего общего со стратегией этого ответа.   -  person Jeff Martin    schedule 13.08.2012
comment
@JeffMartin Вы правы, это было больше разговором, чем фактами. Удаленный.   -  person Daniel Crenna    schedule 15.08.2012


Ответы (3)


Идемпотентность не означает, что запрос не может иметь побочных эффектов (это то, что описывает свойство «безопасный»). Это просто означает, что многократное выполнение одного и того же запроса не приведет к различным или дополнительным побочным эффектам.

На мой взгляд, последующий запрос DELETE должен вернуть ошибку - он все еще идемпотент, потому что состояние сервера такое же, как если бы был сделан только один запрос DELETE. Затем снова возврат статуса 200 OK также должен быть в порядке - я не думаю, что идемпотентность требует возврата кода ошибки для последующих запросов DELETE - просто возврат статуса ошибки, кажется, делает мне больше смысла.

person Michael Burr    schedule 12.04.2009
comment
У меня такое же мнение, рада, что кто-то его разделяет. Наверное, мне просто нужно было это услышать. Спасибо. У меня есть довольно много книг на эту тему, и удивительное количество трактует идемпотентность так, как будто она также безопасна, а это не так. - person Daniel Crenna; 12.04.2009
comment
Как противоположная точка зрения, я чувствую, что запрос DELETE больше похож на «Убедитесь, что этот ресурс удален/удален». Существует ли он во время запроса, на самом деле не имеет значения для статуса успеха ответа. Если два разных клиента отправляют запрос DELETE одновременно, почему 1 должен получить ответ об ошибке, а другой - успех, когда на самом деле оба успешно выполнили цель. - person Jeff Martin; 13.08.2012
comment
@JeffMartin Вы могли бы пойти по этому пути, но я бы сказал, что причина, по которой второй получает ошибку, заключается в том, что первый запрос был получен первым. Это более точно. В некоторых системах имеет значение, кто первым выполнил удаление и что привело к неработоспособности из-за отсутствия ресурса. Если ваша система просто обеспечивает небытие, тогда ваша концепция работает. Не-200 - это просто не-200, это не ошибка, это результат. Некоторые фреймворки рассматривают это как исключительный случай, что может привести к мысли, что не-200 — это плохо. - person Daniel Crenna; 15.08.2012
comment
@DanielCrenna Я согласен с принципом того, что вы говорите. Я думаю, что есть некоторые культурные проблемы, связанные с этими 400 ответами, которые действительно приводят к тому, что фреймворки вынуждают исключения. Я думаю, что здесь мы идем на хороший компромисс: return-codes.aspx" rel="nofollow noreferrer">blogs.msdn.com/b/cellfish/archive/2012/04/02/ - person Jeff Martin; 17.08.2012
comment
Я больше с @JeffMartin, я не понимаю, что именно клиент должен делать с информацией о том, что элемент уже был удален до его запроса на его удаление. Это может означать либо то, что он отправлял запрос несколько раз, либо то, что какой-то другой клиент сначала удалил его. Но если клиент действительно делает что-то другое, то это означает, что состояние, переданное с сервера CHANGED с первого DELETE на второе, имеет значение ответ (возврат функции), потому что состояние на сервере явно меняется после первого запроса. - person Dandre Allison; 22.10.2012
comment
Это безумие. Если вы намерены удалить существующий объект с помощью идентификатора, то это должно быть явным сбоем, если этот ресурс не существует или уже был удален. Вызов удаления {bogus_id} НИКОГДА не должен быть успешным. Другими словами, если нет способа узнать, был ли когда-либо действительным {bogus_id}, он должен дать сбой. - person Triynko; 07.12.2017
comment
С другой стороны, идемпотентность имеет смысл ТОЛЬКО в том случае, если вы можете утверждать, что идентификатор был действительным, например, если вы на самом деле не удаляете ресурс, а помечаете его как удаленный. В этом случае конечный результат один и тот же, поэтому имеет смысл, чтобы вызов удаления возвращал успешное значение при многократном вызове для ранее существовавшего ресурса. Но, как я уже сказал, вызов удаления с недопустимым идентификатором (т. е. для ресурса, которого больше нет или никогда не существовало) не имеет смысла возвращать успех. - person Triynko; 07.12.2017

@MichaelBurr прав насчет идемпотентности и побочных эффектов.

Я считаю, что в данном запросе REST участвуют два состояния: состояние клиента и состояние сервера. REST — это передача этих состояний между сервером и клиентом, так что состояние клиента отображается в подмножество состояния сервера, другими словами, подмножество остается согласованным с сервером. Из-за этой идемпотентности должно означать, что последующие идемпотентные запросы не приведут к тому, что какое-либо состояние будет отличаться от того, которое было бы при однократном выполнении запроса. С первым DELETE вы можете себе представить, что сервер удаляет ресурс и сообщает клиенту, что он также может удалить ресурс (поскольку ресурс «больше не существует»). Теперь оба состояния должны быть идентичными предыдущему, за исключением удаленного элемента. Чтобы клиент делал что-то другое при попытке удалить элемент после того, как он уже был удален, состояние, передаваемое с сервера клиенту, должно содержать другую информацию. Сервер может действовать немного по-другому с информацией о том, что ресурс уже был удален, но как только он отвечает чем-то другим, идемпотентность методов существенно нарушается.

Для идемпотентной функции:

delete(client_state) -> client_state - {item}
delete(delete(client_state)) -> client_state - {item}
delete(client_state) = delete(delete(client_state))

Лучший способ гарантировать эту идемпотентность — если ответ сервера идентичен, что означает, что единственный способ для состояния клиента нарушить идемпотентность — это неопределенность или побочные эффекты в обработке клиентом ответа (что, вероятно, указывает на к некорректной реализации обработки ответа).

Если между клиентом и сервером достигнуто соглашение о том, что коды состояния существуют вне представления передаваемого состояния (REST), то можно сообщить клиенту, что элемент «больше не существует» (как это было бы в первом запросе) с дополнительным комментарием о том, что ранее он был удален. Что клиент делает с этой информацией, неясно, но это не должно влиять на результирующее состояние клиента. Но тогда код состояния нельзя использовать для сообщения о состоянии, или, скорее, если он также сообщает о состоянии в других ситуациях (например, «у вас нет разрешения на удаление этого элемента» или «элемент не был удален»), тогда есть некоторая введенная двусмысленность или путаница. Таким образом, вам, по крайней мере, нужна довольно веская причина для внесения большей путаницы в сообщение, если вы хотите сказать, что DELETE является идемпотентным, и ответ сервера по-прежнему зависит от предыдущих запросов DELETE, которые идентичны.

HTTP-запросы включают методы удаления, поэтому функция может напоминать

delete(client_state) = send_delete(client_state) -> receive_delete(client_state) 
                                                 -> respond_to_delete(informative_state) 
                                                 -> handle_response(informative_state) 
                                                 -> client_state - {item} 
person Dandre Allison    schedule 22.10.2012
comment
Это интересный аргумент. Я до сих пор не понимаю, как OK решает эту несогласованность или вынужденную двусмысленность. Я согласен с тем, что если вы предоставите идентичный ответ, вам будет лучше, но я не вижу, чтобы мы нарушили какие-либо переходы между состояниями, если мы решим устранить неоднозначность, в первую очередь, что этот ресурс никогда не существовал, поэтому вы не можете его удалить (404 ) по сравнению с этим ресурсом, который раньше был здесь, но его больше нет, поэтому вам, вероятно, следует очистить кеш (406)... - person Daniel Crenna; 28.10.2012
comment
... Вы получите идентичные ответы от сервера в каждой ситуации, когда это верно для определенного ресурса в течение его срока службы. Запрашивая идентичность через OK, вы просите сервер скрыть подробности о том, что произошло до/после ответа клиента, что может быть не очень хорошим дизайном в зависимости от API. Я буду разжевывать это дальше, но мне все еще нравится DELETE 1 -> OK, DELETE 2..n - GONE для ресурса, который когда-либо был создан, и DELETE 1..n 404 NOT FOUND для всего остального. - person Daniel Crenna; 28.10.2012
comment
@DanielCrenna, но попытка удалить что-то означает, что вы думаете, что это существует (т.е. клиент предполагает, что это должно быть на сервере). Для ресурсов, которые никогда не создавались, 404 является более подходящим кодом для операции GET. - person tenkod; 05.01.2013

Википедия определяет Idempotence как операцию, которая:

может применяться несколько раз без изменения результата после первоначального применения.

Обратите внимание, что они говорят о result операции. Для меня это включает в себя и состояние сервера, и код ответа.

Спецификация HTTP немного расплывчата в этом вопросе. Он определяет, что Методы HTTP являются идемпотентными:

если предполагаемый эффект нескольких идентичных запросов такой же, как и для одного запроса.

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

Заключительный момент: Идемпотентность определяется с точки зрения одного клиента. Как только вы начнете вводить параллельные запросы от других клиентов, все ставки сняты. Вы должны использовать заголовки условного обновления (такие как If-Match-ETag) для решения таких случаев.

Повторяю: вы должны возвращать один и тот же код возврата, независимо от того, был ли ресурс только что удален, был удален по предыдущему запросу или вообще никогда не существовал.

person Gili    schedule 15.10.2013
comment
Нет, этот эффект не предназначен для покрытия ответа. Вы предлагаете, чтобы DELETE либо всегда проходил (200), либо терпел неудачу (404 или 410), независимо от состояния сервера. Это не имеет никакого смысла и, кроме того, совершенно не помогает клиенту. - person Julian Reschke; 15.10.2013
comment
@JulianReschke, если вы читаете инструменты. ietf.org/html/ в спецификации перечислены все законные ответы на DELETE. 404 и 410 к ним не относятся. Таким образом, пожалуйста, удалите отрицательный голос. - person Gili; 15.10.2013
comment
нет, в спецификации не указаны все допустимые ответы на DELETE. Просто приводит примеры. - person Julian Reschke; 15.10.2013