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

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

  • Если вы удалите ресурс с помощью HTTP DELETE, и он преуспеет (200 OK), а затем вы попытаетесь удалить этот ресурс N раз, вы должны вернуть сообщение об успешном завершении (200 OK) для каждого и каждый из этих вызовов удаления. Это его "идемпотентность".

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

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

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

Ответы

Ответ 1

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

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

Ответ 2

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

Мое мнение состоит в том, что в заданном запросе REST есть состояние, состоящее из двух состояний, состояния клиента и состояния сервера. REST - это передача этих состояний между сервером и клиентом, так что состояние клиента сопоставляется с подмножеством состояния сервера, другими словами, подмножество остается совместимым с сервером. Из-за этого idempotency должен означать, что последующие идемпотентные запросы не приведут ни к одному из состояний, которые отличались бы от того, что он будет делать только один раз. С помощью первого 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} 

Ответ 3

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

может применяться несколько раз, не изменяя результат за пределы исходного приложения.

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

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

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

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

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

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