Jak zaprojektować RESTful wyszukiwanie / filtrowanie? [Zamknięte]


456

Obecnie projektuję i wdrażam API RESTful w PHP. Jednak nie udało mi się wdrożyć mojego początkowego projektu.

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

Jak dotąd dość standardowy, prawda?

Mój problem dotyczy pierwszego GET /users. Zastanawiałem się nad przesłaniem parametrów w treści żądania w celu przefiltrowania listy. Jest tak, ponieważ chcę móc określić złożone filtry bez uzyskiwania bardzo długiego adresu URL, na przykład:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

Zamiast tego chciałem mieć coś takiego:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

który jest znacznie bardziej czytelny i daje ogromne możliwości ustawienia złożonych filtrów.

W każdym razie file_get_contents('php://input')nie zwrócił treści GETżądania. Próbowałem też http_get_request_body(), ale hostowanie współdzielone, którego używam, nie ma pecl_http. Nie jestem pewien, czy i tak by to pomogło.

Znalazłem to pytanie i zdałem sobie sprawę, że GET prawdopodobnie nie powinien mieć treści żądania. To było trochę nieprzekonujące, ale odradzali to.

Więc teraz nie jestem pewien, co robić. Jak zaprojektować funkcję wyszukiwania / filtrowania RESTful?

Myślę, że mógłbym użyć POST, ale to nie wydaje się bardzo ODPOCZNE.


7
możliwy duplikat projektu RESTful URL dla wyszukiwania
obecnie

60
Bądź ostrożny!!! Metoda GET musi być IDEMPOTENT i musi być „buforowalna”. Jeśli wysyłasz informacje w treści Jak system może buforować twoje zapytanie? HTTP umożliwia buforowanie żądania GET przy użyciu tylko adresu URL, a nie treści żądania. Na przykład te dwa żądania: example.com {test: „some”} example.com {anotherTest: „some2”} są uważane przez system pamięci podręcznej za takie same: oba mają dokładnie ten sam adres URL
jfcorugedo

15
Aby dodać, powinieneś POST do / users (kolekcja), a nie / user (pojedynczy użytkownik).
Mladen B.

1
Inną kwestią do rozważenia jest to, że większość serwerów aplikacji ma dzienniki dostępu, które rejestrują adres URL, a więc może znajdować się pomiędzy nimi. Więc może być jakiś nieautoryzowany wyciek informacji w GET.
user3206144

2
Możliwy duplikat projektu RESTful URL dla wyszukiwania
ivan_pozdeev,

Odpowiedzi:


397

Najlepszym sposobem na wdrożenie wyszukiwania RESTful jest uznanie samego wyszukiwania za zasób. Następnie możesz użyć czasownika POST, ponieważ tworzysz wyszukiwanie. Nie musisz dosłownie tworzyć czegoś w bazie danych, aby użyć testu POST.

Na przykład:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

Tworzysz wyszukiwanie z punktu widzenia użytkownika. Szczegóły implementacji tego są nieistotne. Niektóre interfejsy API RESTful mogą nawet nie wymagać trwałości. To szczegół implementacji.


209
Jednym ze znaczących ograniczeń korzystania z żądania POST dla punktu końcowego wyszukiwania jest to, że nie można go dodać do zakładek. Dodawanie zakładek do wyników wyszukiwania (szczególnie złożonych zapytań) może być bardzo przydatne.
couchand

73
Używanie POST do wyszukiwania może złamać ograniczenie pamięci podręcznej REST. whatisrest.com/rest_constraints/cache_excerps
Filipe

55
Wyszukiwania ze swej natury są przejściowe: dane ewoluują między dwoma wyszukiwaniami o tych samych parametrach, więc myślę, że żądanie GET nie mapuje się czysto na wzorzec wyszukiwania. Zamiast tego żądanie wyszukiwania powinno być POST (/ Resource / search), wtedy możesz zapisać to wyszukiwanie i przekierować do wyniku wyszukiwania, np. / Resource / search / iyn3zrt. W ten sposób żądania GET kończą się powodzeniem i mają sens.
sleblanc

32
Nie sądzę, aby poczta była odpowiednią metodą wyszukiwania, dane dla normalnych żądań GET mogą się zmieniać w czasie.
zastanawiam się,

82
To jest absolutnie najgorsza możliwa odpowiedź. Nie mogę uwierzyć, że ma tak wiele pozytywnych opinii. Ta odpowiedź wyjaśnia, dlaczego: programmers.stackexchange.com/questions/233164/…
richard

141

Jeśli używasz treści żądania w żądaniu GET, łamiesz zasadę REST, ponieważ twoje żądanie GET nie będzie mogło być buforowane, ponieważ system pamięci podręcznej używa tylko adresu URL.

