Czy powinienem używać kodów stanu HTTP do opisywania zdarzeń na poziomie aplikacji

54

Kilka serwerów, z którymi miałem do czynienia, zwróci HTTP 200 dla żądań, które klient powinien rozważyć jako błąd, z czymś w rodzaju „sukces: fałsz” w treści.

Nie wydaje mi się to prawidłową implementacją kodów HTTP, szczególnie w przypadku nieudanego uwierzytelnienia. Przeczytałem kody błędów HTTP dość zwięźle podsumowane, ponieważ „4xx” oznacza, że ​​żądanie nie powinno zostać wykonane ponownie, dopóki nie zostanie zmienione, a „5xx” oznacza, że ​​żądanie może być lub może nie być prawidłowe i może zostać ponowione, ale nie powiodło się. W tym przypadku 200: logowanie nie powiodło się lub 200: nie można znaleźć tego pliku lub 200: brak parametru x, zdecydowanie wydaje się nieprawidłowy.

Z drugiej strony widziałem argument, że „4xx” powinien wskazywać jedynie na problem strukturalny z żądaniem. Warto więc zwrócić 200: zły użytkownik / hasło zamiast 401 nieautoryzowanych, ponieważ klient może złożyć żądanie, ale zdarza się, że jest niepoprawny. Argument ten można podsumować, ponieważ jeśli serwer byłby w stanie przetworzyć żądanie i w ogóle ustalić, kod odpowiedzi powinien wynosić 200, a to od klienta zależy sprawdzenie treści w celu uzyskania dalszych informacji.

Zasadniczo wydaje się to być kwestią preferencji. Ale to nie jest satysfakcjonujące, więc jeśli ktoś ma powód, dla którego któryś z tych paradygmatów jest bardziej poprawny, chciałbym wiedzieć.

Kagan Mattson
źródło
9
success: falseoznacza, że ​​żądanie nie powiodło się i wiesz o tym. To powinno być 500. Coś w rodzaju złej nazwy użytkownika / hasła to 401. To nie jest takie dwuznaczne.
Pete,
4
Myślę, że jest to jedno z tych pytań, które mogą wywoływać wojny religijne. W przypadku interfejsu API RESTful odpowiedź jest jasna, ale istnieją inne rodzaje interfejsów API, w których HTTP jest traktowany jak warstwa transportowa, aw takich przypadkach błędy aplikacji nie powinny spadać do tej warstwy.
Gort the Robot
5
Kiedy naprawdę nie jestem pewien, jaki status HTTP zwrócić, zawsze kuszące jest użycie 418 „Jestem czajnikiem”.
joshp,
1
Przykładem jest wiele (zestawionych) żądań i odpowiedzi . Dozowanie nie jest kojącą rzeczą; ale praktyczne problemy z wydajnością często wymagają pewnego wsparcia w rozwiązywaniu problemów związanych z elegancją.
rwong,

Odpowiedzi:

35

Interesujące pytanie.

Zasadniczo możemy sprowadzić to do właściwego sposobu klasyfikowania rzeczy w kategoriach analogicznych do warstw OSI. HTTP jest powszechnie definiowany jako protokół poziomu aplikacji, a HTTP jest rzeczywiście ogólnym protokołem klient / serwer.

Jednak w praktyce serwer jest prawie zawsze urządzeniem przekazującym, a klient jest przeglądarką internetową, odpowiedzialną za interpretację i renderowanie treści: serwer po prostu przekazuje rzeczy do dowolnej aplikacji, która wysyła dowolne skrypty, które przeglądarka przegląda jest odpowiedzialny za wykonanie. Sama interakcja HTTP - formularze żądania / odpowiedzi, kody stanu itd. - to głównie kwestia tego, jak żądać, obsługiwać i renderować dowolne treści tak skutecznie, jak to możliwe, bez przeszkadzania. Wiele kodów stanu i nagłówków jest rzeczywiście zaprojektowanych do tych celów.

Problem z próbą nałożenia protokołu HTTP na obsługę przepływów specyficznych dla aplikacji polega na tym, że masz jedną z dwóch opcji: 1) Musisz ustawić logikę żądania / odpowiedzi jako podzbiór reguł HTTP; lub 2) Musisz ponownie zastosować określone zasady, a następnie rozdzielenie obaw zwykle się rozmywa. Na początku może to wyglądać ładnie i czysto, ale myślę, że jest to jedna z tych decyzji projektowych, których żałujesz w miarę rozwoju projektu.

Dlatego powiedziałbym, że lepiej jest wyraźnie powiedzieć o rozdzieleniu protokołów. Pozwól, aby serwer HTTP i przeglądarka internetowa zrobiły swoje, a aplikacja niech zrobi to samo. Aplikacja musi być w stanie wysyłać żądania i potrzebuje odpowiedzi - a jej logika dotycząca sposobu żądania, interpretowania odpowiedzi może być bardziej (lub mniej) złożona niż perspektywa HTTP.

