REST API 404: Zły URI lub brakujący zasób?

219

Tworzę interfejs API REST, ale napotkałem problem.

Wydaje się, że przyjętą praktyką w projektowaniu interfejsu API REST jest to, że jeśli żądany zasób nie istnieje, zwracany jest kod 404.

Jednak dla mnie powoduje to niepotrzebną dwuznaczność. HTTP 404 jest tradycyjnie kojarzony ze złym identyfikatorem URI. W efekcie mówimy „Albo trafiłeś we właściwe miejsce, ale ten konkretny zapis nie istnieje lub nie ma takiej lokalizacji w Internecie! Naprawdę nie jestem pewien, który…”

Rozważ następujący identyfikator URI:

http://mywebsite/api/user/13

Jeśli otrzymam 404, czy to dlatego, że Użytkownik 13 nie istnieje? A może dlatego, że mój adres URL powinien być:

http://mywebsite/restapi/user/13

W przeszłości zwracałem właśnie wynik NULL z HTTP 200 OKkodem odpowiedzi, jeśli rekord nie istnieje. To proste i moim zdaniem bardzo czyste, nawet jeśli niekoniecznie jest to akceptowana praktyka. Ale czy jest na to lepszy sposób?

Brian Lacy
źródło
Prawdopodobnie duplikat. stackoverflow.com/questions/3821663/…
Spencer Kormos
7
Drugie pytanie wydaje się być związane z formatami ciągów zapytań URI. Dyskusja na temat 404 nie wystarcza, aby odpowiedzieć na moje pytanie, czyli czy istnieje bardziej odpowiedni lub przydatny sposób ustalenia, co tak naprawdę oznacza 404. Przejrzałem tę przed opublikowaniem.
Brian Lacy
Czy to normalne, że konsola przeglądarki zawiera błędy 404? Gdy wykonuję regularne operacje z poprawnym identyfikatorem URI, ale nie znaleziono zasobu.
Wasilij Mazhekin
3
404 nie wskazuje złego URI, wskazuje zasób Nie znaleziono. Może to wynikać z braku użytkownika „13” lub z braku zasobu / mywebsite / api.
ChrisV

Odpowiedzi:

101

404 to tylko kod odpowiedzi HTTP. Ponadto możesz podać treść odpowiedzi i / lub inne nagłówki z bardziej znaczącym komunikatem o błędzie, który zobaczą programiści.

Robert Levy
źródło
7
To ma sens. Muszę się jednak zastanowić, czy w ogóle rzeczywiście zyskasz na zwrocie 404, czy na zwróceniu 200 z zerową odpowiedzią?
Brian Lacy
15
Jeśli zwrócisz wartość 200 z wartością null, będziesz przeciwny specyfikacji HTTP. Możesz to zrobić, ale wtedy interfejs API nie będzie zgodny z ograniczeniem REST „Uniformed Interface”.
pozwanie
5
... a treść odpowiedzi powinna zawierać hiperłącza do prawidłowych identyfikatorów URI. Z wyjątkiem głównego identyfikatora URI i być może zakładki lub dwóch, klienci powinni zawsze podążać za linkami podanymi im przez serwer. Nie ma potrzeby wymyślania szczegółowej semantyki dotyczącej dokładnie tego, jak zdecydowali się pracować poza systemem.
fumanchu
@DarrylHebbes, co masz na myśli, mogę złożyć wniosek i zobaczyć pełną treść odpowiedzi na karcie Sieć konsoli programisty Chrome.
jaapz
Bardzo źle jest zwracać błąd (404 lub cokolwiek innego) dla każdego zapytania, które zwraca puste wyniki. Żadna baza danych tego nie zrobi i zwykle nie chcesz zgłaszać użytkownikowi pustego wyniku jako nieoczekiwanego warunku. Przykład: pytasz interfejsu API, aby sprawdzić, czy zaplanowano spotkanie na następne 30 minut. Jeśli nie ma, NIE powinieneś zwracać 404. Zwróć 200 OK i pustą tablicę, proszę.
esmiralha
59

Użyj, 404jeśli zasób nie istnieje. Nie wracaj 200z pustym ciałem.

Jest to podobne do undefinedpustego ciągu (np. "") W programowaniu. Chociaż są bardzo podobne, na pewno jest różnica.

