Альтернативы RESTful для тела запроса DELETE

95

В то время как спецификация HTTP 1.1, кажется, позволяет телам сообщений на запросах DELETE , похоже, это указывает на то, что серверы должны игнорировать его, поскольку для него нет определенной семантики.

4.3 Тело сообщения

Серверу СЛЕДУЕТ читать и пересылать тело сообщения по любому запросу; если метод запроса не включает определенную семантику для тела объекта, то тело сообщения СЛЕДУЕТ игнорировать при обработке запроса.

Я уже рассмотрел несколько связанных обсуждений по этой теме в SO и за ее пределами, например:

Большинство обсуждений, похоже, сходятся во мнении, что предоставление тела сообщения при DELETE может быть разрешено. , но обычно не рекомендуется.

Кроме того, я заметил тенденцию в различных клиентских библиотеках HTTP, где все больше и больше улучшений, похоже, регистрируются для этих библиотек для поддержки тел запросов при DELETE. Большинство библиотек, кажется, подчиняются, хотя иногда и с небольшим начальным сопротивлением.

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

  • Тело сообщения - спецификация указывает, что тела сообщения при DELETE не имеют семантического значения; не полностью поддерживается HTTP-клиентами; не стандартная практика
  • Настраиваемые заголовки HTTP. Требование настраиваемых заголовков обычно противоречит стандартной практике ; их использование несовместимо с остальной частью моего API, ни один из которых не требует настраиваемых заголовков; кроме того, нет хорошего HTTP-ответа, указывающего на неправильные значения настраиваемого заголовка (вероятно, это отдельный вопрос вообще)
  • Стандартные заголовки HTTP - стандартные заголовки не подходят
  • Параметры запроса - добавление параметров запроса фактически изменяет удаляемый Request-URI; против стандартной практики
  • Метод POST - (например POST /resourceToDelete { deletemetadata }) POST не является семантическим вариантом удаления; POST фактически представляет собой противоположное желаемое действие (т.е. POST создает подчиненные ресурсы; но мне нужно удалить ресурс)
  • Множественные методы - разделение запроса DELETE на две операции (например, PUT удалить метаданные, затем DELETE) разбивает атомарную операцию на две, потенциально оставляя несогласованное состояние. Причина удаления (и другие связанные метаданные) не являются частью самого представления ресурса.

Моим первым предпочтением, вероятно, было бы использование тела сообщения, а затем - настраиваемых заголовков HTTP; однако, как указано, у этих подходов есть некоторые недостатки.

Существуют ли какие-либо рекомендации или передовые методы, соответствующие стандартам REST / HTTP, для включения таких необходимых метаданных в запросы DELETE? Есть ли другие альтернативы, которые я не рассматривал?

Шелли
источник
2
Некоторые реализации, например Jersey, не допускают тело для deleteзапросов.
basiljames

Ответы:

46

Несмотря на некоторые рекомендации не использовать тело сообщения для запросов DELETE, этот подход может быть уместным в определенных случаях использования. Это подход, который мы в конечном итоге использовали после оценки других вариантов, упомянутых в вопросе / ответах, и после сотрудничества с потребителями услуги.

Хотя использование тела сообщения не идеально, ни один из других вариантов не подходил идеально. Тело запроса DELETE позволило нам легко и четко добавить семантику к дополнительным данным / метаданным, которые были необходимы для сопровождения операции DELETE.

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

Шелли
источник
12
Это плохая идея. У вас могут возникнуть проблемы, если вы позже решите использовать службу ускорения HTTP, такую ​​как Akamai EdgeConnect. Я точно знаю, что EdgeConnect удаляет тела из HTTP-запросов DELETE (поскольку они потребляют пропускную способность, они, вероятно, недействительны). Также вероятно, что аналогичные службы делают то же самое (см. Функцию ускорения Kindle и другие службы, подобные CDN). Вам, вероятно, следует изменить дизайн, чтобы не использовать HTTP-глаголы для вашей службы. Большинство API-интерфейсов не имеют смысла использовать HTTP-глаголы / классический REST, а проблемы с транспортом HTTP-глаголов очень трудно устранить.
Гейб
3
Я согласен с @Gabe, отправка тела с методами, которые по определению не имеют тела , - это верный способ случайной потери данных, когда ваши биты пересекают интернет-каналы, и вам будет очень трудно его отлаживать.
Николас Шанкс
4
Эти комментарии против использования DELETE не имеют значения, пока они не решат очень важные проблемы, которые есть у OP. Я изо всех сил стараюсь придерживаться духа REST, но удаление без оптимистичного параллелизма и удаление без атомарной пакетной операции нецелесообразно в реальных ситуациях. Это серьезный недостаток шаблона REST.
Quarkly,
1
Я с @Quarkly по этому поводу. Я не понимаю, в чем заключается RESTFUL идея о том, как мы должны проверять параллелизм и т. Д. Проверки параллелизма не относятся к клиенту.
Дирк Весселс,
13

Кажется, вам нужно одно из двух, ни одна из которых не является чистой DELETE:

  1. У вас есть две операции,PUT по причине удаления , за которым следуетDELETE ресурса. После удаления содержимое ресурса становится недоступным никому. «Причина» не может содержать гиперссылку на удаленный ресурс. Или,
  2. Вы пытаетесь изменить ресурс с state=activeна state=deletedс помощью DELETEметода. Ресурсы с состоянием = удалено игнорируются вашим основным API, но могут быть доступны для чтения администратору или кому-то с доступом к базе данных. Это разрешено - DELETEне нужно стирать резервные данные для ресурса, только чтобы удалить ресурс, указанный в этом URI.

