Opis REST: czasowniki, kody błędów i uwierzytelnianie

602

Szukam sposobu na zawinięcie interfejsów API wokół domyślnych funkcji w aplikacjach internetowych, bazach danych i CMS opartych na PHP.

Rozejrzałem się i znalazłem kilka szkieletów. Oprócz odpowiedzi na moje pytanie, istnieje Tonic , platforma REST, którą lubię, ponieważ jest bardzo lekka.

Najbardziej podoba mi się REST ze względu na jego prostotę i chciałbym na jego podstawie stworzyć architekturę API. Staram się omijać podstawowe zasady i jeszcze go nie rozumiem. Dlatego też szereg pytań.

1. Czy rozumiem, prawda?

Powiedzmy, że mam zasób „użytkowników”. Mógłbym skonfigurować wiele takich identyfikatorów URI:

/api/users     when called with GET, lists users
/api/users     when called with POST, creates user record
/api/users/1   when called with GET, shows user record
               when called with PUT, updates user record
               when called with DELETE, deletes user record

czy jest to jak dotąd poprawne przedstawienie architektury RESTful?

2. Potrzebuję więcej czasowników

Teoria tworzenia, aktualizacji i usuwania może wystarczyć, ale w praktyce będę potrzebować znacznie więcej czasowników. Zdaję sobie sprawę, że są to rzeczy, które mogą być osadzone w żądaniu aktualizacji, ale są to konkretne akcje, które mogą mieć określone kody powrotu i nie chciałbym, aby wszystkie były wrzucane w jedną akcję.

Niektóre, które przychodzą na myśl w przykładzie użytkownika to:

activate_login
deactivate_login
change_password
add_credit

jak wyraziłbym działania takie jak te w architekturze RESTful URL?

Moim instynktem byłoby wykonanie wywołania GET do adresu URL takiego jak

/api/users/1/activate_login 

i oczekuj z powrotem kodu statusu.

To jednak odbiega od idei używania czasowników HTTP. Co myślisz?

3. Jak zwracać komunikaty o błędach i kody

Duża część piękna REST wynika z zastosowania standardowych metod HTTP. W przypadku błędu wysyłam nagłówek z kodem błędu 3xx, 4xx lub 5xx. Aby uzyskać szczegółowy opis błędu, mogę użyć treści (prawda?). Na razie w porządku. Ale jaki byłby sposób przesłania zastrzeżonego kodu błędu, który jest bardziej szczegółowy w opisie tego, co poszło źle (np. „Nie udało się połączyć z bazą danych” lub „błędne logowanie do bazy danych”)? Jeśli umieszczę go w ciele wraz z wiadomością, muszę go później przeanalizować. Czy istnieje standardowy nagłówek do tego rodzaju rzeczy?

4. Jak przeprowadzić uwierzytelnianie

  • Jak wyglądałoby uwierzytelnianie oparte na kluczu API zgodnie z zasadami REST?
  • Czy istnieją mocne strony, by nie używać sesji podczas uwierzytelniania klienta REST, poza tym, że jest to rażące naruszenie zasady REST? :) (tylko połowa żartów tutaj, uwierzytelnianie oparte na sesji dobrze bawi się z moją istniejącą infrastrukturą).
Pekka
źródło
13
@Daniel, dzięki za edycję. „I więcej czasowników” było celowym kalamburem, ale zostawiam go takim, jakim jest, teraz jest łatwiejsze do odczytania. :)
Pekka
1
BTW, o opisie błędu. W końcu umieściłem opis błędu w nagłówku odpowiedzi. Wystarczy dodać nagłówek o nazwie „Opis błędu”.
Andrii Muzychuk
To bardziej przypomina pytania bezpieczeństwa aplikacji. Bezpieczeństwo aplikacji nie jest tym, o co chodzi w REST.
Nazar Merza
@NazarMerza jak są pytania bezpieczeństwa 1., 2. i 3. aplikacji?
Pekka

Odpowiedzi:

620

Zauważyłem to pytanie kilka dni później, ale czuję, że mogę dodać trochę wglądu. Mam nadzieję, że może to być pomocne w przedsięwzięciu RESTful.


Punkt 1: Czy rozumiem, prawda?

Dobrze zrozumiałeś. To poprawna reprezentacja architektury RESTful. Poniższa matryca z Wikipedii może być bardzo pomocna w definiowaniu rzeczowników i czasowników:


W przypadku identyfikatora URI kolekcji, takiego jak:http://example.com/resources/

  • POBIERZ : Lista członków kolekcji wraz z ich identyfikatorami URI członków w celu dalszej nawigacji. Na przykład wymień wszystkie samochody na sprzedaż.

  • PUT : Znaczenie zdefiniowane jako „zastąp całą kolekcję inną kolekcją”.

  • POST : Utwórz nowy wpis w kolekcji, w którym identyfikator jest automatycznie przypisywany przez kolekcję. Utworzony identyfikator jest zwykle dołączany jako część danych zwracanych przez tę operację.

  • USUŃ : Znaczenie zdefiniowane jako „usuń całą kolekcję”.


W przypadku identyfikatora URI członka, takiego jak:http://example.com/resources/7HOU57Y

  • POBIERZ : pobierz reprezentację adresowanego członka kolekcji wyrażoną odpowiednim typem MIME.

  • PUT : Zaktualizuj adresowanego członka kolekcji lub utwórz go z określonym identyfikatorem.

  • POST : Traktuje adresowanego członka jako zbiór sam w sobie i tworzy nowego podwładnego.

  • USUŃ : Usuń adresowanego członka kolekcji.


