REST API - po co używać PUT DELETE POST GET?

155

Przeglądałem więc kilka artykułów na temat tworzenia REST API. A niektórzy z nich sugerują używanie wszystkich typów żądań HTTP: na przykład PUT DELETE POST GET. Utworzylibyśmy na przykład index.php i napisalibyśmy API w ten sposób:

$method = $_SERVER['REQUEST_METHOD'];
$request = split("/", substr(@$_SERVER['PATH_INFO'], 1));

switch ($method) {
  case 'PUT':
    ....some put action.... 
    break;
  case 'POST':
    ....some post action.... 
    break;
  case 'GET':
    ....some get action.... 
    break;
  case 'DELETE':
    ....some delete action.... 
    break;
}

OK, przyznam - nie wiem zbyt wiele o usługach internetowych (jeszcze). Ale czy nie byłoby łatwiej po prostu zaakceptować obiekt JSON za pomocą zwykłego POSTlub GET(który zawierałby nazwę metody i wszystkie parametry), a następnie odpowiedzieć również w formacie JSON. Możemy łatwo serialize / Cofnięcie poprzez PHP json_encode()i json_decode()i robić, co chcemy z tych danych bez konieczności zajmowania się różnymi metodami żądania HTTP.

Czy coś mi brakuje?

AKTUALIZACJA 1:

Ok - po przekopaniu się przez różne API i nauczeniu się wielu rzeczy o XML-RPC , JSON-RPC , SOAP , REST doszedłem do wniosku, że ten typ API jest dobry. W rzeczywistości wymiana stosów w zasadzie używa tego podejścia w swoich witrynach i myślę, że ci ludzie wiedzą, co robią Stack Exchange API .

Stann
źródło
4
Po co wymuszać ładunek JSON? A co, jeśli nie ma formatu JSON i jest to zwykły stary GET?
Mike DeSimone
1
Tak. w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9
Ignacio Vazquez-Abrams,

Odpowiedzi:

200

Pomysł RE prezentacyjny S tate T ransfer nie chodzi o dostęp do danych w najprostszy możliwy sposób.

Zasugerowałeś użycie żądań postów w celu uzyskania dostępu do JSON, który jest całkowicie prawidłowym sposobem uzyskiwania dostępu do danych / manipulowania nimi.

REST to metodologia istotnego dostępu do danych. Gdy zobaczysz żądanie w REST, powinno być natychmiast widoczne, co dzieje się z danymi.

Na przykład:

GET: /cars/make/chevrolet

prawdopodobnie zwróci listę chevy samochodów. Dobrym REST API może zawierać nawet kilka opcji wydruku w ciągu kwerendy jak ?output=jsonlub ?output=htmlktóre pozwoliłyby accessor zdecydować, jaki format informacje powinny być kodowane.

Po odrobinie myślenia o tym, jak racjonalnie włączyć wpisywanie danych do REST API, mam do wniosku, że najlepszym sposobem, aby określić typ danych jednoznacznie byłoby poprzez już istniejące rozszerzenia pliku, takie jak .js, .json, .html, lub .xml. Brakujące rozszerzenie pliku będzie miało domyślnie dowolny format (taki jak JSON); nieobsługiwane rozszerzenie pliku może zwrócić 501 Not Implementedkod stanu .

Inny przykład:

POST: /cars/
{ make:chevrolet, model:malibu, colors:[red, green, blue, grey] }

prawdopodobnie utworzy nowy chevy malibu w bazie danych z powiązanymi kolorami. Mówię prawdopodobnie, ponieważ interfejs API REST nie musi być bezpośrednio powiązany ze strukturą bazy danych. Jest to po prostu interfejs maskujący, dzięki któremu prawdziwe dane są chronione (pomyśl o nich jak o akcesoriach i mutatorach struktury bazy danych).

Teraz musimy przejść do kwestii idempotencji . Zwykle REST implementuje CRUD przez HTTP. HTTP używa GET, PUT, POSTa DELETEdla wniosków.

Bardzo uproszczona implementacja REST może wykorzystywać następujące mapowanie CRUD:

Create -> Post
Read   -> Get
Update -> Put
Delete -> Delete

Występuje problem z tą implementacją: Post jest zdefiniowany jako metoda nie idempotentna. Oznacza to, że kolejne wywołania tej samej metody Post będą skutkować różnymi stanami serwera. Get, Put i Delete są idempotentne; co oznacza, że ​​wywołanie ich wielokrotnie powinno skutkować identycznym stanem serwera.