404oznacza, że ​​nic nie istnieje przy tym URI (jak niezdefiniowana zmienna w programowaniu). Powrót 200z pustym ciałem oznacza, że ​​coś tam istnieje i że coś jest teraz puste (jak pusty ciąg w programowaniu).

404nie oznacza, że ​​był to „zły URI”. Istnieją specjalne kody HTTP przeznaczone do błędów URI (np 414 Request-URI Too Long.).

nategood
źródło
1
Hej, myślę, że może ci się podobać. Czy nie byłoby sensowniej zwrócić jednego z „41”? błędy, gdy występuje problem z żądanym zasobem? Na przykład 410 Gone oznacza „Żądany zasób nie jest już dostępny na serwerze i nie jest znany żaden adres przekierowania”. - (Patrz w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.11 )
Brian Lacy
W rzeczywistości zasób, o którym wspomniałem powyżej, mówi również: „Jeśli serwer nie wie lub nie ma możliwości ustalenia, czy warunek jest trwały, należy zamiast niego użyć kodu stanu 404 (Nie znaleziono)”. Więc może to niekoniecznie najlepsza opcja ..
Brian Lacy
2
Nie sądzę, aby któryś z błędów 41x był odpowiedni dla twojego przypadku użycia. Podoba mi się porównanie z ciągiem niezdefiniowanym i pustym, co jest bardziej zwięzłą wersją mojego punktu w mojej odpowiedzi. Istnieje różnica, ale nie oznacza to, że pusty ciąg znaków jest błędem. Aby rozciągnąć tę analogię: Można mieć metodę String getName(), która ma implementację takiego: return this.name == null ? "" : this.name. To nie jest błędna, chyba że klient ma wiedzieć, że this.namejest null.
jhericks
1
404 jest nadal najbardziej odpowiednią opcją. Możesz (i zachęca się) do skorzystania z treści tej odpowiedzi 404, aby poinformować użytkownika / klienta o konkretnych szczegółach otrzymania błędu. Zobacz: stackoverflow.com/a/9999335/105484 . W twoim przypadku może chcesz używać tego ciało zasugeruje użytkownikowi, że sformatowanie ich URI wyglądać / restapi / user / USER_ID
nategood
Ok, widzę tu kilka dobrych punktów, ale 4XX to kody błędów, jak to się dzieje w przypadku błędu? W jaki sposób klient może zapobiec wystąpieniu błędu 404?
Krzysztof Kubicki,
20

Jak w większości rzeczy „to zależy”. Ale dla mnie, twoja praktyka nie jest zły i nie jest wbrew specyfikacji HTTP per se . Wyczyśćmy jednak kilka rzeczy.

Po pierwsze, identyfikatory URI powinny być nieprzejrzyste. Nawet jeśli nie są nieprzejrzyste dla ludzi, są nieprzezroczyste dla maszyn. Innymi słowy, różnica pomiędzy http://mywebsite/api/user/13, http://mywebsite/restapi/user/13jest taka sama jak różnica między http://mywebsite/api/user/13i http://mywebsite/api/user/14nie znaczy to samo nie jest taki sam okres. Tak więc 404 byłoby całkowicie odpowiednie http://mywebsite/api/user/14(jeśli nie ma takiego użytkownika), ale niekoniecznie jedyna odpowiednia odpowiedź.

Możesz także zwrócić pustą odpowiedź 200 lub, bardziej precyzyjnie, odpowiedź 204 (bez zawartości). To przekazałoby klientowi coś jeszcze. Oznaczałoby to, że zasób zidentyfikowany przez http://mywebsite/api/user/14nie ma treści lub jest zasadniczo niczym. To znaczy, że nie jest taki zasób. Jednak niekoniecznie oznacza to, że twierdzisz, że jakiś użytkownik trwał w magazynie danych o identyfikatorze 14. To jest twoja prywatna sprawa, a nie klient zgłaszający żądanie. Jeśli więc ma sens modelowanie zasobów w ten sposób, śmiało.