Punkt 2: Potrzebuję więcej czasowników

Ogólnie rzecz biorąc, jeśli uważasz, że potrzebujesz więcej czasowników, może to w rzeczywistości oznaczać konieczność ponownego zidentyfikowania zasobów. Pamiętaj, że w REST zawsze działasz na zasobie lub na zbiorze zasobów. To, co wybierzesz jako zasób, jest dość ważne dla definicji interfejsu API.

Aktywuj / dezaktywuj logowanie : jeśli tworzysz nową sesję, możesz rozważyć „sesję” jako zasób. Aby utworzyć nową sesję, użyj POST do http://example.com/sessions/poświadczeń w treści. Aby wygasnąć, użyj PUT lub DELETE (być może w zależności od tego, czy zamierzasz zachować historię sesji) http://example.com/sessions/SESSION_ID.

Zmień hasło: Tym razem zasobem jest „użytkownik”. Potrzebujesz PUT http://example.com/users/USER_IDze starymi i nowymi hasłami w ciele. Działasz na zasobie „użytkownika”, a hasło zmiany jest po prostu żądaniem aktualizacji. Jest dość podobny do instrukcji UPDATE w relacyjnej bazie danych.

Moim instynktem byłoby wykonanie wywołania GET do adresu URL takiego jak /api/users/1/activate_login

Jest to sprzeczne z bardzo podstawową zasadą REST: Prawidłowe użycie czasowników HTTP. Każde żądanie GET nigdy nie powinno pozostawiać żadnych skutków ubocznych.

Na przykład żądanie GET nigdy nie powinno tworzyć sesji w bazie danych, zwracać pliku cookie z nowym identyfikatorem sesji ani pozostawiać żadnych pozostałości na serwerze. Czasownik GET jest jak instrukcja SELECT w silniku bazy danych. Pamiętaj, że odpowiedź na każde żądanie z czasownikiem GET powinna mieć możliwość buforowania, gdy zostaniesz o to poproszony z tymi samymi parametrami, tak jak na żądanie statycznej strony internetowej.


Punkt 3: Jak zwracać komunikaty o błędach i kody

Rozważ kody statusu HTTP 4xx lub 5xx jako kategorie błędów. Możesz opracować błąd w ciele.

Nie udało się połączyć z bazą danych: / Niepoprawne logowanie do bazy danych : Zasadniczo powinieneś użyć błędu 500 dla tego typu błędów. Jest to błąd po stronie serwera. Klient nie zrobił nic złego. 500 błędów zwykle uważa się za „możliwe do powtórzenia”. tzn. klient może ponowić to samo dokładne żądanie i oczekiwać, że zostanie zrealizowane po rozwiązaniu problemów z serwerem. Podaj szczegóły w ciele, aby klient mógł przedstawić nam kontekst ludzi.

Inną kategorią błędów byłaby rodzina 4xx, co ogólnie wskazuje, że klient zrobił coś złego. W szczególności ta kategoria błędów zwykle wskazuje klientowi, że nie ma potrzeby ponawiania żądania w jego obecnym kształcie, ponieważ będzie ono nadal ulegać ciągłemu niepowodzeniu. tzn. klient musi coś zmienić przed ponowieniem tego żądania. Na przykład błędy „Nie znaleziono zasobu” (HTTP 404) lub „Błędne żądanie” (HTTP 400) należą do tej kategorii.


Punkt 4: Jak przeprowadzić uwierzytelnianie

Jak wskazano w punkcie 1, zamiast uwierzytelniać użytkownika, możesz pomyśleć o utworzeniu sesji. Otrzymasz nowy „Identyfikator sesji” wraz z odpowiednim kodem stanu HTTP (200: przyznany dostęp lub 403: odmowa dostępu).

Następnie zapytasz serwer RESTful: „Czy możesz uzyskać zasoby dla tego identyfikatora sesji?”.

Nie ma trybu uwierzytelnionego - REST jest bezstanowy: tworzysz sesję, prosisz serwer o udostępnienie zasobów za pomocą tego identyfikatora sesji jako parametru, a po wylogowaniu przerywasz lub wygasa sesja.

Daniel Vassallo
źródło
6
Bardzo dobrze, jednak użycie PUThasła do zmiany hasła jest prawdopodobnie nieprawidłowe; PUTwymaga całego zasobu, więc musisz wysłać wszystkie atrybuty użytkownika, aby zachować zgodność z HTTP (a zatem i REST HATEOAS). Zamiast tego po prostu zmień hasło należy użyć PATCHlub POST.
Lawrence Dol
1
Myślę, że ten post byłby idealny, gdybyś rozwinął więcej o tym, co „POST: Traktuje adresowanego członka jako kolekcję samą w sobie i tworzy nowego podwładnego”. znaczy. - Znalazłem, co to znaczy Googling - to wyjątek od twojej skądinąd świetnej odpowiedzi.
Martin Konecny
6
Nie zgadzam się z ostatnim zdaniem. Wyjaśniasz, w jaki sposób REST jest bezstanowy. Zaloguj się, aby utworzyć sesję, a następnie wyloguj się, aby zakończyć sesję po wykonaniu jakiejś pracy, jest najlepszym przykładem stanowego interfejsu API.
Brandon
1
„Jest to sprzeczne z bardzo podstawową zasadą REST: prawidłowe użycie czasowników HTTP. Każde żądanie GET nigdy nie powinno pozostawiać żadnych skutków ubocznych.” - Co jeśli chcesz zachować liczbę trafień dla zasobu?
bobbyalex
1
Ten artykuł powinien odpowiedzieć na twoje pytania. saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices
java_geek
79