Oznacza to, że żądanie takie jak:

Delete: /cars/oldest

faktycznie można by zaimplementować jako:

Post: /cars/oldest?action=delete

Natomiast

Delete: /cars/id/123456

spowoduje ten sam stan serwera, jeśli wywołasz go raz, lub jeśli zadzwonisz 1000 razy.

Lepszym sposobem usunięcia oldestprzedmiotu byłoby zażądanie:

Get: /cars/oldest

iz IDuzyskanych danych skorzystaj w celu zgłoszenia deleteżądania:

Delete: /cars/id/[oldest id]

Problem z tą metodą mógłby wystąpić, gdyby inny /carselement został dodany między momentem /oldestzażądania a deletewydaniem.

zzzzBov
źródło
3
@Andre jest to kombinacja kilku powodów: Postępowanie zgodnie z wytycznymi HTTP oznacza, że ​​(prawdopodobnie) będziesz mieć mniej problemów ze zgodnością wsteczną, gdy coś się zmieni; użycie formularza html przez POST ostrzeże użytkownika przed wielokrotnym przesyłaniem tych samych danych (ma to na celu zapobieżenie transakcji nie idempotentnej); przestrzeganie dobrze określonej najlepszej praktyki jest ... najlepszą praktyką. Reszta nie jest definiowana z myślą o konkretnej implementacji, co pozwala na jej użycie według własnego uznania. Sugerowałbym skorzystanie ze wszystkich kodów błędów HTTP i metod żądań, ale możesz to zrobić, jak chcesz
zzzzBov
4
Tak więc problem z tą odpowiedzią (jest to przyzwoita odpowiedź, ale nie kompletna) polega na tym, że nie odpowiada ona na główne pytanie, które zadał: Dlaczego miałbyś używać czasowników HTTP i URI zamiast niestandardowych danych JSON (być może jakiegoś rodzaju Składnia wywołania interfejsu API oparta na JSON). Możesz ustawić własną składnię JSON w taki sposób, aby „natychmiast ... było jasne, co dzieje się z danymi”. To, czego nie możesz zrobić, to łatwo korzystać z wbudowanych funkcji i warstw sieciowych na szczycie protokołu HTTP, tak jak w przypadku interfejsu API zgodnego ze wszystkimi konwencjami REST. Nie żeby moja odpowiedź była oczywiście idealna;)
Merlyn Morgan-Graham
4
@Andre: Przykłady, których używa wpis wiki, to uwierzytelnianie, buforowanie i negocjacja typu zawartości. Teraz, kiedy myślę o tym więcej, być może uda ci się ich używać z interfejsami w stylu RPC, ale często pokusa będzie implementacja własnego systemu od podstaw lub zaprogramowanie integracji z istniejącym systemem. Dzięki REST możesz korzystać z wbudowanej integracji i administrować nią na serwerze WWW. Oznacza to luźniejsze powiązanie, co oznacza, że ​​musisz mniej implementować, i oznacza, że ​​Twoja aplikacja jest znacznie bardziej elastyczna, jeśli chodzi o zmianę opcji w przyszłości, przy mniejszym kodzie i mniejszym wpływie na testy.
Merlyn Morgan-Graham
10
Zamiast USUŃ: / samochody / najstarsze, co powiesz na GET: / samochody / najstarsze, a następnie USUŃ? W ten sposób masz dwa oddzielnie idempotentne polecenia.
Neil,
3
+1; Zgadzam się, że to dobra odpowiedź (powtarzam to jeszcze raz dla przyjemności i zysku). POST: /cars/oldestzastępowanie DELETE nie ma większego sensu. Coś w rodzaju - POST: /cars/oldest/deletemoże, chociaż myślę, że bardziej podoba mi się rozwiązanie Neila. Jedyną zaletą bezpośredniego usuwania nad rozwiązaniem get-id-delete-id jest atomowość. Chciałbym mieć wyraźne uzasadnienie biznesowe z nieskomplikowanym scenariuszem, zanim wdrożę coś takiego. Nie musisz obsługiwać wszystkich czasowników na wszystkich obiektach / adresach URL.
Merlyn Morgan-Graham
39

To jest kwestia bezpieczeństwa i łatwości konserwacji.

bezpieczne metody