Przekazywanie klientom informacji wiąże się z pewnymi konsekwencjami w zakresie bezpieczeństwa, które ułatwiłyby im odgadnięcie prawdziwych identyfikatorów URI. Zwrócenie 200 za chybienie zamiast 404 może dać klientowi wskazówkę, że przynajmniej http://mywebsite/api/userczęść jest poprawna. Złośliwy klient może po prostu próbować różnych liczb całkowitych. Ale dla mnie złośliwy klient i tak byłby w stanie odgadnąć tę http://mywebsite/api/userczęść. Lepszym rozwiązaniem byłoby użycie UUID. tzn. http://mywebsite/api/user/3dd5b770-79ea-11e1-b0c4-0800200c9a66jest lepszy niż http://mywebsite/api/user/14. W ten sposób możesz użyć swojej techniki zwracania 200, nie zdradzając zbyt wiele.

jhericks
źródło
1
To jest rozwiązanie, które wybrałem i użyłem 204 zamiast 404. Dla mnie 204 oznacza „Znalazłem twój kod, ale nie znalazłem wyników”, a 404 oznacza „Nie mogłem znaleźć żadnego kodu do wykonania, czy to zła ścieżka? ”
Matt Sanders,
11

404 Not Found technicznie oznacza, że obecnie nie uri map do zasobu. W twoim przykładzie interpretuję żądanie, http://mywebsite/api/user/13które zwraca wartość 404, co oznacza, że ​​ten adres URL nigdy nie został zmapowany na zasób. Dla klienta powinien to być koniec rozmowy.

Aby rozwiać wątpliwości w niejednoznaczny sposób, możesz ulepszyć swój interfejs API, podając inne kody odpowiedzi. Załóżmy na przykład, że chcesz zezwolić klientom na wysyłanie żądań GET do adresu URL http://mywebsite/api/user/13, chcesz poinformować, że klienci powinni używać kanonicznego adresu URL http://mywebsite/restapi/user/13. W takim przypadku możesz rozważyć wydanie stałego przekierowania, zwracając 301 Przeniesiono na stałe i podając kanoniczny adres URL w nagłówku lokalizacji odpowiedzi. Mówi to klientowi, że w przypadku przyszłych żądań powinien użyć kanonicznego adresu URL.

Ralph Allan Rice
źródło
11

Zasadniczo brzmi to tak, jakby odpowiedź mogła zależeć od tego, jak powstaje żądanie.

Jeśli żądany zasób stanowi część identyfikatora URI zgodnie z żądaniem, http://mywebsite/restapi/user/13a użytkownik 13 nie istnieje, wówczas 404 jest prawdopodobnie odpowiedni i intuicyjny, ponieważ identyfikator URI jest reprezentatywny dla nieistniejącego użytkownika / encji / dokumentu / itp. To samo dotyczy bezpieczniejszej techniki przy użyciu GUID http://mywebsite/api/user/3dd5b770-79ea-11e1-b0c4-0800200c9a66i powyższego argumentu api / restapi.

Jeśli jednak żądany identyfikator zasobu został zawarty w nagłówku żądania [dołącz własny przykład] lub rzeczywiście w URI jako parametr, np. http://mywebsite/restapi/user/?UID=13Wtedy URI nadal byłby poprawny (ponieważ koncepcja USER kończy się w http://mywebsite/restapi/user/); i dlatego można oczekiwać, że odpowiedź będzie wynosić 200 (z odpowiednio szczegółowym komunikatem), ponieważ określony użytkownik znany jako 13 nie istnieje, ale identyfikator URI tak. W ten sposób mówimy, że identyfikator URI jest dobry, ale żądanie danych nie zawiera treści.

Osobiście 200 nadal nie czuje się dobrze (chociaż wcześniej twierdziłem, że tak jest). Kod odpowiedzi 200 (bez pełnej odpowiedzi) może spowodować, że problem nie zostanie zbadany, gdy na przykład zostanie wysłany nieprawidłowy identyfikator.

Lepszym rozwiązaniem byłoby wysłanie 204 - No Contentodpowiedzi. Jest to zgodne z opisem w3c * Serwer spełnił żądanie, ale nie musi zwracać ciała-jednostki, i może chcieć zwrócić zaktualizowaną metainformację. * 1 Zamieszanie, moim zdaniem, jest spowodowane wpisem w Wikipedii z informacją 204 Brak treści - Serwer pomyślnie przetworzył żądanie, ale nie zwraca żadnej treści. Zwykle używany jako odpowiedź na pomyślne żądanie usunięcia .Ostatnie zdanie jest wysoce dyskusyjne. Rozważ sytuację bez tego zdania, a rozwiązanie jest łatwe - po prostu wyślij 204, jeśli jednostka nie istnieje. Istnieje nawet argument za zwróceniem 204 zamiast 404, żądanie zostało przetworzone i żadna treść nie została zwrócona! Pamiętaj jednak, że 204 nie pozwalają na treść w treści odpowiedzi