Mówiąc najprościej, robisz to całkowicie wstecz.

Nie powinieneś podchodzić do tego z jakich adresów URL powinieneś używać. Adresy URL pojawią się „za darmo”, gdy zdecydujesz, jakie zasoby są niezbędne dla twojego systemu ORAZ w jaki sposób będziesz reprezentować te zasoby oraz interakcje między zasobami a stanem aplikacji.

Cytując Roy Fielding

Interfejs API REST powinien poświęcić prawie cały swój wysiłek opisowy na zdefiniowanie typów mediów używanych do reprezentowania zasobów i sterowania stanem aplikacji lub na definiowanie rozszerzonych nazw relacji i / lub narzutów z włączonym hipertekstem dla istniejących standardowych typów mediów. Wszelkie wysiłki włożone w opisanie metod, które należy zastosować w odniesieniu do interesujących URI, powinny być całkowicie zdefiniowane w ramach reguł przetwarzania dla typu nośnika (i, w większości przypadków, już zdefiniowane przez istniejące typy nośników). [Błąd tutaj oznacza, że ​​informacje pozapasmowe napędzają interakcję zamiast hipertekstu.]

Ludzie zawsze zaczynają od identyfikatorów URI i myślą, że to jest rozwiązanie, a potem brakuje im kluczowej koncepcji w architekturze REST, w szczególności, jak cytowano powyżej: „Brak tutaj oznacza, że ​​informacje pozapasmowe napędzają interakcję zamiast hipertekstu. „

Szczerze mówiąc, wiele osób widzi wiele identyfikatorów URI i niektórych GET, PUT i POST i uważa, że ​​REST jest łatwy. REST nie jest łatwe. RPC przez HTTP jest łatwe, przenoszenie obiektów BLOB danych tam iz powrotem za pośrednictwem ładunków HTTP jest łatwe. Jednak REST wykracza poza to. REST jest niezależny od protokołu. HTTP jest po prostu bardzo popularny i odpowiedni dla systemów REST.

REST występuje w typach mediów, ich definicjach i sposobie, w jaki aplikacja steruje działaniami dostępnymi dla tych zasobów za pośrednictwem hipertekstu (efektywnie łączy).

Istnieją różne poglądy na temat typów mediów w systemach REST. Niektóre preferują ładunki specyficzne dla aplikacji, podczas gdy inne, takie jak podnoszenie istniejących typów mediów do ról odpowiednich dla aplikacji. Na przykład, z jednej strony, masz określone schematy XML dostosowane do twojej aplikacji, w przeciwieństwie do używania czegoś takiego jak XHTML jako reprezentacji, być może za pomocą mikroformatów i innych mechanizmów.

Wydaje mi się, że oba podejścia mają swoje miejsce, XHTML działa bardzo dobrze w scenariuszach, które pokrywają się zarówno z siecią napędzaną przez człowieka, jak i przez maszynę, podczas gdy poprzednie, bardziej specyficzne typy danych lepiej czuję ułatwiają interakcje między maszynami. Uważam, że podnoszenie formatów towarowych może potencjalnie utrudniać negocjowanie treści. „application / xml + yourresource” jest o wiele bardziej specyficzny jako typ nośnika niż „application / xhtml + xml”, ponieważ ten ostatni może dotyczyć wielu ładunków, które mogą być lub nie być czymś, czym tak naprawdę interesuje się klient maszyny, ani nie może ustal bez introspekcji.

Jednak XHTML działa bardzo dobrze (oczywiście) w ludzkiej sieci, w której przeglądarki i rendering są bardzo ważne.

Twoja aplikacja poprowadzi Cię w tego rodzaju decyzjach.

Częścią procesu projektowania systemu REST jest odkrywanie w systemie pierwszorzędnych zasobów wraz z pochodnymi zasobami pomocniczymi niezbędnymi do obsługi operacji na zasobach podstawowych. Po odkryciu zasobów, reprezentacja tych zasobów, a także diagramy stanów przedstawiające przepływ zasobów poprzez hipertekst w reprezentacjach, ponieważ jest to kolejne wyzwanie.

Przypomnij sobie, że każda reprezentacja zasobu w systemie hipertekstowym łączy zarówno rzeczywistą reprezentację zasobu, jak i przejścia stanu dostępne dla zasobu. Rozważ każdy zasób jako węzeł na wykresie, a łącza to linie wychodzące z tego węzła do innych stanów. Te linki informują klientów nie tylko o tym, co można zrobić, ale także o tym, co jest im potrzebne (ponieważ dobre łącze łączy w sobie identyfikator URI i wymagany typ nośnika).

Na przykład możesz mieć:

<link href="http://example.com/users" rel="users" type="application/xml+usercollection"/>
<link href="http://example.com/users?search" rel="search" type="application/xml+usersearchcriteria"/>

Twoja dokumentacja będzie mówić o polu rel o nazwie „users” oraz typie mediów „application / xml + youruser”.

Te linki mogą wydawać się zbędne, wszystkie rozmawiają z tym samym URI. Ale nie są.

Wynika to z faktu, że w relacji „użytkownicy” ten link mówi o kolekcji użytkowników, a do pracy z kolekcją można użyć jednolitego interfejsu (GET, aby odzyskać wszystkie, DELETE, aby usunąć wszystkie itp.)

Jeśli prześlesz ten adres URL, będziesz musiał przekazać dokument „application / xml + usercollection”, który prawdopodobnie będzie zawierał tylko jedną instancję użytkownika w dokumencie, abyś mógł dodać użytkownika, a może nie dodać kilku na pewnego razu. Być może twoja dokumentacja sugeruje, że możesz po prostu przekazać jeden typ użytkownika zamiast kolekcji.