O ile to możliwe, należy używać „bezpiecznych” (jednokierunkowych) metod, takich jak GET i HEAD, aby ograniczyć potencjalną podatność.

metody idempotentne

O ile to możliwe, powinieneś używać metod „idempotentnych”, takich jak GET, HEAD, PUT i DELETE, które nie mogą mieć skutków ubocznych i dlatego są mniej podatne na błędy / łatwiejsze do kontrolowania.

Źródło

markus
źródło
1
Przepraszam, ale jak działają idempotentne metody PUT i DELETE? Wpływają na stan serwera i jego dane!
Mahmoud Al-Qudsi,
27
@Komputer: wykonanie tego samego PUT lub tego samego DELETE skutkuje tym samym stanem końcowym. To właśnie oznacza „idempotentny”.
Ignacio Vazquez-Abrams
4
Dla wyjaśnienia: operacja F jest idempotentna, jeśli jej pojedyncza aplikacja i kilka następujących po niej aplikacji zwraca ten sam wynik. Dokładniej, F jest idempotentne wtedy i tylko wtedy, gdy F (x) = F (F (x)). Na przykład Usuń jest idempotentne, ponieważ gdy usuniesz element raz lub usuniesz go kilka razy, wynik jest taki sam: element jest usuwany tylko raz za pomocą pierwszej aplikacji usuwania i nic się nie dzieje w usuwaniu drugiej lub trzeciej aplikacji.
qartal
1
Ale jeśli chodzi o tworzenie, kiedy tworzysz nowy rekord za pomocą polecenia tworzenia i ponownie wydajesz to samo polecenie, tworzone są (prawdopodobnie) dwa rekordy (chociaż oba odzwierciedlają te same informacje).
qartal
qartal - Twoja funkcjonalna definicja idempotentna powinna brzmieć „F (X) = F (X) F (X)”. Miły sposób, żeby to wyrazić.
Gerard ONeill
26

Krótko mówiąc, REST kładzie nacisk na rzeczowniki nad czasowniki. Gdy twoje API staje się bardziej złożone, dodajesz więcej rzeczy, a nie więcej poleceń.

Neil
źródło
2
Miałem trochę problemów ze zrozumieniem tego. Ten post ( lornajane.net/posts/2013/… ), że czasownik powinien pochodzić z żądania HTTP, aby identyfikator URI zawierał tylko rzeczowniki, wyjaśnił to odrobinę
icc97
9

Zapytałeś :

czy nie byłoby łatwiej po prostu zaakceptować obiekt JSON przez normalne $ _POST, a następnie odpowiedzieć również w formacie JSON

Z Wikipedii na REST :

Aplikacje RESTful maksymalizują wykorzystanie wcześniej istniejącego, dobrze zdefiniowanego interfejsu i innych wbudowanych funkcji zapewnianych przez wybrany protokół sieciowy oraz minimalizują dodawanie nowych funkcji specyficznych dla aplikacji.

Z tego, co (niewiele) widziałem, wydaje mi się, że zwykle osiąga się to poprzez maksymalizację wykorzystania istniejących czasowników HTTP i zaprojektowanie schematu adresu URL dla Twojej usługi, który jest tak potężny i oczywisty, jak to tylko możliwe.

Niestandardowe protokoły danych (nawet jeśli są zbudowane na podstawie standardowych, takich jak SOAP lub JSON) są odradzane i powinny być zminimalizowane, aby jak najlepiej odpowiadały ideologii REST.

Z drugiej strony, protokół SOAP RPC przez HTTP zachęca każdego projektanta aplikacji do zdefiniowania nowego, dowolnego słownika rzeczowników i czasowników (na przykład getUsers (), savePurchaseOrder (...)), zwykle nakładanych na czasownik HTTP „POST”. Pomija to wiele istniejących możliwości HTTP, takich jak uwierzytelnianie, buforowanie i negocjowanie typów zawartości, i może pozostawić projektantowi aplikacji ponowne wymyślenie wielu z tych funkcji w nowym słowniku.

Rzeczywiste obiekty, z którymi pracujesz, mogą mieć dowolny format. Chodzi o to, aby ponownie wykorzystać jak najwięcej HTTP, aby ujawnić operacje, które użytkownik chce wykonać na tych zasobach (zapytania, zarządzanie stanem / mutacja, usuwanie).

Zapytałeś :

Czy coś mi brakuje?