Źródła

http://en.wikipedia.org/wiki/List_of_HTTP_status_codes 1. http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

Jan
źródło
9

To bardzo stary post, ale napotkałem podobny problem i chciałbym podzielić się z wami moimi doświadczeniami.

Buduję architekturę mikrousług z pozostałymi interfejsami API. Mam niektóre usługi GET odpoczynku, zbierają dane z systemu zaplecza na podstawie parametrów żądania.

Postępowałem zgodnie z pozostałymi dokumentami projektowymi API i odesłałem HTTP 404 z doskonałym komunikatem o błędzie JSON do klienta, gdy nie było danych, które pasowałyby do warunków zapytania (na przykład wybrano rekord zero).

Gdy nie było żadnych danych do wysłania z powrotem do klienta, przygotowałem doskonały komunikat JSON z wewnętrznym kodem błędu itp., Aby poinformować klienta o przyczynie „Nie znaleziono”, i został on odesłany z powrotem do klienta z HTTP 404. działa w porządku.

Później utworzyłem klasę spoczynkowego interfejsu API, która jest łatwym pomocnikiem do ukrycia kodu związanego z komunikacją HTTP i korzystałem z tego pomocnika przez cały czas, gdy dzwoniłem do pozostałych interfejsów API z mojego kodu.

ALE musiałem pisać mylący dodatkowy kod tylko dlatego, że HTTP 404 miał dwie różne funkcje:

  • prawdziwy HTTP 404, gdy reszta interfejsu API nie jest dostępna w danym adresie URL, jest on generowany przez serwer aplikacji lub serwer WWW, na którym działa reszta aplikacji interfejsu API
  • klient również zwraca HTTP 404, gdy nie ma danych w bazie danych w zależności od warunków zapytania.

Ważne: mój moduł obsługi błędów spoczynkowego interfejsu API przechwytuje wszystkie wyjątki pojawiające się w usłudze zaplecza, co oznacza, że ​​w przypadku każdego błędu mój spoczynkowy interfejs API zawsze zwraca doskonały komunikat JSON ze szczegółami komunikatu.

To jest pierwsza wersja metody pomocniczej mojego klienta, która obsługuje dwie różne odpowiedzi HTTP 404:

public static String getSomething(final String uuid) {
    String serviceUrl = getServiceUrl();
    String path = "user/" + , uuid);
    String requestUrl = serviceUrl + path;
    String httpMethod = "GET";

    Response response = client
            .target(serviceUrl)
            .path(path)
            .request(ExtendedMediaType.APPLICATION_UTF8)
            .get();

    if (response.getStatus() == Response.Status.OK.getStatusCode()) {
        // HTTP 200
        return response.readEntity(String.class);
    } else {
        // confusing code comes here just because
        // I need to decide the type of HTTP 404...

        // trying to parse response body
        try {
            String responseBody = response.readEntity(String.class);
            ObjectMapper mapper = new ObjectMapper();
            ErrorInfo errorInfo = mapper.readValue(responseBody, ErrorInfo.class);

            // re-throw the original exception
            throw new MyException(errorInfo);

        } catch (IOException e) {
            // this is a real HTTP 404
            throw new ServiceUnavailableError(response, requestUrl, httpMethod);
        }

    // this exception will never be thrown
    throw new Exception("UNEXPECTED ERRORS, BETTER IF YOU DO NOT SEE IT IN THE LOG");
}

ALE , ponieważ mój klient Java lub JavaScript może odbierać dwa rodzaje HTTP 404, muszę jakoś sprawdzić treść odpowiedzi w przypadku HTTP 404. Jeśli mogę przeanalizować treść odpowiedzi, to jestem pewien, że otrzymałem odpowiedź tam, gdzie była brak danych do odesłania do klienta.

Jeśli nie jestem w stanie przeanalizować odpowiedzi, oznacza to, że odzyskałem prawdziwy HTTP 404 z serwera WWW (nie z pozostałej aplikacji API).

Jest to tak mylące, że aplikacja kliencka musi zawsze wykonać dodatkowe parsowanie, aby sprawdzić prawdziwy powód HTTP 404.

