Jestem zainteresowany udostępnieniem bezpośredniego interfejsu REST do kolekcji dokumentów JSON (pomyśl o CouchDB lub Persevere ). Problem, z którym się spotykam, polega na tym, jak obsłużyć GET
operację na katalogu głównym kolekcji, jeśli kolekcja jest duża.
Jako przykład udaję, że ujawniam Questions
tabelę StackOverflow, w której każdy wiersz jest ujawniony jako dokument (niekoniecznie musi istnieć taka tabela, tylko konkretny przykład sporej kolekcji „dokumentów”). Kolekcja będzie udostępniona na /db/questions
ze zwykłymi CRUD API GET /db/questions/XXX
, PUT /db/questions/XXX
, POST /db/questions
jest w grze. Standardowym sposobem uzyskania całej kolekcji jest, GET /db/questions
ale jeśli naiwnie zrzuci to każdy wiersz jako obiekt JSON, otrzymasz dość spore pobieranie i dużo pracy ze strony serwera.
Rozwiązaniem jest oczywiście stronicowanie. Dojo rozwiązało ten problem w swoim JsonRestStore za pomocą sprytnego rozszerzenia zgodnego z RFC2616, używającego Range
nagłówka z niestandardową jednostką zasięgu items
. Wynikiem jest a, 206 Partial Content
który zwraca tylko żądany zakres. Zaletą tego podejścia w porównaniu z parametrem zapytania jest to, że pozostawia on ciąg zapytania dla ... zapytań (np. GET /db/questions/?score>200
Lub coś takiego, i tak, które byłyby zakodowane %3E
).
To podejście całkowicie pokrywa pożądane przeze mnie zachowanie. Problem polega na tym, że RFC 2616 określa, że w odpowiedzi 206 (moje wyróżnienie):
Żądanie muszą obejmować pole nagłówka Range ( rozdział 14.35 ) wskazując pożądany zakres i mogą mieć włączone pole nagłówka If-Range ( rozdział 14.27 ) do przeprowadzenia żądania warunkowe.
Ma to sens w kontekście standardowego użycia nagłówka, ale stanowi problem, ponieważ chciałbym, aby odpowiedź 206 była domyślna do obsługi naiwnych klientów / przypadkowych ludzi.
Szczegółowo przejrzałem RFC, szukając rozwiązania, ale nie byłem zadowolony z moich rozwiązań i jestem zainteresowany podejściem SO do problemu.
Pomysły, które miałem:
- Wróć
200
zContent-Range
nagłówkiem! - Nie sądzę, żeby to było złe, ale wolałbym bardziej oczywistym wskaźnikiem, że odpowiedź jest tylko Częściową Treścią. - Powrót
400 Range Required
- nie ma specjalnego kodu odpowiedzi 400 dla wymaganych nagłówków, więc domyślny błąd musi być używany i odczytywany ręcznie. Utrudnia to również eksplorację za pomocą przeglądarki internetowej (lub innego klienta, takiego jak Resty). - Użyj parametru zapytania - podejście standardowe, ale mam nadzieję, że zezwolę na zapytania a la Wytrwałość, a to przecina przestrzeń nazw zapytania.
- Po prostu wróć
206
! - Myślę, że większość klientów by się nie wystraszyła, ale wolałbym nie występować przeciwko MUST w RFC - Rozszerz specyfikację! Return
266 Partial Content
- zachowuje się dokładnie jak 206, ale jest odpowiedzią na żądanie, które NIE MOŻE zawieraćRange
nagłówka. Wydaje mi się, że 266 jest na tyle wysokie, że nie powinienem napotkać problemów z kolizją i ma to dla mnie sens, ale nie jestem pewien, czy jest to uważane za tabu, czy nie.
Myślę, że jest to dość powszechny problem i chciałbym zobaczyć, jak robi się to w sposób de facto, więc ani ja, ani ktoś inny nie wymyślamy na nowo koła.
Jaki jest najlepszy sposób ujawnienia pełnej kolekcji za pośrednictwem protokołu HTTP, gdy kolekcja jest duża?
źródło
Range = "Range" ":" ranges-specifier
gdzie ta ostatnia w tools.ietf.org/html/rfc2616#section-14.35.1 jest opisana jedynie jako „specyfikator-zakresów bajtów”, który musi zaczynać się od „jednostki-bajtów”, która jest zdefiniowana jako ciąg znaków „bajty ”.Content-Range
Nagłówek odnosi się do ciała (może być używany z prośbą podczas przesyłania dużych plików itp, lub do odpowiedzi podczas pobierania).Range
Header służy do żądania określonego zakresu. Należy odpowiedzieć,206
kiedyRange
nagłówek został zawarty w żądaniu. Jeśli tak nie jest, odpowiedź może nadal zawieraćContent-Range
nagłówek, ale kod odpowiedzi powinien być200
. Ten nagłówek wydaje się być idealny do stronicowania.Odpowiedzi:
Mam przeczucie, że rozszerzenia zakresu HTTP nie są zaprojektowane do twojego przypadku użycia, więc nie powinieneś próbować. Częściowa odpowiedź oznacza
206
i206
należy ją wysłać tylko wtedy, gdy klient o to poprosił.Możesz rozważyć inne podejście, takie jak użycie w Atom (gdzie reprezentacja według projektu może być częściowa i jest zwracana ze statusem
200
i potencjalnie linkami stronicowania). Zobacz RFC 4287 i RFC 5005 .źródło
items
jednostki zakresu, zwraca pełną odpowiedź. Znam Atom, ale nie jest to ogólne rozwiązanie stronicowania Rest. To nie jest rozwiązanie dla pojedynczego przypadku, a raczej to, jakie powinno być rozwiązanie ogólne. Nie wszystkie dokumenty / zbiory pasują do modelu Atom i nie ma powodu, aby go wymuszać, chyba że jest to wymagane.Range
iContent-Range
służy do celów stronicowania.Nie zgadzam się z niektórymi z was. Od tygodni pracuję nad tą funkcją dla mojej usługi REST. To, co ostatecznie zrobiłem, jest naprawdę proste. Moje rozwiązanie ma sens tylko dla tego, co ludzie REST nazywają kolekcją.
Klient MUSI dołączyć nagłówek „Zakres”, aby wskazać, której części kolekcji potrzebuje, lub w inny sposób być gotowym do obsługi zbyt dużego błędu 413 ŻĄDANA JEDNOSTKA, gdy żądana kolekcja jest zbyt duża, aby można ją było pobrać w pojedynczym ruchu w obie strony.
Serwer wysyła odpowiedź 206 PARTIAL CONTENT, z nagłówkiem Content-Range określającym, która część zasobu została wysłana oraz z nagłówkiem ETag w celu zidentyfikowania bieżącej wersji kolekcji. Zwykle używam ETag podobnego do Facebooka {last_modification_timestamp} - {resource_id} i uważam, że ETag kolekcji to ten z ostatnio zmodyfikowanego zasobu, który zawiera.
Aby zażądać określonej części kolekcji, klient MUSI użyć nagłówka „Range” i wypełnić nagłówek „If-Match” tagiem ET kolekcji uzyskanym z wcześniej wykonanych żądań nabycia innych części tej samej kolekcji. Serwer może zatem sprawdzić, czy kolekcja nie uległa zmianie przed wysłaniem żądanej części. Jeśli istnieje nowsza wersja, zwracana jest odpowiedź 412 PRECONDITION FAILED, aby zaprosić klienta do pobrania kolekcji od podstaw. Jest to konieczne, ponieważ może to oznaczać, że niektóre zasoby mogły zostać dodane lub usunięte przed lub po aktualnie żądanej części.
Używam ETag / If-Match w połączeniu z Last-Modified / If-Unmodified-Since, aby zoptymalizować pamięć podręczną. Przeglądarki i serwery proxy mogą polegać na jednym lub obu z nich w zakresie algorytmów buforowania.
Uważam, że adres URL powinien być czysty, chyba że zawiera zapytanie wyszukiwania / filtru. Jeśli się nad tym zastanowić, wyszukiwanie to nic innego jak częściowy widok zbioru. Zamiast adresów URL samochody / wyszukiwanie? Q = BMW, powinniśmy zobaczyć więcej samochodów? Producent = BMW.
źródło
If-Unmodified-Since
, co odpowiada wariantowi E-TagIf-Match
, a nieIf-Modified-Since
. To powiedziawszy, możesz również rozważyć usunięcie tego ograniczenia, w zależności od przypadku użycia. Załóżmy, że masz kolekcję, która rośnie tylko z góry (jak niektóre kolekcje w stylu „najpierw najnowsze”). Najgorsze, co może się zdarzyć, jeśli kolekcja ta zmieni się między żądaniami, to fakt, że użytkownik przeglądający kolekcję widzi wpisy dwukrotnie. (Co samo w sobie jest również przydatną informacją: informuje użytkownika, że kolekcja się zmieniła)413
. Jest to kod błędu, który oznacza, że klient wysyła coś, czego serwer odmawia przyjęcia ze względu na rozmiar. Nie na odwrót! Zobacz tools.ietf.org/html/rfc7231#section-6.5.11 (zwróć uwagę, że jest napisane żądanie ładunku. Brak odpowiedzi )!Nadal można wrócić
Accept-Ranges
iContent-Ranges
z200
kodem odpowiedzi. Te dwa nagłówki odpowiedzi zapewniają wystarczającą ilość informacji, aby wywnioskować te same informacje, które206
jawnie dostarcza kod odpowiedzi.Range
Użyłbym do paginacji i po prostu zwróciłbym a200
dla zwykłegoGET
.Jest to w 100% RESTful i nie utrudnia przeglądania.
Edycja: napisałem post na blogu na ten temat: http://otac0n.com/blog/2012/11/21/range-header-i-choose-you.html
źródło
Jeśli jest więcej niż jedna strona odpowiedzi i nie chcesz oferować całej kolekcji naraz, czy to oznacza, że istnieje wiele możliwości wyboru?
Na żądanie do
/db/questions
, wróć300 Multiple Choices
zLink
nagłówkami, które określają, jak dostać się do każdej strony, a także do obiektu JSON lub strony HTML z listą adresów URL.Będziesz mieć jeden
Link
nagłówek dla każdej strony wyników (pusty ciąg oznacza aktualny adres URL, a adres URL jest taki sam dla każdej strony, tylko dostępny z różnymi zakresami), a relacja jest definiowana jako niestandardowa zgodnie z nadchodzącąLink
specyfikacją . Ten związek wyjaśniałby twój zwyczaj266
lub naruszenie206
. Te nagłówki są twoją wersją do odczytu maszynowego, ponieważ wszystkie twoje przykłady i tak wymagają zrozumienia klienta.(Jeśli trzymasz się trasy „zakres”, uważam
2xx
, że najlepszym zachowaniem byłby twój własny kod powrotu, tak jak go opisałeś. Oczekuje się, że zrobisz to dla swoich aplikacji i takich [„Kody stanu HTTP są rozszerzalne. „] i masz dobre powody).300 Multiple Choices
mówi, że POWINIENEŚ również udostępnić treść, w której agent użytkownika może wybierać. Jeśli klient rozumie, powinien użyćLink
nagłówków. Jeśli jest to użytkownik przeglądający ręcznie, być może strona HTML z linkami do specjalnego, „stronicowanego” zasobu głównego, który może obsłużyć renderowanie tej strony na podstawie adresu URL?/humanpage/1/db/questions
czy coś takiego ohydnego?Komentarze do postu Richarda Levasseura przypominają mi o dodatkowej opcji:
Accept
nagłówku (sekcja 14.1). Kiedy pojawiła się specyfikacja oEmbed, zastanawiałem się, dlaczego nie została wykonana w całości przy użyciu protokołu HTTP, i napisałem alternatywę, używając ich.Zachowaj
300 Multiple Choices
,Link
nagłówki i stronę HTML dla początkowego naiwnego HTTPGET
, ale zamiast używać zakresów, użyj nowej relacji stronicowania, aby zdefiniować użycieAccept
nagłówka. Twoje kolejne żądanie HTTP może wyglądać następująco:Accept
Nagłówka pozwala określić akceptowalny typ zawartości (zwrot JSON), a także parametry rozszerzalne dla danego typu (numer strony). Riffując moje notatki z mojego wpisu oEmbed (nie mogę tutaj utworzyć linku, wymienię to w moim profilu), możesz być bardzo wyraźny i podać tutaj wersję specyfikacji / relacji na wypadek, gdybyś musiał przedefiniować znaczeniepage
parametru w przyszłości.źródło
Edytować:
Po dłuższym przemyśleniu jestem skłonny zgodzić się, że nagłówki zakresów nie są odpowiednie do paginacji. Logika jest taka, że nagłówek Range jest przeznaczony do odpowiedzi serwera, a nie aplikacji. Jeśli podałeś 100 megabajtów wyników, ale serwer (lub klient) mógł przetwarzać tylko 1 megabajt naraz, cóż, do tego służy nagłówek Range.
Jestem też zdania, że podzbiór zasobów jest własnym zasobem (podobnie jak algebra relacyjna), więc zasługuje na przedstawienie w adresie URL.
Zasadniczo wycofuję swoją pierwotną odpowiedź (poniżej) dotyczącą używania nagłówka.
Myślę, że odpowiedziałeś mniej więcej na swoje pytanie - zwróć 200 lub 206 z zakresem zawartości i opcjonalnie użyj parametru zapytania. Sniperowałbym agenta użytkownika i typ zawartości i, w zależności od nich, sprawdzał parametr zapytania. W przeciwnym razie wymagaj nagłówków zakresów.
Zasadniczo masz sprzeczne cele - pozwól ludziom używać przeglądarki do przeglądania (co nie pozwala łatwo na niestandardowe nagłówki) lub zmusić ludzi do korzystania ze specjalnego klienta, który może ustawiać nagłówki (co nie pozwala im odkrywać).
Możesz po prostu dostarczyć im specjalnego klienta w zależności od żądania - jeśli wygląda jak zwykła przeglądarka, wyślij małą aplikację Ajax, która renderuje stronę i ustawia niezbędne nagłówki.
Oczywiście toczy się również debata na temat tego, czy adres URL powinien zawierać wszystkie niezbędne informacje o tego rodzaju rzeczach. Określanie zakresu za pomocą nagłówków może być przez niektórych uważane za „niespokojne”.
Nawiasem mówiąc, byłoby miło, gdyby serwery mogły odpowiedzieć nagłówkiem „Can-Specify: Header1, header2”, a przeglądarki internetowe przedstawiałyby interfejs użytkownika, aby użytkownicy mogli wpisać wartości, jeśli chcą.
źródło
Możesz rozważyć użycie modelu podobnego do protokołu Atom Feed Protocol, ponieważ ma on rozsądny model zbiorów HTTP i sposób ich manipulowania (gdzie szaleństwo oznacza WebDAV).
Istnieje protokół publikowania Atom, który definiuje model kolekcji i operacje REST, a ponadto możesz użyć RFC 5005 - stronicowanie i archiwizowanie kanałów, aby przeglądać duże kolekcje.
Przełączenie z Atom XML na zawartość JSON nie powinno mieć wpływu na pomysł.
źródło
Myślę, że prawdziwym problemem jest to, że w specyfikacji nie ma nic, co mówi nam, jak wykonać automatyczne przekierowania w obliczu 413 - Requested Entity Too Large.
Ostatnio borykałem się z tym samym problemem i szukałem inspiracji w książce RESTful Web Services . Osobiście uważam, że 206 nie jest odpowiednie ze względu na wymagania dotyczące nagłówka. Moje myśli również doprowadziły mnie do 300, ale pomyślałem, że jest to bardziej dla różnych typów mime, więc poszukałem tego, co Richardson i Ruby mieli do powiedzenia na ten temat w Dodatku B, strona 377. Sugerują, że serwer wybiera po prostu preferowaną reprezentacji i odeślij ją z 200, zasadniczo ignorując pogląd, że powinna to być 300.
To również kłóci się z pojęciem powiązań z następnymi zasobami, które mamy z atomu. Rozwiązanie, które zaimplementowałem, polegało na dodaniu kluczy „next” i „previous” do mapy json, którą odesłałem i skończyło się z tym.
Później zacząłem myśleć, że być może należy wysłać 307 - Tymczasowe przekierowanie do odsyłacza, który wyglądałby na coś takiego jak / db / questions / 1,25 - co pozostawia oryginalny URI jako kanoniczną nazwę zasobu, ale prowadzi do odpowiednio nazwany podrzędny zasób. To jest zachowanie, które chciałbym zobaczyć w przypadku 413, ale 307 wydaje się dobrym kompromisem. Jednak nie próbowałem jeszcze tego w kodzie. Jeszcze lepsze byłoby przekierowanie do adresu URL zawierającego rzeczywiste identyfikatory ostatnio zadawanych pytań. Na przykład, jeśli każde pytanie ma identyfikator całkowity, aw systemie jest 100 pytań i chcesz wyświetlić dziesięć ostatnich, żądania do / db / pytania powinny mieć 307 do / db / questions / 100,91
To bardzo dobre pytanie, dziękuję, że je zadałeś. Potwierdziłeś mi, że nie jestem wariatem, że spędziłem dni na rozmyślaniu o tym.
źródło
Możesz wykryć
Range
nagłówek i naśladować Dojo, jeśli jest obecny, i naśladować Atom, jeśli nie jest. Wydaje mi się, że to zgrabnie dzieli przypadki użycia. Jeśli odpowiadasz na zapytanie REST z aplikacji, spodziewasz się, że zostanie ono sformatowane za pomocąRange
nagłówka. Jeśli odpowiadasz zwykłej przeglądarce, to jeśli zwrócisz linki stronicowania, narzędzie zapewni łatwy sposób przeglądania kolekcji.źródło
Jednym z największych problemów z nagłówkami zakresów jest to, że wiele korporacyjnych serwerów proxy je odfiltrowuje. Radziłbym zamiast tego użyć parametru zapytania.
źródło
Wraz z publikacją rfc723x , niezarejestrowane jednostki zakres idą przeciwko wyraźnego zalecenia w spec . Rozważmy rfc7233 (przestarzałe rfc2616):
„ Nowe jednostki zakresu powinny być zarejestrowane w IANA ” (wraz z odniesieniem do rejestru jednostek zakresu HTTP ).
źródło
Wydaje mi się, że najlepszym sposobem na to jest uwzględnienie zakresu jako parametrów zapytania. np. GET / db / questions /? date> mindate & date <maxdate . Po GET do / db / questions / bez parametrów zapytania, zwróć 303 z Location: / db / questions /? Query-parameters-to-retrieve-the-default-page . Następnie podaj inny adres URL, przez który ktoś korzysta z Twojego interfejsu API, aby uzyskać statystyki dotyczące kolekcji (np. Jakich parametrów zapytania użyć, jeśli chce mieć całą kolekcję);
źródło
Chociaż możliwe jest użycie w tym celu nagłówka Range, nie sądzę, aby taki był zamiar. Wygląda na to, że został zaprojektowany do obsługi niestabilnych połączeń, a także do ograniczania danych (więc klient może zażądać części żądania, jeśli czegoś brakowało lub rozmiar był zbyt duży, aby go przetworzyć). Włamujesz paginację do czegoś, co prawdopodobnie jest używane do innych celów w warstwie komunikacyjnej. „Właściwym” sposobem obsługi paginacji są zwracane typy. Zamiast zwracać obiekt pytania, powinieneś zamiast tego zwrócić nowy typ.
Więc jeśli pytania są takie:
<questions> <question index=1></question> <question index=2></question> ... </questions>
Nowy typ może wyglądać mniej więcej tak:
<questionPage> <startIndex>50</startIndex> <returnedCount>10</returnedCount> <totalCount>1203</totalCount> <questions> <question index=50></question> <question index=51></question> .. </questions> <questionPage>
Oczywiście masz kontrolę nad typami multimediów, dzięki czemu możesz tworzyć „strony” w formacie odpowiadającym Twoim potrzebom. Jeśli make jest czymś ogólnym, możesz mieć pojedynczy parser na kliencie do obsługi stronicowania tak samo dla wszystkich typów. Myślę, że jest to bardziej zgodne z duchem specyfikacji HTTP, a nie fałszowaniem parametru Range dla czegoś innego.
źródło