Możesz zobaczyć, czego wymaga aplikacja do przeprowadzenia wyszukiwania, zgodnie z definicją linku „szukaj” i jego typu mediacji. Dokumentacja typu nośnika wyszukiwania pokaże Ci, jak się to zachowuje i czego można oczekiwać jako wyników.

Na wynos tutaj jednak same URI są w zasadzie nieistotne. Aplikacja kontroluje identyfikatory URI, a nie klientów. Poza kilkoma „punktami wejścia”, klienci powinni polegać na identyfikatorach URI dostarczonych przez aplikację do jej pracy.

Klient musi wiedzieć, jak manipulować i interpretować typy mediów, ale nie musi bardzo dbać o to, dokąd idzie.

Te dwa łącza są semantycznie identyczne w oczach klientów:

<link href="http://example.com/users?search" rel="search" type="application/xml+usersearchcriteria"/>
<link href="http://example.com/AW163FH87SGV" rel="search" type="application/xml+usersearchcriteria"/>

Skoncentruj się na swoich zasobach. Skoncentruj się na zmianach stanu w aplikacji i na tym, jak najlepiej to osiągnąć.

Will Hartung
źródło
1
Dzięki Will za tę bardzo głęboką odpowiedź. Zdobyto kilka punktów. Zdaję sobie sprawę, że planowanie z „jak wygląda adres URL” robi to na odwrót i planuję również od strony zasobów. Posługiwanie się adresami URL ułatwia mi zrozumienie tej koncepcji. To mogło być to, że moje wymagania mogą być spełnione z systemem, który nie robi 100% zasady obserwacji REST jak zdefiniować go tutaj. Wykreślę pełną listę wymagań dla każdego rodzaju zasobów, myślę, że będę w stanie podjąć decyzję. Twoje zdrowie.
Pekka
30

do 1 : Jak dotąd wygląda dobrze. Pamiętaj, aby zwrócić identyfikator URI nowo utworzonego użytkownika w nagłówku „Lokalizacja:” jako część odpowiedzi na POST wraz z kodem statusu „201 Utworzono”.

do 2: Aktywacja przez GET jest złym pomysłem, a umieszczenie czasownika w URI to zapach projektowy. Możesz rozważyć zwrot formularza na GET. W aplikacji internetowej byłby to formularz HTML z przyciskiem przesyłania; w przypadku użycia interfejsu API możesz chcieć zwrócić reprezentację zawierającą identyfikator URI do PUT w celu aktywacji konta. Oczywiście możesz dołączyć ten identyfikator URI również w odpowiedzi na POST do / users. Użycie PUT sprawi, że twoje żądanie będzie idempotentne, tzn. Może zostać bezpiecznie wysłane ponownie, jeśli klient nie jest pewien sukcesu. Ogólnie rzecz biorąc, zastanów się, w jakie zasoby możesz zmienić swoje czasowniki (rodzaj „nounification czasowników”). Zadaj sobie pytanie, z jaką metodą Twoje konkretne działanie jest najbardziej dostosowane. Np. Hasło_zmian -> PUT; dezaktywuj -> prawdopodobnie USUŃ; add_credit -> ewentualnie POST lub PUT.

do 3. Nie wymyślaj nowych kodów statusu, chyba że uważasz, że są one tak ogólne, że zasługują na standaryzację globalną. Staraj się używać najbardziej odpowiedniego dostępnego kodu stanu (przeczytaj o wszystkich z nich w RFC 2616). Dołącz dodatkowe informacje do treści odpowiedzi. Jeśli naprawdę, naprawdę jesteś pewien, że chcesz wymyślić nowy kod stanu, pomyśl jeszcze raz; jeśli nadal tak uważasz, upewnij się, że wybrałeś przynajmniej odpowiednią kategorię (1xx -> OK, 2xx -> informacyjne, 3xx -> przekierowanie; 4xx-> błąd klienta, 5xx -> błąd serwera). Czy wspominałem, że wymyślanie nowych kodów stanu to zły pomysł?

do 4. Jeśli w jakikolwiek sposób jest to możliwe, użyj struktury uwierzytelniania wbudowanej w HTTP. Sprawdź, jak Google dokonuje uwierzytelnienia w GData. Zasadniczo nie należy umieszczać kluczy API w identyfikatorach URI. Staraj się unikać sesji w celu zwiększenia skalowalności i obsługi buforowania - jeśli odpowiedź na żądanie różni się z powodu czegoś, co zdarzyło się wcześniej, zwykle przywiązujesz się do konkretnej instancji procesu serwera. O wiele lepiej jest przekształcić stan sesji w dowolny stan klienta (np. Uczynić go częścią kolejnych żądań) lub jawnie, przekształcając go w stan zasobu (serwera), tj. Nadając mu własny identyfikator URI.

Stefan Tilkov
źródło
Czy możesz omówić, dlaczego nie umieszczać kluczy API w adresach URL? Czy to dlatego, że są widoczne w dziennikach proxy? Co jeśli klucze są przejściowe i zależą od czasu? Co się stanie, jeśli użyty zostanie HTTPS?
MikeSchinkel,
4
Oprócz naruszania ducha (identyfikatory URI powinny identyfikować rzeczy), główną konsekwencją jest to, że rujnuje buforowanie.
Stefan Tilkov,
22

1. Masz dobry pomysł, jak zaprojektować swoje zasoby, IMHO. Nic bym nie zmienił.