Co gorsza, Twojego adresu URL nie można dodać do zakładek, ponieważ nie zawiera on wszystkich informacji potrzebnych do przekierowania użytkownika na tę stronę

Użyj parametrów adresu URL lub zapytania zamiast parametrów treści żądania.

na przykład:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

W rzeczywistości HTTP RFC 7231 mówi, że:

Ładunek w komunikacie żądania GET nie ma zdefiniowanej semantyki; wysłanie treści ładunku na żądanie GET może spowodować, że niektóre istniejące implementacje odrzucą żądanie.

Aby uzyskać więcej informacji, spójrz tutaj


29
Ucz się na moim błędzie - zaprojektowałem interfejs API przy użyciu sugestii zaakceptowanej odpowiedzi (POSTing json), ale przechodzę do parametrów adresu URL. Możliwość dodawania zakładek może być ważniejsza niż myślisz. W moim przypadku zaistniała potrzeba skierowania ruchu na określone zapytania (kampania reklamowa). Ponadto użycie interfejsu API historii ma większy sens w przypadku parametrów adresu URL.
Jake,

2
To zależy od tego, jak jest używany. Jeśli linkujesz do adresu URL, który ładuje stronę na podstawie tych parametrów, ma to sens, ale jeśli strona główna wykonuje wywołanie AJAX tylko w celu uzyskania danych na podstawie parametrów filtru, nie możesz dodać do zakładek tej zakładki, ponieważ jest to wywołanie ajax i nie ma wpływu. Oczywiście możesz również dodać do zakładek adres URL, który, gdy tam wejdziesz, tworzy filtr i testy POST, które wywołują wywołanie ajax i działałoby dobrze.
Daniel Lorenz,

@DanielLorenz Aby zapewnić jak najlepszą obsługę, adres URL powinien nadal zostać zmieniony za pomocą History API. Nie mogę znieść, gdy witryna nie pozwala na korzystanie z funkcji powrotu przeglądarki do przechodzenia do poprzednich stron. A jeśli jest to standardowa strona generowana przez serwer, jedynym sposobem na uczynienie go bukmacherskim byłoby użycie żądania GET. Wydaje się, że dobre parametry zapytania są najlepszym rozwiązaniem.
Nathan

@Nathan Myślę, że źle odczytałem tę odpowiedź. Mówiłem o użyciu parametrów ciągu zapytania w get. Nigdy nie należy używać parametrów treści w wywołaniu GET, ponieważ byłoby to całkowicie bezużyteczne. Mówiłem więcej o GET z ciągiem zapytania, którego można użyć / dodać do zakładek, a następnie przy uruchomieniu strony można użyć tych parametrów, aby zbudować filtr do POST, używając tych parametrów, aby uzyskać dane. Historia nadal działałaby dobrze w tym scenariuszu.
Daniel Lorenz

@DanielLorenz Ah w porządku, to ma sens. Myślę, że źle zrozumiałem, co mówiłeś.
Nathan

70

Wygląda na to, że filtrowanie / wyszukiwanie zasobów można wdrożyć w sposób RESTful. Chodzi o to, aby wprowadzić nowy punkt końcowy o nazwie /filters/lub /api/filters/.

Korzystanie z tego filtra punktu końcowego może być traktowane jako zasób, a zatem tworzone za pomocą POSTmetody. W ten sposób - oczywiście - body można wykorzystać do przenoszenia wszystkich parametrów, a także można tworzyć złożone struktury wyszukiwania / filtrów.

Po utworzeniu takiego filtra istnieją dwie możliwości uzyskania wyniku wyszukiwania / filtrowania.

  1. Nowy zasób z unikalnym identyfikatorem zostanie zwrócony wraz z 201 Createdkodem statusu. Następnie za pomocą tego identyfikatora GETmożna poprosić o /api/users/polubienie:

    GET /api/users/?filterId=1234-abcd
    
  2. Po utworzeniu nowego filtra za POSTjego pośrednictwem nie będzie odpowiadać, 201 Createdale jednocześnie 303 SeeOtherz Locationnagłówkiem wskazującym na /api/users/?filterId=1234-abcd. To przekierowanie będzie obsługiwane automatycznie przez bibliotekę bazową.

W obu przypadkach konieczne jest przesłanie dwóch żądań, aby uzyskać filtrowane wyniki - może to być uważane za wadę, szczególnie w przypadku aplikacji mobilnych. W przypadku aplikacji mobilnych użyłbym pojedynczego POSTpołączenia z /api/users/filter/.

Jak zachować utworzone filtry?

Mogą być przechowywane w DB i używane później. Mogą być również przechowywane w tymczasowym magazynie, np. Redis i mają trochę czasu wygaśnięcia, po upływie którego wygasną i zostaną usunięte.

