RESTful Alternatives to DELETE Request Body

95

Chociaż specyfikacja HTTP 1.1 wydaje się zezwalać na treści wiadomości w żądaniach DELETE , wydaje się wskazywać, że serwery powinny ją ignorować, ponieważ nie ma dla niej zdefiniowanej semantyki.

4.3 Treść wiadomości

Serwer POWINIEN odczytać i przesłać dalej treść wiadomości na każde żądanie; jeśli metoda żądania nie obejmuje zdefiniowanej semantyki dla treści jednostki, wówczas treść wiadomości POWINNA zostać zignorowana podczas obsługi żądania.

Przejrzałem już kilka powiązanych dyskusji na ten temat w SO i nie tylko, takich jak:

Większość dyskusji wydaje się zgadzać, że dostarczanie treści wiadomości w DELETE może być dozwolone , ale generalnie nie jest zalecane.

Co więcej, zauważyłem trend w różnych bibliotekach klienta HTTP, w którym wydaje się, że coraz więcej ulepszeń jest rejestrowanych dla tych bibliotek w celu obsługi treści żądań w DELETE. Większość bibliotek wydaje się być do tego zobowiązana, choć czasami z niewielkim początkowym oporem.

Mój przypadek użycia wymaga dodania niektórych wymaganych metadanych do DELETE (np. „Powód” usunięcia, wraz z innymi metadanymi wymaganymi do usunięcia). Rozważyłem następujące opcje, z których żadna nie wydaje się całkowicie odpowiednia i zgodna ze specyfikacjami HTTP i / lub najlepszymi praktykami REST:

  • Treść wiadomości - specyfikacja wskazuje, że treści wiadomości w DELETE nie mają wartości semantycznej; nie w pełni obsługiwane przez klientów HTTP; nie jest to standardowa praktyka
  • Niestandardowe nagłówki HTTP - wymaganie niestandardowych nagłówków jest generalnie sprzeczne ze standardowymi praktykami ; używanie ich jest niezgodne z resztą mojego API, z których żadne nie wymaga niestandardowych nagłówków; ponadto brak jest dobrej odpowiedzi HTTP wskazującej złe wartości nagłówka niestandardowego (prawdopodobnie jest to zupełnie oddzielne pytanie)
  • Standardowe nagłówki HTTP - żadne standardowe nagłówki nie są odpowiednie
  • Parametry zapytania - dodanie parametrów zapytania w rzeczywistości zmienia usuwany identyfikator URI żądania; przeciwko standardowym praktykom
  • Metoda POST - (np. POST /resourceToDelete { deletemetadata }) POST nie jest semantyczną opcją usuwania; POST w rzeczywistości reprezentuje odwrotną pożądaną akcję (tj. POST tworzy podrzędne zasoby; ale muszę usunąć zasób)
  • Wiele metod - Dzielenie żądania DELETE na dwie operacje (np. PUT delete metadata, a następnie DELETE) dzieli niepodzielną operację na dwie, potencjalnie pozostawiając niespójny stan. Przyczyna usunięcia (i inne powiązane metadane) nie są częścią samej reprezentacji zasobów.

Moją pierwszą preferencją byłoby prawdopodobnie użycie treści wiadomości, a następnie niestandardowych nagłówków HTTP; jednak, jak wskazano, podejście to ma pewne wady.

Czy istnieją jakieś zalecenia lub najlepsze praktyki zgodne ze standardami REST / HTTP dotyczące umieszczania takich wymaganych metadanych w żądaniach DELETE? Czy są jakieś inne alternatywy, których nie rozważałem?

Shelley
źródło
2
Niektóre implementacje, takie jak Jerseynie zezwalają na deletetreść żądań.
basiljames,

Odpowiedzi:

46

Pomimo pewnych zaleceń, aby nie używać treści wiadomości dla żądań DELETE, to podejście może być odpowiednie w niektórych przypadkach użycia. To podejście, które ostatecznie zastosowaliśmy po ocenie innych opcji wymienionych w pytaniu / odpowiedziach i po współpracy z konsumentami usługi.

Chociaż użycie treści wiadomości nie jest idealne, żadna z pozostałych opcji również nie była idealnie dopasowana. Treść żądania DELETE pozwoliła nam łatwo i wyraźnie dodać semantykę wokół dodatkowych danych / metadanych, które były potrzebne do towarzyszenia operacji DELETE.

Nadal byłbym otwarty na inne przemyślenia i dyskusje, ale chciałem zamknąć pętlę w tej kwestii. Doceniam wszystkie przemyślenia i dyskusje na ten temat!