Szczerze mówiąc nie podoba mi się to rozwiązanie. Jest to mylące, musi cały czas dodawać klientom dodatkowe bzdury.

Zamiast więc używać HTTP 404 w tych dwóch różnych scenariuszach, zdecydowałem, że wykonam następujące czynności:

  • Nie używam już HTTP 404 jako kodu HTTP odpowiedzi w mojej pozostałej aplikacji.
  • Zamierzam użyć HTTP 204 (bez zawartości) zamiast HTTP 404.

W takim przypadku kod klienta może być bardziej elegancki:

public static String getString(final String processId, final String key) {
    String serviceUrl = getServiceUrl();
    String path = String.format("key/%s", key);
    String requestUrl = serviceUrl + path;
    String httpMethod = "GET";

    log(requestUrl);

    Response response = client
            .target(serviceUrl)
            .path(path)
            .request(ExtendedMediaType.APPLICATION_JSON_UTF8)
            .header(CustomHttpHeader.PROCESS_ID, processId)
            .get();

    if (response.getStatus() == Response.Status.OK.getStatusCode()) {
        return response.readEntity(String.class);
    } else {
        String body = response.readEntity(String.class);
        ObjectMapper mapper = new ObjectMapper();
        ErrorInfo errorInfo = mapper.readValue(body, ErrorInfo.class);
        throw new MyException(errorInfo);
    }

    throw new AnyServerError(response, requestUrl, httpMethod);
}

Myślę, że to lepiej radzi sobie z tym problemem.

Jeśli masz jakieś lepsze rozwiązanie, udostępnij je nam.

zappee
źródło
Metody Apache HttpClient nie zgłaszają wyjątku dla odpowiedzi 204. Jeśli Twój klient odpowiada tylko na obiekty biznesowe (modele), to zwróci wartość null. Działa, ale trudno jest użytkownikom końcowym wykryć sytuację 204.
chrisinmtown
Wszystko dobrze, ale pytanie brzmi: jak często piszesz, jeśli oświadczenia dla 404? A co by to było? Z drugiej strony, jeśli wykonasz wywołanie interfejsu API, który określa możliwe 404 z błędem json w środku, możesz obsłużyć to tylko w tym przypadku.
Maks
zappee Zgadzam się z tobą. Mam usługę, która zwraca dane. Ale są sytuacje, w których dane są uszkodzone. URL żądania jest poprawny (więc nie 404), ale tak naprawdę nie jest to błąd logiczny (500), więc 204 wydaje się najbardziej odpowiedni. To naprawdę denerwujące z powodu braku treści wiadomości. Chociaż prawdopodobnie mógłbym wstawić powód do nagłówka.
cs94njw
6

Ten stary, ale doskonały artykuł… http://www.infoq.com/articles/webber-rest-workflow mówi o tym ...

404 Nie znaleziono - usługa jest zbyt leniwa (lub bezpieczna), aby dać nam prawdziwy powód niepowodzenia naszej prośby, ale bez względu na powód musimy sobie z tym poradzić.

Michael Dausmann
źródło
1

Uniform Resource Identifier to unikalny wskaźnik do zasobu. Źle sformatowany identyfikator URI nie wskazuje zasobu, dlatego wykonanie GET na nim nie zwróci zasobu. 404 oznacza, że ​​serwer nie znalazł niczego pasującego do URI żądania. Jeśli podasz niewłaściwy lub zły identyfikator URI, to jest to twój problem i powód, dla którego nie dotarłeś do zasobu, czy to strony HTML, czy IMG.

pozywanie
źródło
0

W tym scenariuszu HTTP 404 jest kodem odpowiedzi dla interfejsu API REST, takim jak jednostka nieprzetworzona 400, 401, 404, 422

użyj obsługi wyjątków, aby sprawdzić pełny komunikat wyjątku.

try{
  // call the rest api
} catch(RestClientException e) {
     //process exception
     if(e instanceof HttpStatusCodeException){
        String responseText=((HttpStatusCodeException)e).getResponseBodyAsString();
         //now you have the response, construct json from it, and extract the errors
         System.out.println("Exception :" +responseText);
     }

}

Ten blok wyjątków zapewnia właściwy komunikat generowany przez interfejs API REST

Venkata Naresh Babu
źródło