2. Zamiast próbować rozszerzyć HTTP o więcej czasowników, zastanów się, do czego możesz zredukować proponowane czasowniki pod względem podstawowych metod i zasobów HTTP. Na przykład zamiast activate_loginczasownika można skonfigurować zasoby typu: /api/users/1/login/activektóry jest prostym boolowskim. Aby aktywować login, wystarczy PUTdokument „prawda”, 1 lub cokolwiek innego. Aby dezaktywować, PUTdokument, który jest pusty lub zawiera 0 lub fałsz.

Podobnie, aby zmienić lub ustawić hasło, po prostu wykonaj PUTs /api/users/1/password.

Ilekroć musisz dodać coś (np. Kredyt), pomyśl w kategoriach POSTs. Na przykład, możesz zrobić a POSTdla zasobu takiego jak /api/users/1/creditsciało zawierające liczbę kredytów do dodania. A PUTw tym samym zasobie można użyć do zastąpienia wartości zamiast dodawania. A POSTz liczbą ujemną w ciele odejmowałoby się i tak dalej.

3. Zdecydowanie odradzam rozszerzanie podstawowych kodów stanu HTTP. Jeśli nie możesz znaleźć takiego, który dokładnie pasuje do Twojej sytuacji, wybierz najbliższy i podaj szczegóły błędu w treści odpowiedzi. Pamiętaj też, że nagłówki HTTP są rozszerzalne; Twoja aplikacja może zdefiniować wszystkie niestandardowe nagłówki, które lubisz. Na przykład jedna aplikacja, nad którą pracowałem, może zwrócić 404 Not Foundw wielu okolicznościach. Z tego powodu zamiast zmuszać klienta do przeanalizowania treści odpowiedzi, właśnie dodaliśmy nowy nagłówek X-Status-Extended, który zawierał nasze zastrzeżone rozszerzenia kodów stanu. Możesz więc zobaczyć odpowiedź:

HTTP/1.1 404 Not Found    
X-Status-Extended: 404.3 More Specific Error Here

W ten sposób klient HTTP, taki jak przeglądarka internetowa, nadal będzie wiedział, co zrobić ze zwykłym kodem 404, a bardziej zaawansowany klient HTTP może wybrać X-Status-Extendedbardziej szczegółowe informacje w nagłówku.

4. W celu uwierzytelnienia zalecam używanie uwierzytelniania HTTP, jeśli możesz. Ale IMHO nie ma nic złego w korzystaniu z uwierzytelniania opartego na plikach cookie, jeśli jest to dla Ciebie łatwiejsze.

Friedo
źródło
4
Świetny pomysł wykorzystania „rozszerzonych” zasobów do robienia rzeczy na mniejszych częściach większego zasobu.
womble 28.06.11
1
Pliki cookie są poprawne w HTTP / REST, ale serwer nie powinien przechowywać ciasteczka jako stanu (a więc nie jako sesji). Plik cookie może przechowywać wartość taką jak HMAC, którą można jednak zdemontować bez sprawdzania stanu w innym miejscu.
Bruce Alderson,
14

Podstawy REST

REST ma jednolite ograniczenie interfejsu, co oznacza, że ​​klient REST musi polegać na standardach zamiast szczegółowych informacji dotyczących konkretnej usługi REST specyficznych dla aplikacji, więc klient REST nie przełamie drobnych zmian i prawdopodobnie będzie można go ponownie użyć.

Więc istnieje umowa między klientem REST a usługą REST. Jeśli użyjesz protokołu HTTP jako protokołu bazowego, następujące standardy są częścią umowy:

  • HTTP 1.1
    • definicje metod
    • definicje kodów statusu
    • nagłówki kontroli pamięci podręcznej
    • akceptuj i nagłówki treści
    • nagłówki auth
  • IRI ( URI utf8 )
  • ciało (wybierz jedno)
  • hiperłącza
    • co powinny je zawierać (wybierz jeden)
      • wysyłanie nagłówków linków
      • wysyłanie odpowiedzi hipermedialnej, np. html, atom + xml, hal + json, ld + json & hydra itp.
    • semantyka
      • użyj relacji łącza IANA i prawdopodobnie niestandardowych relacji łącza
      • użyj specyficznego dla aplikacji słownika RDF

REST ma ograniczenie bezstanowe, które deklaruje, że komunikacja między usługą REST a klientem musi być bezstanowa. Oznacza to, że usługa REST nie może utrzymywać stanów klienta, więc nie można przechowywać pamięci po stronie serwera. Musisz uwierzytelnić każde pojedyncze żądanie. Na przykład podstawowe uwierzytelnianie HTTP (część standardu HTTP) jest w porządku, ponieważ wysyła nazwę użytkownika i hasło przy każdym żądaniu.