Shelley
źródło
12
To jest zły pomysł. Jedynym miejscem, w którym możesz mieć kłopoty, jest późniejsza decyzja o skorzystaniu z usługi przyspieszania HTTP, takiej jak Akamai EdgeConnect. Wiem na pewno, że EdgeConnect usuwa treści z żądań HTTP DELETE (ponieważ zużywają one przepustowość, są prawdopodobnie nieprawidłowe). Jest również prawdopodobne, że podobne usługi robią to samo (patrz funkcja przyspieszania Kindle i inne usługi podobne do CDN). Prawdopodobnie powinieneś przeprojektować, aby nie używać czasowników HTTP w swojej usłudze. Większość interfejsów API nie ma sensu przy użyciu czasowników HTTP / klasycznego-REST i problemy z transportem czasowników HTTP są bardzo trudne do rozwiązania.
Gabe
3
Zgadzam się z @Gabe, wysyłanie treści metodami, które z definicji nie mają treści , jest pewnym sposobem na losową utratę danych, gdy twoje bity przechodzą przez rury internetowe, i będziesz miał bardzo trudny czas na debugowanie go.
Nicholas Shanks
4
Te uwagi przeciwko używaniu DELETE są nieistotne, dopóki nie odnoszą się do bardzo ważnych problemów, które ma PO. Staram się jak najlepiej trzymać się ducha REST, ale usuwanie bez optymistycznej współbieżności i usuwanie, które nie ma atomowej operacji wsadowej, nie jest praktyczne w rzeczywistych sytuacjach. To poważna wada we wzorcu REST.
Quarkly
1
Jestem z @Quarkly w tej sprawie. Nie rozumiem, na czym polega idea RESTFUL, w jaki sposób powinniśmy sprawdzać współbieżność itp. Kontrole współbieżności nie należą do klienta.
Dirk Wessels
13

Wydaje się, że pragniesz jednej z dwóch rzeczy, z których żadna nie jest czysta DELETE:

  1. Masz dwie operacje,PUT o powód usunięcia następnie przez DELETEzasobu. Po usunięciu zawartość zasobu nie jest już dostępna dla nikogo. „Powód” nie może zawierać hiperłącza do usuniętego zasobu. Lub,
  2. Próbujesz zmienić zasób z state=activedo state=deletedprzy użyciu DELETEmetody. Zasoby ze stanem = usunięte są ignorowane przez główny interfejs API, ale nadal mogą być czytelne dla administratora lub osoby z dostępem do bazy danych. Jest to dozwolone - DELETEnie trzeba usuwać danych kopii zapasowej zasobu, a jedynie usunąć zasób uwidoczniony pod tym identyfikatorem URI.

Dowolną operację, która wymaga treści wiadomości w DELETEżądaniu, można podzielić na najogólniej: a, POSTaby wykonać wszystkie niezbędne zadania z treścią wiadomości, i DELETE. Nie widzę powodu, aby łamać semantykę protokołu HTTP.

Nicholas Shanks
źródło
3
Co się stanie, jeśli PUTpowód się powiedzie, a DELETEzasoby zawiodą? Jak można zapobiegać stanom niekonsekwentnym?
Lightman
1
@Lightman PUT określa tylko zamiar. Może istnieć bez odpowiedniego DELETE, co oznaczałoby, że ktoś chciał usunąć, ale albo się nie udało, albo zmienił zdanie. Odwrócenie kolejności wywołań pozwoliłoby również na wystąpienie DELETE bez powodu - podanie powodu byłoby wówczas traktowane jedynie jako adnotacja. Z obu tych powodów zalecałbym użycie opcji 2 z powyższego, tj. Zmianę stanu rekordu bazowego, tak aby zasób HTTP zniknął z jego bieżącego adresu URL. Odśmiecacz / administrator może wtedy wyczyścić rekordy
Nicholas Shanks
7

Biorąc pod uwagę sytuację, którą masz, podjąłbym jedno z następujących podejść:

  • Wyślij PUT lub PATCH : Wnioskuję, że operacja usuwania jest wirtualna, ze względu na potrzebę usunięcia przyczyny. Dlatego uważam, że aktualizowanie rekordu za pomocą operacji PUT / PATCH jest prawidłowym podejściem, nawet jeśli nie jest to operacja DELETE jako taka.
  • Użyj parametrów zapytania : identyfikator URI zasobu nie jest zmieniany. Właściwie uważam, że jest to również właściwe podejście. Pytanie, które utworzyłeś, dotyczyło zakazu usuwania, jeśli brakowało parametru zapytania. W twoim przypadku miałbym po prostu domyślny powód, jeśli przyczyna nie jest określona w ciągu zapytania. Zasób nadal będzie resource/:id. Możesz uczynić go wykrywalnym za pomocą nagłówków Link w zasobie z każdego powodu (z reltagiem na każdym z nich, aby zidentyfikować przyczynę).
  • Użyj osobnego punktu końcowego dla każdego powodu : używając adresu URL, takiego jak resource/:id/canceled. To faktycznie zmienia identyfikator URI żądania i zdecydowanie nie jest zgodny z REST. Ponownie, nagłówki linków mogą sprawić, że będzie to wykrywalne.