Jest dużo więcej informacji o REST i samych czasownikach URI / HTTP. Na przykład niektóre czasowniki są idempotentne, inne nie. Nie widziałem nic na ten temat w twoim pytaniu, więc nie próbowałem się w to zagłębiać. Pozostałe odpowiedzi i Wikipedia zawierają wiele dobrych informacji.

Ponadto można się wiele dowiedzieć o różnych technologiach sieciowych zbudowanych na podstawie protokołu HTTP, z których można skorzystać, jeśli używasz naprawdę spokojnego interfejsu API. Zacząłbym od uwierzytelnienia.

Merlyn Morgan-Graham
źródło
8

W odniesieniu do używania rozszerzenia do definiowania typu danych. Zauważyłem, że MailChimp API to robi, ale nie sądzę, żeby to był dobry pomysł.

GET /zzz/cars.json/1

GET /zzz/cars.xml/1

Wydaje mi się, że to dobry pomysł, ale myślę, że „starsze” podejście jest lepsze - użycie nagłówków HTTP

GET /xxx/cars/1
Accept: application/json

Również nagłówki HTTP są znacznie lepsze do komunikacji między typami danych (jeśli kiedykolwiek ktoś by ich potrzebował)

POST /zzz/cars
Content-Type: application/xml     <--- indicates we sent XML to server
Accept: application/json          <--- indicates we want get data back in JSON format  
Paweł Cioch
źródło
4

Czy coś mi brakuje?

Tak. ;-)

Zjawisko to występuje z powodu jednolitego ograniczenia interfejsu . REST lubi wykorzystywać już istniejące standardy zamiast wymyślać koło na nowo. Standard HTTP już okazał się wysoce skalowalny (sieć działa już jakiś czas). Po co naprawiać coś, co nie jest zepsute ?!

Uwaga: Jednolite ograniczenie interfejsu jest ważne, jeśli chcesz oddzielić klientów od usługi. Jest to podobne do definiowania interfejsów dla klas, aby oddzielić je od siebie. Ofc. tutaj jednolity interfejs składa się ze standardów, takich jak HTTP , typy MIME , URI , RDF , połączone słowniki danych , hydra vocab itp.

inf3rno
źródło
2

Dobra semantyka jest ważna w programowaniu.

Wykorzystanie większej liczby metod poza GET / POST będzie pomocne, ponieważ zwiększy czytelność kodu i ułatwi jego utrzymanie.

Czemu?

Ponieważ wiesz, że GET pobierze dane z Twojego interfejsu API. Wiesz, że POST doda nowe dane do twojego systemu. Wiesz, że PUT dokona aktualizacji. DELETE usunie wiersze itp.,

Zwykle strukturyzuję moje RESTFUL Web Services tak, że mam wywołanie zwrotne funkcji o tej samej nazwie, co metoda.

Używam PHP, więc używam function_exists (myślę, że to się nazywa). Jeśli funkcja nie istnieje, rzucam 405 (METODA NIEDOZWOLONA).

HumbleWebDev
źródło
2

Bill Venners: W swoim poście na blogu zatytułowanym „Why REST Failed” powiedziałeś, że potrzebujemy wszystkich czterech czasowników HTTP - GET, POST, PUT i DELETE - i narzekałeś, że dostawcy przeglądarek tylko GET i POST. „Dlaczego potrzebujemy wszystkich czterech czasowniki? Dlaczego nie wystarczy GET i POST?

Elliotte Rusty Harold: Istnieją cztery podstawowe metody HTTP: GET, POST, PUT i DELETE. GET jest używany przez większość czasu. Służy do wszystkiego, co jest bezpieczne, co nie powoduje żadnych skutków ubocznych. GET może być dodawany do zakładek, zapisywany w pamięci podręcznej, powiązany z serwerem proxy. To bardzo potężna operacja, bardzo użyteczna operacja.