Aby odpowiedzieć na pytania

  1. Tak, to może być.

    Wystarczy wspomnieć, że klienci nie dbają o strukturę IRI, dbają o semantykę, ponieważ podążają za linkami posiadającymi atrybuty relacji łącza lub danych połączonych (RDF).

    Jedyną ważną rzeczą w IRI jest to, że pojedynczy IRI musi identyfikować tylko jeden zasób. Pojedynczy zasób, taki jak użytkownik, może mieć wiele różnych IRI.

    To całkiem proste, dlaczego używamy ładnych IRI, takich jak /users/123/password; znacznie łatwiej jest napisać logikę routingu na serwerze, gdy zrozumiesz IRI, po prostu czytając go.

  2. Masz więcej czasowników, takich jak PUT, PATCH, OPCJE, a nawet więcej, ale nie potrzebujesz ich więcej ... Zamiast dodawać nowe czasowniki, musisz nauczyć się, jak dodawać nowe zasoby.

    activate_login -> PUT /login/active true deactivate_login -> PUT /login/active false change_password -> PUT /user/xy/password "newpass" add_credit -> POST /credit/raise {details: {}}

    (Logowanie nie ma sensu z punktu widzenia REST, z powodu ograniczenia bezstanowego).

  3. Twoi użytkownicy nie dbają o przyczynę problemu. Chcą wiedzieć tylko, czy wystąpił sukces lub błąd, i prawdopodobnie komunikat o błędzie, który mogą zrozumieć, na przykład: „Przykro nam, ale nie byliśmy w stanie zapisać Twojego posta.” Itp.

    Nagłówki statusu HTTP to standardowe nagłówki. Wszystko inne powinno znajdować się w ciele. Pojedynczy nagłówek nie wystarczy, aby opisać na przykład szczegółowe wielojęzyczne komunikaty o błędach.

  4. Ograniczenie bezstanowe (wraz z pamięcią podręczną i warstwowymi ograniczeniami systemowymi) zapewnia prawidłowe skalowanie usługi. Na pewno nie chcesz utrzymywać milionów sesji na serwerze, kiedy możesz zrobić to samo na klientach ...

    Klient zewnętrzny otrzymuje token dostępu, jeśli użytkownik przyzna mu dostęp za pomocą głównego klienta. Następnie klient zewnętrzny wysyła token dostępu do każdego żądania. Istnieją bardziej skomplikowane rozwiązania, na przykład możesz podpisywać każde pojedyncze żądanie itp. W celu uzyskania dalszych informacji sprawdź instrukcję OAuth.

Literatura pokrewna

inf3rno
źródło
11

W podanych przez ciebie przykładach użyłbym:

Activ_login

POST /users/1/activation

dezaktywuj_logowanie

DELETE /users/1/activation

Zmień hasło

PUT /passwords (zakłada to, że użytkownik jest uwierzytelniony)

Dodaj kredyt

POST /credits (zakłada to, że użytkownik jest uwierzytelniony)

W przypadku błędów zwrócisz błąd w treści w formacie, w którym otrzymałeś żądanie, więc jeśli otrzymasz:

DELETE /users/1.xml

Odesłałbyś odpowiedź z powrotem w formacie XML, to samo dotyczy JSON itp.

Do uwierzytelnienia należy użyć uwierzytelniania HTTP.

jonnii
źródło
1
Nie używałbym createjako części identyfikatora URI (pamiętaj, że identyfikatory URI powinny być rzeczownikami, a metody HTTP powinny być czasownikami działającymi na tych rzeczownikach). Zamiast tego miałbym zasób, /users/1/activektóry może być prostym boolowskim i może być ustawiane przez umieszczenie 1 lub 0 dla tego zasobu.
friedo
Masz rację, wyjąłem / create. Powinien to być post do zasobu singleton.
jonnii
3
Nie używałbym też activationtego identyfikatora URI, chyba że jawnie będziesz manipulował zasobem i zarządzał nim pod nazwą /users/1/activation. Co robi GET? Co robi PUT? Na pewno czuję, że weryfikujesz URI. Ponadto, tak jak w przypadku negocjacji typu zawartości, często najlepiej jest pominąć ten identyfikator URI i wstawić go do nagłówków Accept.
Cheeso
6
  1. Użyj postu, gdy nie wiesz, jak powinien wyglądać nowy identyfikator URI zasobu (tworzysz nowego użytkownika, aplikacja przypisuje nowemu użytkownikowi jego identyfikator), PUT do aktualizacji lub tworzenia zasobów, które wiesz, w jaki sposób będą reprezentowane (przykład : PUT /myfiles/thisismynewfile.txt)
  2. zwraca opis błędu w treści komunikatu
  3. Możesz użyć uwierzytelniania HTTP (jeśli jest wystarczające) Usługi sieciowe powinny być statelami
Arjan
źródło
5

Sugerowałbym (jako pierwsze przejście), który PUTpowinien być używany tylko do aktualizacji istniejących jednostek. POSTpowinny być używane do tworzenia nowych. to znaczy

/api/users     when called with PUT, creates user record

nie wydaje mi się właściwe. Reszta pierwszej sekcji (użycie czasownika) wygląda jednak logicznie.

Brian Agnew
źródło
prawdopodobnie ktoś pomyślał, że tak naprawdę nie była to odpowiedź na jego pytanie
lubos hasko
6
Moje podejście PUT kontra POST do tworzenia nowych encji polega na użyciu PUT, gdy osoba dzwoniąca kontroluje nazwę zasobu, więc możesz PUT do dokładnego zasobu i POST, gdy odbiorca kontroluje nazwę nowego zasobu (tak jak w przykładzie tutaj).
SteveD,
5

Pełne, ale skopiowane ze specyfikacji metody HTTP 1.1 pod adresem http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

9.3 GET

Metoda GET oznacza pobieranie wszelkich informacji (w formie encji) identyfikowanych przez identyfikator URI żądania. Jeśli identyfikator URI żądania odnosi się do procesu generowania danych, to dane wytworzone zostaną zwrócone jako byt w odpowiedzi, a nie tekst źródłowy procesu, chyba że tekst ten jest wynikiem procesu.

