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:
- Użytkownik wypełnia formularz
- Klient publikuje dane w interfejsie API
- API zwraca 202 Zaakceptowano
- Klient przekierowuje użytkownika na unikalny adres URL tego żądania (
/results/{request_id}
) - ~ ostatecznie ~
- 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 GET
proś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) switch
o response.data.status
ustalenie, czy żądanie zostało zakończone.
Czy to powinienem robić? Czy jest lepsza alternatywa? To po prostu wydaje mi się tak Web 1.0.
POST
proś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ł.Odpowiedzi:
Zaakceptowano HTTP 202 (HTTP / 1.1)
Szukasz
HTTP 202 Accepted
statusu. Zobacz RFC 2616 :Przetwarzanie HTTP 102 (WebDAV)
RFC 2518 sugeruje użycie
HTTP 102 Processing
:ale ma zastrzeżenie:
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 Accepted
i 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/result
odpowiadają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:
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 Accepted
treś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:
co prowadzi do
HTTP 200
.Co się stanie, jeśli klient zapyta o ten identyfikator URI przed otrzymaniem powiadomienia? Cóż, serwer odpowie,
HTTP 404
ponieważ 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:
co dałoby odpowiedź podobną do tej:
Wykonanie żądania w kółko pokaże postęp, dopóki:
Ważne jest, aby wprowadzić rozróżnienie między tymi trzema rodzajami żądań:
POST /video/convert
ustawia w kolejce zadanie. Powinien zostać wywołany tylko raz: ponowne wywołanie go w kolejce spowoduje dodatkowe zadanie.GET /video/download/123
dotyczy 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/123
dotyczy 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.źródło
GET
? To z pewnością właściwy wybór dla inicjałuPOST
, dlatego go używam. Ale semantycznie podejrzane wydajeGET
się powiedzenie „zaakceptowane”, gdy nie akceptuje niczego z tej konkretnej prośby.POST
zwiększam zadanie do kolejkowania, a następnieGET
wyniki, 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ą.GET
część, 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.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ś
GET
zasobem, 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. CzyPOST
od klienta do serwera przesłano zasób na serwer, gdzie serwer dał mu adres URL, który klient może wyświetlić? Tak? To jest201 Created
odpowiedź.Ź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 Type
zasobu. 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ć
źródło
Uznałem, że sugestie z tego bloga są uzasadnione: REST i długoterminowe zadania .
Podsumowując:
źródło
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 :
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.
źródło