Любая операция, которая требует тела сообщения в DELETEзапросе, может быть разбита на самые общие, POSTна выполнение всех необходимых задач с телом сообщения и на DELETE. Я не вижу причин нарушать семантику HTTP.

Николас Шэнкс
источник
3
Что произойдет, если PUTразум преуспеет, а DELETEресурс окажется неудачным ? Как можно предотвратить противоречивое состояние?
Lightman
1
@Lightman PUT указывает только намерение. Он может существовать без соответствующего DELETE, что означало бы, что кто-то хотел удалить, но либо это не удалось, либо они передумали. Изменение порядка вызовов на противоположное также позволит выполнять DELETE без причины - указание причины тогда будет рассматриваться просто как аннотация. По обеим этим причинам я бы рекомендовал использовать вариант 2 из вышеизложенного, то есть изменить состояние базовой записи, например, чтобы ресурс HTTP исчез из текущего URL-адреса. Затем сборщик мусора / администратор может очищать записи
Николас Шэнкс,
7

В вашей ситуации я бы выбрал один из следующих подходов:

  • Отправьте PUT или PATCH : я делаю вывод, что операция удаления является виртуальной, поскольку для этого требуется причина удаления. Поэтому я считаю, что обновление записи с помощью операции PUT / PATCH является допустимым подходом, даже если это не операция DELETE как таковая.
  • Используйте параметры запроса : URI ресурса не изменяется. Я действительно думаю, что это тоже правильный подход. В связанном вами вопросе говорилось о запрете удаления, если параметр запроса отсутствует. В вашем случае у меня будет просто причина по умолчанию, если причина не указана в строке запроса. Ресурс все равно будет resource/:id. Вы можете сделать его доступным для обнаружения с помощью заголовков Link на ресурсе для каждой причины (с relтегом для каждой причины).
  • Используйте отдельную конечную точку для каждой причины : использование URL-адреса, например resource/:id/canceled. Это действительно меняет Request-URI и определенно не является RESTful. Опять же, заголовки ссылок могут сделать это видимым.

Помните, что ОТДЫХ - это не закон или догма. Думайте об этом больше как о руководстве. Итак, если имеет смысл не следовать рекомендациям в вашей проблемной области, не делайте этого. Просто убедитесь, что ваши потребители API проинформированы об отклонении.

код
источник
Что касается использования параметров запроса, я понимаю, что запрос является частью Request-URI в разделе 3.2 , и, следовательно, использование этого подхода (или аналогичным образом отдельных конечных точек) противоречит определению метода DELETE , так что "ресурс идентифицированный Request-URI "удаляется.
Shelley
Ресурс идентифицируется по пути uri. Таким образом, GET /orders/:idвозвращает тот же ресурс, что и /orders/:id?exclude=orderdetails. Строка запроса дает серверу только подсказки - в данном случае, чтобы исключить детали заказа в ответе (если поддерживается). Точно так же, если вы отправляете DELETE на /orders/:idили /orders/:id?reason=canceledили /orders/:id?reason=bad_credit, вы по-прежнему действуете на том же базовом ресурсе. Чтобы сохранить «единый интерфейс», у меня была бы причина по умолчанию, так что отправка параметра запроса не требуется.
codeprogression
@shelley Вы правы, говоря о строках запроса. Строка запроса является частью URI. Отправка запроса DELETE /foo?123означает, что вы удаляете другой ресурс, чем если бы вы отправляли DELETE /foo?456.
Николас Шанкс
@codeprogression Извините, но многое из того, что вы говорите, неверно. Ресурс идентифицируется всем URI, а не только путем. Различные строки запроса - это разные ресурсы (в смысле слова «ресурс» в HTTP). Кроме того, для унифицированного интерфейса не требуется причина по умолчанию. Этот термин относится к использованию GET, PUT, POST, PATCH и DELETE, как их определил HTTP. Общность существует между поставщиками (поставщиками пользовательских агентов, поставщиками API, поставщиками кэширующих прокси, поставщиками услуг Интернета и т. Д.), А не внутри собственного API (хотя он тоже должен быть единообразным по дизайну для разумности пользователей!).
Николас Шанкс
@Nicholas: Я не понимаю, что вам нужно спорить о дискуссии, которая закончилась три года назад. Ответ и комментарии, которые я сделал, действительны и верны с точки зрения REST. REST - это не HTTP (и не реализация HTTP каким-либо поставщиком). В контексте REST ресурсы такие же. И, как я сказал в своем ответе, ОТДЫХ - это не закон или догма, а руководство.
codeprogression
0

Я предлагаю вам включить необходимые метаданные как часть самой иерархии URI. Пример (наивный):

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

например

DELETE /entries/range/01012012/31122012 - Удалить все записи в период с 1 января 2012 г. по 31 декабря 2012 г.

Надеюсь это поможет.

Суреш Кумар
источник
5
Не распространяется на такие случаи, как отправка причины удаления, например, поле комментария.
Kugel
3
Вау. это ужасная идея. Если у вас слишком много метаданных, это приведет к увеличению ограничений на размер URI.
Баладжи Боггарам Раманараян
1
Этот подход не соответствует практике RESTful и не рекомендуется, поскольку у вас будет запутанная структура URI. Конечные точки запутываются из-за переплетения идентификации ресурсов и метаданных, и со временем они станут кошмаром обслуживания по мере изменения вашего API. Гораздо предпочтительнее иметь rangeуказанные в параметрах запроса или полезной нагрузке, которые являются основой этого вопроса: чтобы понять лучший подход к проблеме, который, я бы сказал, не в этом.
digitaldreamer
@digitaldreamer - я не понимаю, что вы имеете в виду под запутанной структурой URI? Кроме того, это HTTP-УДАЛЕНИЕ, поэтому полезная нагрузка - это не вариант, а параметры запроса - да.
Суреш Кумар