Jakie są zalety tego pomysłu?

Filtry, przefiltrowane wyniki są buforowane i można je nawet dodać do zakładek.


2
to powinna być zaakceptowana odpowiedź. Nie naruszasz zasad REST i możesz składać długie, złożone zapytania do zasobów. Jest ładny, czysty i kompatybilny z zakładkami. Jedyną dodatkową wadą jest konieczność przechowywania par klucz / wartość dla utworzonych filtrów oraz wspomniane już dwa kroki żądania.
dantebarba

2
Jedynym problemem związanym z tym podejściem jest to, czy w zapytaniu masz filtry daty i godziny (lub ciągle zmieniającą się wartość). Wówczas liczba filtrów do przechowywania w db (lub cache) jest niezliczona.
Rvy Pandey,

17

Myślę, że powinieneś iść z parametrami żądania, ale tylko tak długo, dopóki nie ma odpowiedniego nagłówka HTTP, aby osiągnąć to, co chcesz zrobić. Specyfikacja HTTP nie mówi wprost, że GET nie może mieć treści. Jednak ten artykuł stwierdza:

Zgodnie z konwencją, gdy używana jest metoda GET, wszystkie informacje wymagane do identyfikacji zasobu są kodowane w URI. W HTTP / 1.1 nie ma konwencji dotyczącej bezpiecznej interakcji (np. Pobierania), w której klient dostarcza dane do serwera w treści jednostki HTTP, a nie w części URI zapytania. Oznacza to, że dla bezpiecznych operacji identyfikatory URI mogą być długie.


5
ElasticSearch działa również na ciało i działa dobrze!
Tarun Sapra

Tak, ale kontrolują implementację serwera, może nie być internebem.
user432024

7

Kiedy używam backendu laravel / php, mam tendencję do korzystania z czegoś takiego:

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

PHP automatycznie przekształca parametry []w tablicę, więc w tym przykładzie skończę ze $filterzmienną, która przechowuje tablicę / obiekt filtrów, wraz ze stroną i wszelkimi powiązanymi zasobami, które chcę załadować.

Jeśli używasz innego języka, może to być dobra konwencja i możesz utworzyć analizator składni w celu konwersji []na tablicę.


To podejście wygląda ładnie, ale mogą występować problemy z używaniem nawiasów kwadratowych w adresach URL, zobacz jakie znaki można użyć w jednym adresie URL
Sky,

2
@Sky Można tego uniknąć, kodując URI [i ]. Używanie zakodowanych reprezentacji tych znaków do grupowania parametrów zapytań jest dobrze znaną praktyką. Jest nawet używany w specyfikacji JSON: API .
jelhan

6

Nie przejmuj się zbytnio, jeśli początkowy interfejs API jest w pełni RESTful, czy nie (szczególnie, gdy jesteś właśnie w fazie alfa). Najpierw uruchom hydraulikę zaplecza. Zawsze możesz przeprowadzić transformację / ponowne zapisanie adresu URL, aby zmapować wszystko, dopracowując iteracyjnie, dopóki nie uzyskasz czegoś wystarczająco stabilnego do powszechnego testowania („beta”).

Możesz zdefiniować identyfikatory URI, których parametry są kodowane przez pozycję i konwencję na samych identyfikatorach URI, poprzedzone ścieżką, o której wiesz, że zawsze będziesz na nią mapować. Nie znam PHP, ale zakładam, że istnieje taka możliwość (tak jak w innych językach z frameworkiem):

.to znaczy. Wykonaj wyszukiwanie typu „użytkownik” z param [i] = wartość [i] dla i = 1..4 w sklepie nr 1 (z wartością1, wartość2, wartość3, ... jako skrót dla parametrów zapytania URI):

1) GET /store1/search/user/value1,value2,value3,value4

lub

2) GET /store1/search/user,value1,value2,value3,value4

lub w następujący sposób (chociaż nie poleciłbym tego, więcej o tym później)

3) GET /search/store1,user,value1,value2,value3,value4