Pamiętaj, że REST to nie prawo ani dogmat. Potraktuj to bardziej jako wskazówkę. Tak więc, jeśli ma sens nie stosować się do wskazówek dotyczących domeny, w której występuje problem, nie rób tego. Po prostu upewnij się, że konsumenci API są poinformowani o rozbieżności.

Codeprogression
źródło
Jeśli chodzi o użycie parametrów zapytania, rozumiem, że zapytanie jest częścią identyfikatora URI żądania zgodnie z sekcją 3.2 , a zatem stosowanie tego podejścia (lub podobnie oddzielnych punktów końcowych) jest sprzeczne z definicją metody DELETE , tak że „zasób identyfikowane przez Request-URI ”.
shelley
Zasób jest identyfikowany przez ścieżkę uri. Więc GET /orders/:idto zwróci ten sam zasób co /orders/:id?exclude=orderdetails. Ciąg zapytania jest tylko wskazówką dla serwera - w tym przypadku w celu wykluczenia szczegółów zamówienia z odpowiedzi (jeśli jest obsługiwana). Podobnie, jeśli wysyłasz DELETE do /orders/:idlub /orders/:id?reason=canceledlub /orders/:id?reason=bad_credit, nadal działasz na tym samym zasobie bazowym. Aby zachować „jednolity interfejs”, miałbym domyślny powód, aby wysyłanie parametru zapytania nie było wymagane.
Codeprogression,
@shelley Masz rację, jeśli chodzi o ciągi zapytań. Ciąg zapytania jest częścią identyfikatora URI. Wysłanie żądania DELETE do /foo?123oznacza, że ​​usuwasz inny zasób, niż gdybyś miał wysłać DELETE do /foo?456.
Nicholas Shanks
@codeprogression Przepraszamy, ale wiele z tego, co mówisz, jest błędnych. Zasób jest identyfikowany przez cały identyfikator URI, a nie tylko przez ścieżkę. Różne ciągi zapytań to różne zasoby (w znaczeniu słowa „zasób” w HTTP). Również domyślny powód nie jest wymagany dla jednolitego interfejsu. Termin ten odnosi się do używania funkcji GET, PUT, POST, PATCH i DELETE w sposób zdefiniowany przez HTTP. Podobieństwo występuje między dostawcami (dostawcy agentów użytkownika, dostawcy API, dostawcy buforowania proxy, dostawcy usług internetowych itp.), A nie w ramach własnego interfejsu API (chociaż to również powinno być jednolite pod względem projektu ze względu na rozsądek użytkowników!).
Nicholas Shanks
@Nicholas Nie rozumiem Twojej potrzeby dyskutowania o dyskusji, która zakończyła się trzy lata temu. Odpowiedź i uwagi, które poczyniłem, są ważne i poprawne z punktu widzenia REST. REST to nie HTTP (ani żadna implementacja HTTP dostawcy). W kontekście REST zasoby są takie same. I jak powiedziałem w mojej odpowiedzi, REST nie jest prawem ani dogmatem, ale wskazówkami.
Codeprogression
0

Proponuję dołączyć wymagane metadane jako część samej hierarchii URI. Przykład (naiwny):

Jeśli chcesz usunąć wpisy na podstawie zakresu dat, zamiast przekazywać datę początkową i końcową w treści lub jako parametry zapytania, skonstruuj identyfikator URI w taki sposób, aby przekazać wymagane informacje jako część identyfikatora URI.

na przykład

DELETE /entries/range/01012012/31122012 - Usuń wszystkie wpisy między 1 stycznia 2012 a 31 grudnia 2012

Mam nadzieję że to pomoże.

Suresh Kumar
źródło
5
Nie obejmuje przypadków, takich jak przesłanie powodu usunięcia, np. Pole zamówienia.
Kugel
3
Łał. to okropny pomysł. Jeśli masz zbyt dużo metadanych, spowoduje to zwiększenie ograniczeń rozmiaru w identyfikatorze URI.
Balaji Boggaram Ramanarayan
1
To podejście nie jest zgodne z praktykami RESTful i nie jest zalecane, ponieważ będziesz mieć zawiłą strukturę URI. Punkty końcowe są mylone z przeplatającą się identyfikacją zasobów w porównaniu z metadanymi iz czasem staną się koszmarem konserwacji, gdy zmieni się twoje API. O wiele lepiej jest mieć rangeokreślone w zapytaniu parametry lub ładunek, który jest podstawą tego pytania: aby zrozumieć podejście najlepszych praktyk do problemu, które powiedziałbym, że nie jest to.
digitaldreamer
@digitaldreamer - Nie rozumiem, co masz na myśli mówiąc o zawiłej strukturze URI? Jest to również HTTP DELETE, więc ładunek nie jest opcją, ale parametrami zapytania tak.
Suresh Kumar