Kod stanu HTTP dla „Still Processing”

47

Buduję RESTful API, który obsługuje kolejkowanie długotrwałych zadań do ewentualnej obsługi.

Typowy przepływ pracy dla tego interfejsu API to:

  1. Użytkownik wypełnia formularz
  2. Klient publikuje dane w interfejsie API
  3. API zwraca 202 Zaakceptowano
  4. Klient przekierowuje użytkownika na unikalny adres URL tego żądania ( /results/{request_id})
  5. ~ ostatecznie ~
  6. Klient ponownie odwiedza adres URL i widzi wyniki na tej stronie.

Mój problem dotyczy kroku 6. Za każdym razem, gdy użytkownik odwiedza stronę, przesyłam żądanie do mojego API ( GET /api/results/{request_id}). Idealnie byłoby, gdyby zadanie zostało już wykonane i zwróciłbym 200 OK z wynikami ich zadania.

Ale użytkownicy są natarczywi i oczekuję wielu nadgorliwych odświeżeń, gdy wynik nie jest jeszcze zakończony.

Jaka jest moja najlepsza opcja dla kodu statusu wskazującego, że:

  • ta prośba istnieje,
  • jeszcze się nie skończyło,
  • ale też nie zawiodło.

Nie spodziewam się, że jeden kod przekaże to wszystko, ale chciałbym czegoś, co pozwoli mi przekazywać metadane zamiast tego, aby klient oczekiwał treści.

Zwrócenie wartości 202 może mieć sens, ponieważ nie miałoby to tutaj innego znaczenia: jest to GETprośba, więc prawdopodobnie nic nie jest „akceptowane”. Czy byłby to rozsądny wybór?

Oczywistą alternatywą dla tego wszystkiego - która działa, ale pokonuje jeden cel kodów statusu - byłoby zawsze dołączanie metadanych:

200 OK

{
    status: "complete",
    data: {
        foo: "123"
    }
}

...lub...

200 OK

{
    status: "pending"
}

Następnie po stronie klienta, chciałbym (westchnienie) switcho response.data.statusustalenie, czy żądanie zostało zakończone.

Czy to powinienem robić? Czy jest lepsza alternatywa? To po prostu wydaje mi się tak Web 1.0.

Matthew Haugen
źródło
1
Czy kody 1xx nie są stworzone właśnie do tego celu?
Andy
@Andy Patrzyłem na 102, ale to dotyczy WebDAV. Poza tym nie ... Są one głównie przeznaczone do komunikacji w transporcie. Przydatny w przełączaniu do gniazd sieciowych i tym podobnych.
Matthew Haugen
O jakim opóźnieniu mówisz? 10 sekund? A może 6 godzin? Jeśli opóźnienia są krótkie i zwykle dotyczą tej samej wizyty w przeglądarce, możesz przeprowadzać długie ankiety lub gniazda sieciowe zamiast okresowego.
GrandmasterB,
@GrandmasterB Potencjalnie są godziny. Nie jestem odpowiedzialny za samo przetwarzanie zadania, więc nie mam naprawdę dobrej oceny, ale to potrwa. W przeciwnym razie zostawię pierwszą POSTprośbę otwartą. Głównym problemem związanym z długim odpytywaniem lub gniazdami internetowymi jest to, że użytkownik może zamknąć przeglądarkę i wrócić. Mógłbym je ponownie otworzyć w tym czasie (i tak właśnie robię), ale wydaje się, że czystsze jest posiadanie jednego interfejsu API do wywołania przed otwarciem tych gniazd, ponieważ jest to przypadek skrajny, aby ten problem się pojawił.
Matthew Haugen,

Odpowiedzi:

48

Zaakceptowano HTTP 202 (HTTP / 1.1)

Szukasz HTTP 202 Acceptedstatusu. Zobacz RFC 2616 :

Żądanie zostało zaakceptowane do przetworzenia, ale przetwarzanie nie zostało zakończone.

Przetwarzanie HTTP 102 (WebDAV)

RFC 2518 sugeruje użycie HTTP 102 Processing:

Kod statusu 102 (Przetwarzanie) jest odpowiedzią tymczasową używaną do poinformowania klienta, że ​​serwer zaakceptował kompletne żądanie, ale jeszcze go nie zrealizował.

ale ma zastrzeżenie:

Serwer MUSI wysłać ostateczną odpowiedź po zakończeniu żądania.

Nie jestem pewien, jak interpretować ostatnie zdanie. Czy serwer powinien unikać wysyłania czegokolwiek podczas przetwarzania i odpowiadać dopiero po zakończeniu? A może wymusza zakończenie odpowiedzi dopiero po zakończeniu przetwarzania? Może to być przydatne, jeśli chcesz zgłosić postępy. Wyślij HTTP 102 i opróżnij bajt po bajcie (lub linia po linii).

Na przykład, dla długiego, ale liniowego procesu, możesz wysłać sto kropek, spłukujących po każdym znaku. Jeśli strona klienta (na przykład aplikacja JavaScript) wie, że powinna spodziewać się dokładnie 100 znaków, może dopasować ją do paska postępu, aby wyświetlić ją użytkownikowi.

Kolejny przykład dotyczy procesu, który składa się z kilku nieliniowych kroków. Po każdym kroku możesz opróżnić komunikat dziennika, który ostatecznie zostanie wyświetlony użytkownikowi, aby użytkownik końcowy mógł wiedzieć, jak przebiega proces.

Problemy z postępującym spłukiwaniem

Pamiętaj, że chociaż ta technika ma swoje zalety, nie polecałbym jej . Jednym z powodów jest to, że zmusza połączenie do pozostania otwartym, co może zaszkodzić dostępności usług i nie skaluje się dobrze.

Lepszym rozwiązaniem jest udzielenie odpowiedzi HTTP 202 Acceptedi umożliwienie użytkownikowi skontaktowania się z Tobą później w celu ustalenia, czy przetwarzanie zostało zakończone (na przykład poprzez wielokrotne wywoływanie danego identyfikatora URI, na przykład /process/resultodpowiadającego komunikatem HTTP 404 Not Found lub HTTP 409 Conflict, aż do zakończenia procesu kończy się, a wynik jest gotowy) lub powiadom użytkownika o zakończeniu przetwarzania, jeśli będziesz w stanie oddzwonić do klienta na przykład za pośrednictwem usługi kolejki wiadomości ( przykład ) lub WebSockets.

Praktyczny przykład

Wyobraź sobie serwis internetowy, który konwertuje filmy. Punktem wejścia jest:

POST /video/convert

który pobiera plik wideo z żądania HTTP i robi z nim trochę magii. Wyobraźmy sobie, że magia wymaga dużego procesora, więc nie można tego zrobić w czasie rzeczywistym podczas przesyłania żądania. Oznacza to, że po przesłaniu pliku serwer odpowie z HTTP 202 Acceptedtreścią JSON, co oznacza: „Tak, mam twój film i pracuję nad nim; będzie gotowy gdzieś w przyszłości i będzie dostępny poprzez ID 123. ”

Klient ma możliwość zasubskrybowania kolejki komunikatów, aby otrzymywać powiadomienia o zakończeniu przetwarzania. Po zakończeniu klient może pobrać przetworzone wideo, przechodząc do:

GET /video/download/123

co prowadzi do HTTP 200.

Co się stanie, jeśli klient zapyta o ten identyfikator URI przed otrzymaniem powiadomienia? Cóż, serwer odpowie, HTTP 404ponieważ rzeczywiście wideo jeszcze nie istnieje. To może być obecnie przygotowane. Nigdy nie można o to poprosić. Może istnieć jakiś czas w przeszłości, a później zostanie usunięty. Liczy się tylko to, że powstałe wideo nie jest dostępne.

Co teraz, jeśli klientowi zależy nie tylko na końcowym filmie, ale także na postępie (co byłoby jeszcze ważniejsze, gdyby nie było usługi kolejki wiadomości lub podobnego mechanizmu)?

W takim przypadku możesz użyć innego punktu końcowego:

GET /video/status/123

co dałoby odpowiedź podobną do tej:

HTTP 200
{
    "id": 123,
    "status": "queued",
    "priority": 2,
    "progress-percent": 0,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Wykonanie żądania w kółko pokaże postęp, dopóki:

HTTP 200
{
    "id": 123,
    "status": "done",
    "progress-percent": 100,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Ważne jest, aby wprowadzić rozróżnienie między tymi trzema rodzajami żądań:

  • POST /video/convertustawia w kolejce zadanie. Powinien zostać wywołany tylko raz: ponowne wywołanie go w kolejce spowoduje dodatkowe zadanie.
  • GET /video/download/123dotyczy wyniku operacji: zasobem jest wideo. Przetwarzanie - czyli to, co wydarzyło się pod maską, aby przygotować rzeczywisty wynik przed żądaniem i niezależnie od żądania - nie ma tutaj znaczenia. Można go wywołać raz lub kilka razy.
  • GET /video/status/123dotyczy przetwarzania per se . Nic nie stoi w kolejce. Nie obchodzi go powstały film. Zasób jest sama obróbka. Można go wywołać raz lub kilka razy.
Arseni Mourzenko
źródło
1
Czy 202 ma jednak sens w odpowiedzi na GET? To z pewnością właściwy wybór dla inicjału POST, dlatego go używam. Ale semantycznie podejrzane wydaje GETsię powiedzenie „zaakceptowane”, gdy nie akceptuje niczego z tej konkretnej prośby.
Matthew Haugen
2
@MainMa Jak powiedziałem, POSTzwiększam zadanie do kolejkowania, a następnie GETwyniki, potencjalnie po zamknięciu sesji przez klienta. 404 to również coś, co rozważałem, ale wydaje się złe, ponieważ żądanie zostało znalezione, po prostu nie zostało ukończone. Oznaczałoby to, że nie znaleziono pracy w kolejce, co jest zupełnie inną sytuacją.
Matthew Haugen,
1
@MatthewHaugen: kiedy wykonujesz tę GETczęść, nie myśl o tym jako o niekompletnym żądaniu , ale jako o żądaniu uzyskania wyniku operacji . Na przykład, jeśli powiem, aby przekonwertować wideo i zajmuje to pięć minut, prośba o konwersję wideo dwie minuty później powinna spowodować HTTP 404, ponieważ wideo po prostu jeszcze nie ma. Z drugiej strony, żądanie samego postępu operacji prawdopodobnie spowoduje HTTP 200 zawierający liczbę przekonwertowanych bajtów, prędkość itp.
Arseni Mourzenko 19.04.16
5
Kod stanu HTTP dla zasobu, który nie jest jeszcze dostępny, sugeruje zwrócenie odpowiedzi konfliktu 409 („Żądanie nie mogło zostać zakończone z powodu konfliktu z bieżącym stanem zasobu.”) Zamiast odpowiedzi 404, w przypadku gdy zasób nie istnieje, ponieważ jest w trakcie generowania.
Brian
1
@Brian Twój komentarz byłby rozsądną odpowiedzią na to pytanie. Chociaż odpowiedziałbym wtedy „jego kod jest dozwolony tylko w sytuacjach, w których oczekuje się, że użytkownik będzie w stanie rozwiązać konflikt i ponownie przesłać żądanie”, co w moim przypadku nie jest do końca prawdą, ale wydaje się, że mniej źle niż „nie znaleziono”. Część mnie pochyla się w kierunku 409 z przypiętym nagłówkiem Retry-After. Jedynym problemem jest to, że dziwne wydaje się zwracanie 409 za GET, ale mogę żyć z tą dziwnością - jest mało prawdopodobne, aby w przyszłości zdefiniowano inaczej.
Matthew Haugen,
5

Oczywistą alternatywą dla tego wszystkiego - która działa, ale pokonuje jeden cel kodów statusu - byłoby zawsze dołączanie metadanych:

To jest właściwa droga. Stan, w jakim znajdują się zasoby w odniesieniu do dziennika specyficznego dla domeny (inaczej logiki biznesowej), zależy od typu zawartości reprezentacji zasobu.

Istnieją dwie koncepcje różnic, które są różne. Jednym z nich jest status transferu stanu między klientem a serwerem zasobu, a drugim stan samego zasobu w jakimkolwiek kontekście domena biznesowa przyjmuje różne stany tego zasobu. Ten ostatni nie ma nic wspólnego z kodami stanu HTTP.

Pamiętaj, że kody stanu HTTP odpowiadają transferowi stanu między klientem a serwerem przetwarzanego zasobu, niezależnie od jakichkolwiek szczegółów tego zasobu. Gdy jesteś GETzasobem, twój klient prosi serwer o przedstawienie zasobu w obecnym stanie, w którym się znajduje. To może być zdjęcie ptaka, może to być dokument Word, może to być bieżąca temperatura zewnętrzna. Protokół HTTP nie ma znaczenia. Kod stanu HTTP odpowiada wynikowi tego żądania. Czy POSTod klienta do serwera przesłano zasób na serwer, gdzie serwer dał mu adres URL, który klient może wyświetlić? Tak? To jest 201 Createdodpowiedź.

Źródłem może być rezerwacja linii lotniczej, która jest obecnie w stanie „do sprawdzenia”. Lub może to być zamówienie zakupu produktu w stanie „zatwierdzonym”. Te stany są specyficzne dla domeny, a nie na tym polega protokół HTTP. Protokół HTTP zajmuje się przenoszeniem zasobów między klientem a serwerem.

Istotą REST i HTTP jest to, że protokoły nie zajmują się szczegółami zasobów. Jest to celowe, nie dotyczy problemów związanych z domeną, dzięki czemu można z niego korzystać bez konieczności posiadania wiedzy na temat problemów związanych z domeną. Nie interpretujesz co oznaczają kody stanu HTTP w różnych kontekstach (system rezerwacji linii lotniczych, system przetwarzania wyobrażeń, system zabezpieczeń wideo itp.).

Specyficzne dla domeny rzeczy są dla klienta i serwera, aby ustalić między sobą na podstawie Content Typezasobu. Protokół HTTP jest niezależny od tego.

Jeśli chodzi o sposób, w jaki klient odkryje, że zasób żądania zmienił stan, odpytywanie jest najlepszym rozwiązaniem, ponieważ utrzymuje kontrolę nad klientem i nie zakłada nieprzerwanego połączenia. Zwłaszcza jeśli minie potencjalnie godzina do zmiany stanu. Nawet jeśli powiedziałeś do diabła z REST, po prostu utrzymasz połączenie otwarte, utrzymywanie go otwarte przez wiele godzin i zakładanie, że nic nie pójdzie źle, byłoby złym pomysłem. Co jeśli użytkownik zamknie klienta lub sieć przestanie działać. Jeśli ziarnistość to godziny, klient może po prostu żądać stanu co kilka minut, aż żądanie zmieni się z „oczekujące” na „gotowe”.

Mam nadzieję, że pomoże to wyjaśnić

Cormac Mulhall
źródło
„Czy test POST z klienta na serwer przesłał zasób na serwer, gdzie serwer dał mu adres URL, który klient może wyświetlić? Tak? To jest odpowiedź utworzona przez 201”. 202 Zaakceptowano również jako odpowiedź na to, jeśli serwer nie może natychmiast podjąć działania w celu przetworzenia zasobu, co robi OP.
Andy
1
Chodzi o to, że serwer działa natychmiast. Natychmiast tworzy zasób z adresem URL. Po prostu stan zasobu to „Oczekuje” (lub coś w tym rodzaju). To jest stan domeny biznesowej. Jeśli chodzi o protokół HTTP, serwer działał, gdy tylko utworzył zasób i podał klientowi adres URL zasobu. Możesz zdobyć ten zasób. Samo żądanie POST nie jest w toku. To mam na myśli przez oddzielenie dwóch różnych domen pojęciowych. Gdyby klient wysyłał ogień i zapomniał, że żądanie POST nie było wykonywane przez godziny, wówczas zastosowanie miałaby 202.
Cormac Mulhall
Nikt nie dba o to, czy adres URL istnieje, ale nie można uzyskać danych reprezentowanych przez zasób, ponieważ jest on nadal przetwarzany. Może również NIE utworzyć adresu URL, dopóki nie będzie można go użyć do uzyskania filmu.
Andy
Zasób jest tworzony, jest w stanie „w toku”. To samo w sobie istotne dane. W pewnym momencie w przyszłości serwer może zmienić stan zasobów na „zakończony” (lub „nieudany”), ale jest to inna koncepcja niż zadanie „tworzenia zasobu” specyficzne dla domeny HTTP. Oczekiwanie może być całkowicie poprawnym stanem, w którym ma być zasób „Żądaj”, a klient oczywiście chce wiedzieć, że serwer utworzył zasób w tym stanie, ponieważ rezygnuje z proszenia serwera o utworzenie zasobu, aby wiedział, że odpytuje go, aby się dowiedzieć jeśli stan się zmienił.
Cormac Mulhall
4

Uznałem, że sugestie z tego bloga są uzasadnione: REST i długoterminowe zadania .

Podsumowując:

  1. Serwer zwraca kod „202 Zaakceptowano” z nagłówkiem „Lokalizacja” ustawionym na identyfikator URI, aby klient mógł sprawdzić status, np. „/ Queue / 12345”.
  2. Do czasu zakończenia przetwarzania serwer odpowiada na zapytania o status za pomocą „200 OK” i niektórych danych odpowiedzi pokazujących status zadania.
  3. Po zakończeniu przetwarzania serwer odpowiada na zapytania o status za pomocą „303 See Other” i „Location” zawierającego URI do końcowego wyniku.
Xiangming Hu
źródło
2

Kod stanu HTTP dla zasobu, który nie jest jeszcze dostępny, sugeruje zwrócenie odpowiedzi konfliktu 409 zamiast odpowiedzi 404, w przypadku gdy zasób nie istnieje, ponieważ jest w trakcie generowania.

Ze specyfikacji w3 :

10.4.10 409 Konflikt

Żądanie nie mogło zostać zakończone z powodu konfliktu z bieżącym stanem zasobu. Ten kod jest dozwolony tylko w sytuacjach, w których oczekuje się, że użytkownik będzie w stanie rozwiązać konflikt i ponownie przesłać żądanie. Ciało odpowiedzi POWINNO zawierać wystarczającą liczbę

informacje dla użytkownika w celu rozpoznania źródła konfliktu. Idealnie, jednostka odpowiedzi zawierałaby wystarczającą ilość informacji dla użytkownika lub agenta użytkownika, aby rozwiązać problem; może to jednak nie być możliwe i nie jest wymagane.

Konflikty najczęściej występują w odpowiedzi na żądanie PUT. Na przykład, jeśli użyto wersjonowania, a jednostka PUT zawierała zmiany w zasobie, które powodują konflikt z zasobami dokonanymi przez wcześniejsze żądanie (strony trzeciej), serwer może użyć odpowiedzi 409, aby wskazać, że nie może zrealizować żądania . W takim przypadku jednostka odpowiedzi prawdopodobnie zawierałaby listę różnic między dwiema wersjami w formacie zdefiniowanym przez typ zawartości odpowiedzi.

Jest to nieco niewygodne, ponieważ kod 409 jest „dozwolony tylko w sytuacjach, w których oczekuje się, że użytkownik będzie w stanie rozwiązać konflikt i ponownie przesłać żądanie”. Sugeruję, że treść odpowiedzi zawiera komunikat (być może w formacie odpowiedzi pasującym do reszty interfejsu API), na przykład: „Ten zasób jest obecnie generowany. Został zainicjowany o [TIME], a jego zakończenie szacuje się na [TIME]. Spróbuj ponownie później."

Zauważ, że sugerowałbym podejście 409 tylko wtedy, gdy wysoce prawdopodobne jest, że użytkownik, który żąda zasobu, jest także użytkownikiem, który zainicjował generowanie tego zasobu. Użytkownikom nie związanym z generowaniem zasobu błąd 404 byłby mniej mylący.

Brian
źródło
Wydaje się, że odcinek 409 jest tak naprawdę przeznaczony, co jest odpowiedzią na put.
Andy
@Andy: To prawda, ale tak samo jest z każdą inną alternatywą. Np. 202 naprawdę ma być odpowiedzią na żądanie, które zainicjowało przetwarzanie, a nie na żądanie, które zażądało wyników przetwarzania. Naprawdę najbardziej zgodna odpowiedź to 404, ponieważ nie znaleziono zasobu (ponieważ jeszcze nie istniał). Nic nie stoi na przeszkodzie, aby interfejs API udostępniał odpowiednie dane interfejsu API w odpowiedzi 404. Pamiętaj, że odpowiedzi 4xx / 5xx są irytujące; niektóre języki uruchamiają wyjątek, a nie tylko podają inny kod stanu.
Brian
2
Nie, szczególnie kilka ostatnich akapitów odpowiedzi MainMa. Oddzielne punkty końcowe, aby sprawdzić status żądania i uzyskać sam film. Status nie jest tym samym zasobem co film i powinien być możliwy do samodzielnego adresowania.
Andy,