Dzięki opcji 1 mapujesz wszystkie identyfikatory URI z prefiksem /store1/search/userna moduł obsługi wyszukiwania (lub dowolne oznaczenie PHP), domyślnie wykonując wyszukiwanie zasobów w magazynie 1 (równoważne z/search?location=store1&type=user .

Zgodnie z konwencją udokumentowaną i wymuszoną przez API wartości parametrów od 1 do 4 są oddzielane przecinkami i prezentowane w tej kolejności.

Opcja 2 dodaje typ wyszukiwania (w tym przypadku user) jako parametr pozycyjny nr 1. Każda z tych opcji jest tylko wyborem kosmetycznym.

Opcja 3 jest również możliwa, ale nie sądzę, żebym chciał. Myślę, że zdolność wyszukiwania w ramach niektórych zasobów powinna być prezentowana w samym URI poprzedzającym samo wyszukiwanie (tak jakby wyraźnie wskazywał w URI, że wyszukiwanie jest specyficzne w obrębie zasobu).

Zaletą tego nad przekazywaniem parametrów w URI jest to, że wyszukiwanie jest częścią URI (w ten sposób traktując wyszukiwanie jako zasób, zasób, którego treść może - i będzie - zmieniać się w czasie). Wadą jest to, że kolejność parametrów jest obowiązkowa .

Gdy zrobisz coś takiego, możesz użyć GET, a byłby to zasób tylko do odczytu (ponieważ nie możesz POST lub PUT do niego - jest aktualizowany, gdy jest GET'ed). Byłby to również zasób, który powstaje dopiero po wywołaniu.

Można również dodać do niego więcej semantyki, buforując wyniki przez pewien czas lub USUŃ powodując usunięcie pamięci podręcznej. Może to jednak być sprzeczne z tym, do czego ludzie zwykle używają DELETE (i ponieważ ludzie zwykle kontrolują buforowanie za pomocą nagłówków buforowania).

Sposób, w jaki sobie poradzisz, byłby decyzją projektową, ale tak właśnie bym sobie podjął. To nie jest idealne i jestem pewien, że będą przypadki, w których robienie tego nie jest najlepszą rzeczą (szczególnie w przypadku bardzo złożonych kryteriów wyszukiwania).


7
Yo, jeśli ty (ktoś, ktokolwiek / cokolwiek) rzeczy są właściwe, by głosować za moją odpowiedzią, czy zaszkodzi ci ego, jeśli przynajmniej skomentujesz to, z czym dokładnie się nie zgadzasz? Wiem, że to splot, ale ...;)
luis.spinal

107
Nie przegłosowałem, ale fakt, że pytanie zaczyna się od: „Obecnie projektuję i wdrażam RESTful API”, a twoja odpowiedź zaczyna się od: „Nie przejmuj się zbytnio, jeśli początkowy API jest w pełni RESTful, czy nie” źle dla mnie. Jeśli projektujesz interfejs API, projektujesz interfejs API. Pytanie dotyczy tego, jak najlepiej zaprojektować interfejs API, a nie tego, czy interfejs API powinien zostać zaprojektowany.
gardarh

14
API jest systemem, najpierw działa na API, a nie na hydraulice wewnętrznej bazy danych, pierwsza implementacja może / powinna być tylko próbą. HTTP ma mechanizm przekazywania parametrów, sugerujesz, że trzeba go wynaleźć na nowo, ale gorzej (parametry uporządkowane zamiast par nazw i nazw). Stąd głosowanie w dół.
Steven Herod

14
@gardarh - tak, wydaje się źle, ale czasami jest pragmatyczny. Podstawowym celem jest zaprojektowanie interfejsu API, który będzie działał w danym kontekście biznesowym. Jeśli podejście w pełni RESTFULLNE jest odpowiednie dla danego biznesu, to idź do niego. Jeśli tak nie jest, nie rób tego. To znaczy zaprojektuj interfejs API, który będzie spełniał Twoje specyficzne wymagania biznesowe. Poruszanie się, aby uczynić go RESTfull, ponieważ jego podstawowe wymaganie nie różni się niczym od pytania „jak używać wzorca adaptera w problemie X / Y”. Nie butów paradygmatów klaksonu, chyba że rozwiążą rzeczywiste, cenne problemy.
luis.espinal

1
Traktuję zasób jako pewną kolekcję stanu, a parametry jako sposób manipulowania reprezentacją tego stanu parametrycznie. Pomyśl o tym w ten sposób, jeśli możesz użyć pokręteł i przełączników, aby dostosować sposób wyświetlania zasobu (pokaż / ukryj niektóre jego fragmenty, uporządkuj inaczej, itp.). Te elementy sterujące są parametrami. Jeśli w rzeczywistości jest to inny zasób (na przykład „/ albumy” vs. „/ wykonawcy”), wtedy powinien być reprezentowany na ścieżce. W każdym razie jest to dla mnie intuicyjne.
Eric Elliott

2

FYI: Wiem, że to trochę za późno, ale dla każdego, kto jest zainteresowany. W zależności od tego, jak RESTful chcesz być, będziesz musiał wdrożyć własne strategie filtrowania, ponieważ specyfikacja HTTP nie jest w tym bardzo jasna. Chciałbym zaproponować kodowanie adresu URL wszystkich parametrów filtra, np

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

Wiem, że to brzydkie, ale myślę, że to najbardziej ODPOCZYNY sposób, aby to zrobić i powinien być łatwy do przeanalizowania po stronie serwera :)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.