Semantyka metody GET zmienia się na „warunkowy GET”, jeśli komunikat żądania zawiera pole nagłówka If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match lub If-Range. Warunkowa metoda GET wymaga, aby jednostka została przesłana tylko w okolicznościach opisanych w polach warunkowych nagłówków. Warunkowa metoda GET ma na celu ograniczenie niepotrzebnego użycia sieci, umożliwiając odświeżanie buforowanych jednostek bez konieczności wielokrotnego żądania lub przesyłania danych przechowywanych już przez klienta.

Semantyka metody GET zmienia się na „częściowy GET”, jeśli komunikat żądania zawiera pole nagłówka Range. Częściowe żądanie GET wymaga przeniesienia tylko części jednostki, jak opisano w sekcji 14.35. Metoda częściowego GET ma na celu zmniejszenie niepotrzebnego użycia sieci, umożliwiając ukończenie częściowo pobranych jednostek bez przesyłania danych przechowywanych już przez klienta.

Odpowiedź na żądanie GET jest buforowalna, tylko jeśli spełnia wymagania dotyczące buforowania HTTP opisane w sekcji 13.

Uwagi dotyczące bezpieczeństwa w przypadku formularzy znajdują się w sekcji 15.1.3.

9.5 POST

Metoda POST służy do żądania, aby serwer źródłowy zaakceptował jednostkę zawartą w żądaniu jako nowego podwładnego zasobu określonego przez identyfikator URI żądania w wierszu żądania. POST został zaprojektowany, aby umożliwić jednolitą metodę obejmującą następujące funkcje:

  - Annotation of existing resources;
  - Posting a message to a bulletin board, newsgroup, mailing list,
    or similar group of articles;
  - Providing a block of data, such as the result of submitting a
    form, to a data-handling process;
  - Extending a database through an append operation.

Rzeczywista funkcja wykonywana metodą POST jest określana przez serwer i zwykle zależy od URI żądania. Wysłana jednostka jest podporządkowana temu identyfikatorowi URI w taki sam sposób, w jaki plik jest podporządkowany katalogowi zawierającemu go, artykuł z wiadomościami jest podporządkowany grupie dyskusyjnej, do której jest wysyłany, lub rekord jest podporządkowany bazie danych.

Czynność wykonana metodą POST może nie skutkować zasobem, który można zidentyfikować za pomocą identyfikatora URI. W takim przypadku albo 200 (OK), albo 204 (Brak treści) jest odpowiednim statusem odpowiedzi, w zależności od tego, czy odpowiedź zawiera encję, która opisuje wynik.

Jeśli zasób został utworzony na serwerze źródłowym, odpowiedzią POWINNA być 201 (Utworzony) i zawierać byt opisujący stan żądania i odnoszący się do nowego zasobu oraz nagłówek Lokalizacji (patrz sekcja 14.30).

Odpowiedzi na tę metodę nie są buforowane, chyba że odpowiedź zawiera odpowiednie pola nagłówka Cache-Control lub Expires. Jednak odpowiedź 303 (Zobacz inne) może być użyta do nakierowania agenta użytkownika na pobranie zasobu buforowanego.

Żądania POST MUSZĄ być zgodne z wymogami dotyczącymi przesyłania komunikatów określonymi w sekcji 8.2.

Informacje na temat bezpieczeństwa znajdują się w sekcji 15.1.3.

9,6 PUT

Metoda PUT żąda, aby zamknięta jednostka była przechowywana pod dostarczonym URI żądania. Jeśli identyfikator URI żądania odnosi się do już istniejącego zasobu, dołączony byt MUSI być uważany za zmodyfikowaną wersję tego, który znajduje się na serwerze źródłowym. Jeśli identyfikator URI żądania nie wskazuje na istniejący zasób, a ten identyfikator URI może zostać zdefiniowany jako nowy zasób przez żądającego agenta użytkownika, serwer źródłowy może utworzyć zasób z tym identyfikatorem URI. Jeśli zostanie utworzony nowy zasób, serwer źródłowy MUSI poinformować klienta użytkownika za pomocą odpowiedzi 201 (Utworzono). Jeśli istniejący zasób zostanie zmodyfikowany, POWINNY zostać wysłane kody odpowiedzi 200 (OK) lub 204 (brak treści), aby wskazać pomyślne zakończenie żądania. Jeśli nie można utworzyć lub zmodyfikować zasobu za pomocą identyfikatora URI żądania, POWINNA zostać podana odpowiednia odpowiedź na błąd, która odzwierciedla charakter problemu. Odbiorca jednostki NIE MOŻE zignorować żadnych nagłówków Content- * (np. Content-Range), których nie rozumie ani nie wdraża, i MUSI zwrócić odpowiedź 501 (Nie zaimplementowano) w takich przypadkach.

Jeśli żądanie przechodzi przez pamięć podręczną, a identyfikator URI żądania identyfikuje jedną lub więcej aktualnie buforowanych jednostek, wpisy MUSZĄ być traktowane jako nieaktualne. Odpowiedzi na tę metodę nie są buforowane.

Podstawowa różnica między żądaniami POST i PUT znajduje odzwierciedlenie w innym znaczeniu URI żądania. Identyfikator URI w żądaniu POST identyfikuje zasób, który będzie obsługiwał zamknięty obiekt. Ten zasób może być procesem akceptującym dane, bramą do innego protokołu lub osobnym podmiotem, który przyjmuje adnotacje. W przeciwieństwie do tego, URI w żądaniu PUT identyfikuje encję zawartą w żądaniu - agent użytkownika wie, jaki jest URI, a serwer NIE MOŻE próbować zastosować żądania do jakiegoś innego zasobu. Jeśli serwer chce zastosować żądanie do innego identyfikatora URI,