Inną zaletą tego podejścia, o której warto wspomnieć, jest to, że aplikacje zasadniczo nie powinny zależeć od podstawowego protokołu transportowego (z logicznego punktu widzenia). Sam HTTP zmienił się w przeszłości, a teraz uruchamiamy HTTP 2, po SPDY. Jeśli widzisz swoją aplikację jako zwykłą wtyczkę funkcjonalną HTTP, możesz utknąć w niej, gdy przejmą ją nowe infrastruktury.

Yam Marcovic
źródło
8
Bardzo wnikliwy. Najsilniejszym argumentem jest niedopasowanie (impedancja) między kodami stanu HTTP a wartościami zwracanymi przez aplikację. Na dłuższą metę może to stać się koszmarem. Ponadto zdecydowanie popieram rozdzielenie obaw między transportem (HTTP) a ładunkiem (dane aplikacji). Jeśli błędnie wpiszesz adres URL punktu końcowego usługi, otrzymasz 404. Jeśli poprosisz usługę o nieistniejący element, otrzymasz wiadomość specyficzną dla aplikacji (być może z dodatkowymi informacjami, których możesz użyć, aby rozwiązać problem).
2
Jeśli błędnie wpiszesz adres URL, możesz nawet nie trafić na właściwy serwer, a wtedy wszystko może się zdarzyć.
gnasher729,
To ładnie dopracowany wygląd. Myślę, że kwestia stania się warstwą pseudo-transportu HTTP jest prawdziwym problemem przy podejmowaniu decyzji. Najczęściej spotykam się z tym pytaniem, gdy masz serwer nginx lub apache proxy do serwera nodejs, gdzie proxy ma już reguły wysyłania tych kodów, a pytanie brzmi, czy backend jest zgodny ze standardem. W niektórych przypadkach może istnieć powód konstrukcyjny, aby nie wysyłać kodu błędu, ponieważ nginx może interpretować go jako „backend down”.
Kagan Mattson,
4
Zgadzam się. Nie ma nic złego w zgłaszaniu błędu warstwy aplikacji w odpowiedzi HTTP 200. Liczba 200 wskazuje, że samo żądanie / odpowiedź HTTP zakończyło się powodzeniem, nie mówiąc nic o jego treści lub semantyce warstwy aplikacji w tym czasie.
Wyścigi lekkości z Moniką
22

To pytanie jest nieco oparte na opiniach, ale tak czy inaczej.

Z mojego punktu widzenia 200 może podawać „miękkie błędy”. Jeśli chodzi o tworzenie interfejsów API, staram się rozróżniać te od „twardych błędów”.

„Błędy miękkie” będą wyświetlane z kodem stanu 200, ale będą zawierać opis błędu i status powodzenia false. „Miękkie błędy” pojawią się tylko wtedy, gdy wynik będzie „zgodny z oczekiwaniami”, ale nie będzie to sukces w ścisłym tego słowa znaczeniu.

Należy zauważyć, że „błędy miękkie” są raczej wskazówką dla implementatora. Dlatego ważne jest, aby podać także więcej informacji o błędzie, takich jak czytelny dla człowieka komunikat o błędzie i / lub jakiś kod, który może być użyty do przekazania użytkownikowi końcowemu informacji zwrotnej. Te błędy dostarczają implementatorowi (i użytkownikowi końcowemu) więcej informacji o tym, co wydarzyło się po stronie serwera rzeczy.

Powiedzmy na przykład, że masz interfejs API z funkcją wyszukiwania, ale podczas wyszukiwania nie są wyświetlane żadne wyniki. Nie jest to błędne, ale też nie jest „sukcesem”, nie w najściślejszym znaczeniu definicji.

Przykład sformatowany jako JSON:

{
    "meta" {
        "success": false,
        "message": "Search yielded no results",
        "code": "NORESULTS"
    }
    "data": []
}

Z drugiej strony „twarde błędy” otrzymają kod statusu zalecany dla błędu. Użytkownik nie jest zalogowany? - 403 / 401. Zniekształcone dane wejściowe? - 400. Błąd serwera? - 50X. I tak dalej.

Ponownie, jest to trochę oparte na opiniach. Niektórzy ludzie chcą traktować wszystkie błędy jednakowo, „twardy błąd” wszystko. Brak wyników wyszukiwania? To 404! Po drugiej stronie monety nie ma wyników wyszukiwania? - Jest to zgodne z oczekiwaniami, bez błędu.

Innym ważnym czynnikiem, który należy wziąć pod uwagę, jest na przykład twoja architektura; jeśli korzystasz z interfejsu API za pomocą zapytań JavaScript XHR i jQuery lub AngularJS. Te „twarde błędy” będą musiały być obsługiwane za pomocą osobnego wywołania zwrotnego, podczas gdy „miękkie błędy” mogą być obsługiwane za pomocą wywołania „powodzenia”. Nic nie psując, wynik jest nadal „zgodny z oczekiwaniami”. Kod po stronie klienta może następnie sprawdzić status powodzenia i kod (lub komunikat). I wydrukuj to użytkownikowi końcowemu.