Natomiast POST jest prawdopodobnie najpotężniejszą operacją. To może zrobić wszystko. Nie ma ograniczeń co do tego, co może się wydarzyć, dlatego trzeba z tym bardzo uważać. Nie dodajesz tego do zakładek. Nie buforujesz tego. Nie pobierasz go z wyprzedzeniem. Nie robisz nic z POST bez pytania użytkownika. Chcesz to zrobić? Jeśli użytkownik naciśnie przycisk, możesz opublikować niektóre treści. Ale nie będziesz patrzeć na wszystkie przyciski na stronie i zacząć je losowo naciskać. Z drugiej strony przeglądarki mogą przeglądać wszystkie linki na stronie i pobierać je z wyprzedzeniem lub pobierać z wyprzedzeniem te, które według nich są najbardziej prawdopodobne, aby były śledzone jako następne. W rzeczywistości niektóre przeglądarki i rozszerzenia Firefoksa oraz różne inne narzędzia próbowały to zrobić w takim czy innym momencie.

PUT i DELETE znajdują się pośrodku między GET i POST. Różnica między PUT lub DELETE i POST polega na tym, że PUT i DELETE są * idempotentne, a POST nie. W razie potrzeby można powtórzyć PUT i DELETE. Załóżmy, że próbujesz przesłać nową stronę do witryny. Załóżmy, że chcesz utworzyć nową stronę pod adresem adresem http://www.example.com/foo.html, więc wpisujesz treść i PUTUJESZ ją pod tym adresem URL. Serwer tworzy tę stronę pod podanym adresem URL. Teraz załóżmy, że z jakiegoś powodu twoje połączenie sieciowe przestaje działać. Nie jesteś pewien, czy prośba została przesłana, czy nie? Może sieć działa wolno. Może wystąpił problem z serwerem proxy. Tak więc można spróbować ponownie lub jeszcze raz - tyle razy, ile chcesz. Ponieważ PODAWANIE tego samego dokumentu pod ten sam adres URL dziesięć razy nie różni się niczym od umieszczenia go raz. To samo dotyczy DELETE. Możesz USUNĄĆ coś dziesięć razy i to tak samo, jak jednorazowe usunięcie.

Z kolei POST może powodować za każdym razem coś innego. Wyobraź sobie, że wychodzisz ze sklepu internetowego, naciskając przycisk Kup. Jeśli ponownie wyślesz żądanie POST, możesz po raz drugi kupić wszystko w koszyku. Jeśli wyślesz go ponownie, kupiłeś go po raz trzeci. Dlatego przeglądarki muszą bardzo uważać na powtarzanie operacji POST bez wyraźnej zgody użytkownika, ponieważ POST może spowodować dwie rzeczy, jeśli zrobisz to dwa razy, trzy rzeczy, jeśli zrobisz to trzy razy. W przypadku PUT i DELETE istnieje duża różnica między zerowymi żądaniami a jednym, ale nie ma różnicy między jednym żądaniem a dziesięcioma.

Więcej informacji można znaleźć pod adresem URL. http://www.artima.com/lejava/articles/why_put_and_delete.html

Aktualizacja:

Metody idempotentne Metoda idempotentna HTTP to metoda HTTP, która może być wywoływana wiele razy bez różnych wyników. Nie ma znaczenia, czy metoda jest wywoływana tylko raz, czy dziesięć razy. Wynik powinien być taki sam. Ponownie dotyczy to tylko wyniku, a nie samego zasobu. Nadal można nim manipulować (jak znacznik czasu aktualizacji, pod warunkiem, że te informacje nie są udostępniane w (bieżącej) reprezentacji zasobów.

Rozważ następujące przykłady:

a = 4;

a ++;

Pierwszy przykład jest idempotentny: bez względu na to, ile razy wykonamy tę instrukcję, a zawsze będzie równe 4. Drugi przykład nie jest idempotentny. Wykonanie tego 10 razy da inny wynik niż wykonanie 5 razy. Ponieważ oba przykłady zmieniają wartość a, obie nie są metodami bezpiecznymi.

Bimal Das
źródło
1
O przykładzie nowej strony, czy nie należy używać POST w ten sposób, podczas gdy PUT do aktualizacji? Tworzenie nowej strony to proces, który za każdym razem wyświetla nowy wynik, podczas gdy ta sama edycja może zostać powtórzona dowolną ilość razy, za każdym razem uzyskując ten sam wynik. Jednak ładne sformułowanie i wyjaśnienie.
Spyryto
0

Zasadniczo REST to ( wiki ):

  1. Architektura klient-serwer
  2. Bezpaństwowość
  3. Cacheability
  4. System warstwowy
  5. Kod na żądanie (opcjonalnie)
  6. Jednolity interfejs

REST to nie protokół, to zasady. Różne metody i metody - ktoś tak zwane najlepsze praktyki.

MAX
źródło