MUSI wysłać odpowiedź 301 (Moved Permanently); klient użytkownika MOŻE następnie podjąć własną decyzję dotyczącą tego, czy przekierować żądanie.

Jeden zasób MOŻE być identyfikowany przez wiele różnych identyfikatorów URI. Na przykład artykuł może mieć identyfikator URI do identyfikowania „bieżącej wersji”, który jest niezależny od identyfikatora URI identyfikującego każdą konkretną wersję. W takim przypadku żądanie PUT dotyczące ogólnego identyfikatora URI może spowodować zdefiniowanie kilku innych identyfikatorów URI przez serwer pochodzenia.

HTTP / 1.1 nie określa, w jaki sposób metoda PUT wpływa na stan serwera źródłowego.

Żądania PUT MUSZĄ spełniać wymagania dotyczące przesyłania wiadomości określone w sekcji 8.2.

O ile nie określono inaczej dla konkretnego nagłówka encji, nagłówki encji w żądaniu PUT POWINNY być stosowane do zasobu utworzonego lub zmodyfikowanego przez PUT.

9.7 USUŃ

Metoda DELETE żąda, aby serwer pochodzenia usunął zasób zidentyfikowany przez URI żądania. Ta metoda MOŻE zostać zastąpiona przez interwencję człowieka (lub w inny sposób) na serwerze źródłowym. Nie można zagwarantować klientowi, że operacja została wykonana, nawet jeśli kod statusu zwrócony z serwera źródłowego wskazuje, że akcja została wykonana pomyślnie. Jednak serwer NIE POWINIEN wskazywać sukcesu, chyba że w chwili udzielenia odpowiedzi zamierza usunąć zasób lub przenieść go w niedostępną lokalizację.

Pomyślna odpowiedź POWINNA wynosić 200 (OK), jeśli odpowiedź zawiera jednostkę opisującą status, 202 (Zaakceptowana), jeśli akcja nie została jeszcze podjęta, lub 204 (Brak treści), jeśli akcja została podjęta, ale odpowiedź nie obejmuje jednostka.

Jeśli żądanie przechodzi przez pamięć podręczną, a identyfikator URI żądania identyfikuje jedną lub więcej aktualnie buforowanych jednostek, wpisy MUSZĄ być traktowane jako nieaktualne. Odpowiedzi na tę metodę nie są buforowane.

gahooa
źródło
2

O kodach powrotu REST: błędne jest mieszanie kodów protokołu HTTP i wyników REST.

Widziałem jednak wiele implementacji mieszających je i wielu programistów może się ze mną nie zgodzić.

Kody zwrotne HTTP są powiązane z samym HTTP Requestsobą. Wywołanie REST jest wykonywane przy użyciu żądania protokołu przesyłania hipertekstu i działa na niższym poziomie niż sama wywołana metoda REST. REST jest koncepcją / podejściem, a jego wynik jest wynikiem biznesowym / logicznym , podczas gdy kod wyniku HTTP jest kodem transportowym .

Na przykład zwracanie „404 Nie znaleziono”, gdy dzwonisz / users /, jest mylące, ponieważ może to oznaczać:

  • Niepoprawny identyfikator URI (HTTP)
  • Nie znaleziono użytkowników (REST)

„403 Zabroniony / Odmowa dostępu” może oznaczać:

  • Potrzebne specjalne pozwolenie. Przeglądarki mogą sobie z tym poradzić, pytając użytkownika / hasło. (HTTP)
  • Niepoprawne uprawnienia dostępu skonfigurowane na serwerze. (HTTP)
  • Musisz być uwierzytelniony (REST)

Lista może być kontynuowana z błędem „500 Server error” (błąd rzucenia HTTP Apache / Nginx lub błąd ograniczeń biznesowych w REST) ​​lub innymi błędami HTTP itp.

Na podstawie kodu trudno zrozumieć, co było przyczyną niepowodzenia, awarii HTTP (transportu) lub awarii REST (logicznej).

Jeśli żądanie HTTP zostało fizycznie wykonane pomyślnie, zawsze powinno zwrócić kod 200, niezależnie od tego, czy rekord (y) został znaleziony, czy nie. Ponieważ zasób URI został znaleziony i był obsługiwany przez serwer http. Tak, może zwrócić pusty zestaw. Czy można otrzymać pustą stronę internetową z wynikiem 200 jako http, prawda?

Zamiast tego możesz zwrócić 200 kod HTTP i po prostu JSON z pustą tablicą / obiektem lub użyć flagi wynik bool / sukces, aby poinformować o statusie wykonanej operacji.

Ponadto niektórzy dostawcy Internetu mogą przechwycić twoje żądania i zwrócić ci kod 404 http. Nie oznacza to, że twoich danych nie znaleziono, ale na poziomie transportu coś jest nie tak.

Z Wiki :

W lipcu 2004 r. Brytyjski dostawca telekomunikacyjny BT Group wdrożył system blokowania treści Cleanfeed, który zwraca błąd 404 do każdego żądania treści zidentyfikowanego jako potencjalnie nielegalne przez Internet Watch Foundation. W tych samych okolicznościach inni dostawcy usług internetowych zwracają błąd „zabronione” HTTP 403. Praktyka stosowania fałszywych błędów 404 jako sposobu na ukrycie cenzury została również zgłoszona w Tajlandii i Tunezji. W Tunezji, gdzie przed rewolucją w 2011 r. Cenzura była poważna, ludzie zdali sobie sprawę z natury fałszywych błędów 404 i stworzyli wyimaginowaną postać o imieniu „Ammar 404”, która reprezentuje „niewidzialnego cenzora”.

Marcodor
źródło