umrzeć maus
źródło
2
Właściwie klasyfikowanie tego jako błędu na poziomie interfejsu API jest w ogóle dziwną decyzją. Nawet jeśli klient może według własnego uznania zaklasyfikować to jako nieoczekiwane na poziomie użytkownika.
Deduplicator,
1
Należy wziąć pod uwagę wiele rzeczy. Wszystko zależy od implementacji interfejsu API. To także znowu, trochę oparte na opiniach, a także do tego, co API definiuje jako „sukces” i / lub „błąd”. Flaga "success": falsejest raczej wskazówką dla implementatora, że ​​coś jest nie tak. Zwykle powinien zawierać wewnętrzny kod statusu. Albo "code": "NORESULTS"albo kod numeryczny - cokolwiek twórca interfejsu API ma ochotę. Przeważnie tam, więc ktokolwiek implementuje API, może odjąć informacje o tym, co się stało na serwerze.
die maus
15

Istnieją dwa aspekty interfejsu API: wysiłek wdrożenia interfejsu API i wysiłek wszystkich klientów w celu prawidłowego korzystania z interfejsu API.

Jako autor klienta wiem, że kiedy wysyłam zapytanie do serwera WWW, mogę otrzymać błąd (nigdy nie rozmawiał poprawnie z serwerem) lub odpowiedź z kodem stanu. Muszę poradzić sobie z błędami. Muszę sobie poradzić z dobrą reakcją. Muszę sobie radzić z oczekiwanymi, udokumentowanymi „złymi” odpowiedziami. Muszę sobie poradzić z tym, co jeszcze wróci.

Projektując interfejs API, powinieneś sprawdzić, co jest najłatwiejsze do przetworzenia przez klienta. Jeśli klient wysyła poprawnie sformułowane żądanie i możesz zrobić to, o co prosi żądanie, powinieneś udzielić odpowiedzi w zakresie 200 (w niektórych przypadkach odpowiednia jest liczba inna niż 200 w tym zakresie).

Jeśli klient zapyta „daj mi wszystkie rekordy, takie jak ...”, a jest zero, to 200 z sukcesem i tablica zerowych rekordów jest w pełni odpowiednia. Przypadki, o których wspominasz:

„Logowanie nie powiodło się” zwykle powinno wynosić 401. „Nie można znaleźć pliku” powinno wynosić 404. „Brak parametru x” powinien wynosić około 500 (właściwie 400, jeśli serwer stwierdzi, że żądanie jest złe, a 500 jeśli serwer jest całkowicie zdezorientowany moim żądaniem i nie ma pojęcia, co się dzieje). Zwrócenie 200 w tych przypadkach jest bezcelowe. Oznacza to po prostu, że jako autor klienta nie mogę po prostu patrzeć na kod statusu, muszę też przestudiować odpowiedź. Nie mogę po prostu powiedzieć „status 200, świetnie, oto dane”.

Zwłaszcza „brak parametru” - nie jest to coś, z czym bym sobie poradził . To znaczy, że moja prośba jest nieprawidłowa. Jeśli moje żądanie jest niepoprawne, nie mam możliwości naprawy tego niepoprawnego żądania - na początek wysłałbym prawidłowe żądanie. Teraz jestem zmuszony to znieść. Dostaję 200 i muszę sprawdzić, czy brakuje odpowiedzi „parametru”. To okropne.

W końcu istnieje kilkanaście lub dwa kody stanu do obsługi wielu różnych sytuacji i należy ich używać.

gnasher729
źródło
3
Podczas łączenia się z interfejsem API osobiście wolałbym uzyskać 200 za „nie znaleziono pliku” podczas łączenia z prawidłowym punktem końcowym, ponieważ wtedy moja obsługa HTTP nie musi krwawić do warstwy, która obsługuje interfejs API na nim.
whatsisname
4
„Brak parametru x” powinien wynosić 400 BAD_REQUEST, ponieważ klient robi coś złego. 500 INTERNAL_SERVER_ERROR należy zarezerwować dla przypadków, w których serwer robi coś złego. 500 oznacza, że ​​klient może spróbować ponownie. 400 oznacza, że ​​ktoś powinien naprawić klienta.
Gort the Robot
1
Jeśli piszesz interfejs RESTful, adres URL identyfikuje konkretny obiekt, więc 404 jest odpowiedni. Jest to koncepcyjnie to samo /customers/premium/johndoe.jsonodnosi się do klienta, którego nie ma w bazie danych, a jeśli /files/morefiles/customers.htmlodnosi się do strony spoza systemu plików.
Gort the Robot
@whatsisname To, co mówisz, ma sens, ponieważ wtedy nie jest jasne, czy jest to zły punkt końcowy, czy zasób nie istnieje. Można również argumentować, że niezależnie od tego, czy punkt końcowy jest poprawny, nie ma żadnego zasobu pod tym adresem, więc 404w obu przypadkach jest poprawny.
Pete,
2
Jedną z rzeczy, o których nie wspomniałem, jest to, że kiedy na barki statusu HTTP nakładasz błędy aplikacji, możesz stracić informacje. Jeśli aplikacja zwraca tylko 404 i nic więcej, nie wiesz, czy to dlatego, że interfejs API zwrócił 404, czy też dlatego, że serwer nie mógł znaleźć pliku. To może dodać jeden dodatkowy krok do debugowania.
AmadeusDrZaius