ODPOWIEDNI projekt URL do wyszukiwania


427

Szukam rozsądnego sposobu reprezentowania wyszukiwań jako RESTful URL-i.

Konfiguracja: Mam dwa modele, Samochody i Garaże, w których Samochody mogą być w Garażach. Moje adresy URL wyglądają następująco:

/car/xxxx
  xxx == car id
  returns car with given id

/garage/yyy
  yyy = garage id
  returns garage with given id

Samochód może istnieć samodzielnie (stąd / car) lub może istnieć w garażu. Jaki jest właściwy sposób reprezentowania, powiedzmy, wszystkich samochodów w danym garażu? Coś jak:

/garage/yyy/cars     ?

Co powiesz na połączenie samochodów w garażu yyy i zzz?

Jaki jest właściwy sposób reprezentowania wyszukiwania samochodów o określonych atrybutach? Powiedz: pokaż mi wszystkie niebieskie sedany z 4 drzwiami:

/car/search?color=blue&type=sedan&doors=4

czy raczej powinno to być / cars?

Użycie „wyszukiwania” wydaje się tam niewłaściwe - jaki jest lepszy sposób / termin? Powinno to być po prostu:

/cars/?color=blue&type=sedan&doors=4

Czy parametry wyszukiwania powinny być częścią PATHINFO lub QUERYSTRING?

Krótko mówiąc, szukam wskazówek dotyczących projektowania adresów URL REST między modelami i wyszukiwania.

[Aktualizacja] Podoba mi się odpowiedź Justina, ale on nie obejmuje przypadku wyszukiwania wielu pól:

/cars/color:blue/type:sedan/doors:4

czy jakoś tak. Jak idziemy

/cars/color/blue

w przypadku wielu pól?


16
Choć wygląda lepiej w języku angielskim, mieszanie /carsi /carnie jest semantyczne i dlatego jest to zły pomysł. Zawsze używaj liczby mnogiej, jeśli w tej kategorii jest więcej niż jeden element.
Zaz

4
To są złe odpowiedzi. Wyszukiwanie powinno używać ciągów zapytań. Ciągi zapytań są w 100% RESTful, jeśli są właściwie używane (np. Do wyszukiwania).
pbreitenbach,

Odpowiedzi:


435

Do wyszukiwania użyj kwerend. Jest to całkowicie RESTful:

/cars?color=blue&type=sedan&doors=4

Zaletą regularnych zapytań jest to, że są one standardowe i szeroko rozumiane oraz że można je generować z form-get.


42
To jest poprawne. Cały ciąg ciągów zapytań służy do wyszukiwania rzeczy.
aehlke

22
Rzeczywiście jest to poprawne, ponieważ zgodnie z RFC3986 ścieżka i kwerenda identyfikują zasób. Co więcej, właściwe nazewnictwo byłoby po prostu /cars?color=whatever.
Lloeki

35
Co z przypadkami, w których chcesz komparatory (>, <, <=,> =)? / samochody? ocena <= 3?
Jesse

3
Co jeśli chcesz uzyskać dostęp do zasobów zagnieżdżonych pod ciągiem zapytania? Np. /cars?color=blue&type=sedan&doors=4/enginesNie zadziała
Abe Voelker

9
@mjs /cars?param=valuesłuży do prostego filtrowania na liście samochodów i /cars/search?param=valuedo tworzenia wyszukiwania (z ou bez wytrwałości), w którym wynik może zawierać punktację wyszukiwania, kategoryzację itp. Możesz także utworzyć / usunąć wyszukiwanie nazwane, takie jak /cars/search/mysearch. Spójrz na to: stackoverflow.com/a/18933902/1480391
Yves M.

121

Projekt RESTful pretty URL polega na wyświetlaniu zasobu opartego na strukturze (struktura katalogowa, data: artykuły / 2005/5/13, obiekt i jego atrybuty, ..), ukośnik /wskazuje strukturę hierarchiczną, -idzamiast tego użyj .

Struktura hierarchiczna

Osobiście wolałbym:

/garage-id/cars/car-id
/cars/car-id   #for cars not in garages

Jeśli użytkownik usunie /car-idczęść, wyświetla carspodgląd - intuicyjny. Użytkownik dokładnie wie, gdzie jest na drzewie, na co patrzy. Od pierwszego spojrzenia wie, że garaże i samochody są ze sobą powiązane. /car-idoznacza również, że należy do siebie w przeciwieństwie do /car/id.

Badawczy

Wyszukiwanie jest w porządku , ponieważ jest tylko twoje preferencje, co należy wziąć pod uwagę. Zabawne jest to, że dołączasz do wyszukiwania (patrz poniżej).

/cars?color=blue;type=sedan   #most prefered by me
/cars;color-blue+doors-4+type-sedan   #looks good when using car-id
/cars?color=blue&doors=4&type=sedan   #I don't recommend using &*

Lub w zasadzie wszystko, co nie jest cięciem, jak wyjaśniono powyżej.
Formuła:, /cars[?;]color[=-:]blue[,;+&]* chociaż nie użyłbym &znaku, ponieważ na pierwszy rzut oka nie można go rozpoznać po tekście.

** Czy wiesz, że przekazanie obiektu JSON w URI jest RESTful? **

Listy opcji

/cars?color=black,blue,red;doors=3,5;type=sedan   #most prefered by me
/cars?color:black:blue:red;doors:3:5;type:sedan
/cars?color(black,blue,red);doors(3,5);type(sedan)   #does not look bad at all
/cars?color:(black,blue,red);doors:(3,5);type:sedan   #little difference

możliwe funkcje?

Neguj ciągi wyszukiwania (!)
Aby wyszukać dowolne samochody, ale nie czarne i czerwone :
?color=!black,!red
color:(!black,!red)

Wyszukiwania Dołączył
Szukaj czerwone lub niebieskie lub czarne samochody z 3 bram w garażach id 1..20 lub 101..103 lub 999 , ale nie 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
Następnie można skonstruować bardziej złożonych zapytań. (Spójrz na dopasowanie atrybutów CSS3 pod kątem idei dopasowania podciągów. Np. Wyszukiwanie użytkowników zawierających „słupek” user*=bar.)

Wniosek

W każdym razie, to może być najważniejszą częścią dla ciebie, ponieważ można to zrobić jak chcesz po wszystkim, po prostu pamiętać, że relaksującego URI oznacza strukturę, która jest łatwo zrozumiałe, np katalog podobny /directory/file, /collection/node/item, daty /articles/{year}/{month}/{day}.. A jeśli pominąć którykolwiek z ostatnich segmentów, od razu wiesz, co dostajesz.

Więc ... wszystkie te znaki są dozwolone niekodowane :

  • bez zastrzeżeń: a-zA-Z0-9_.-~
    zazwyczaj dozwolone zarówno zakodowane, jak i nie, oba zastosowania są wtedy równoważne.
  • znaki specjalne: $-_.+!*'(),
  • zastrzeżone: ;/?:@=&
    Mogą być używane niezakodowane do celów, które reprezentują, w przeciwnym razie muszą zostać zakodowane.
  • niebezpieczne: <>"#%{}|\^~[]`
    Dlaczego niebezpieczne i dlaczego należy raczej zakodować: RFC 1738 patrz 2.2

    Zobacz także RFC 1738 # strona-20, aby uzyskać więcej klas postaci.

RFC 3986 patrz 2.2
Pomimo tego, co powiedziałem wcześniej, tutaj jest wspólne rozróżnienie delimetrów, co oznacza, że ​​niektóre „są” ważniejsze niż inne.

  • ogólne delimetry: :/?#[]@
  • sub-delimetry: !$&'()*+,;=

Więcej lektur:
Hierarchia: patrz 2.3 , patrz 1.2.3
składnia parametru ścieżki adresu URL
atrybut CSS3 zgodny z
IBM: RESTful Web Services - podstawy
Uwaga: RFC 1738 został zaktualizowany przez RFC 3986


3
Nie sądzę, że nie myślałem o użyciu JSON w ciągu zapytania. To odpowiedź na problem, z którym się spotkałem - złożona struktura wyszukiwania bez użycia POST. Również inne pomysły, które podałeś w swojej odpowiedzi, są bardzo znaczące. Dziękuję bardzo!
gustavohenke

4
@Qwerty: great post! Zastanawiałem się: jedynym powodem używania ;w przeciwieństwie do &czytelności? Bo jeśli tak, to myślę, że tak naprawdę wolałbym, &ponieważ jest to bardziej powszechny ogranicznik ... prawda? :) Dzięki!
Flo

3
@ Flo Tak dokładnie :), ale pamiętaj, że &jako separator jest znany tylko programistom. Rodzice, dziadkowie i niewykształcona populacja akceptują ograniczniki, jak to jest używane we wspólnym tekście pisanym.
Qwerty

17
Po co tworzyć niestandardowy schemat, gdy ciągi zapytań są dobrze zrozumiałe i standardowe?
pbreitenbach,

1
@Qwerty nic Cię powstrzymuje od / wyszukiwanie samochodów = czerwony, niebieski, zielony i garaże = 1,2,3 lub jeśli używasz formularza <multiselect>: / search samochody = red & cars = blue & garaże = 1 & garaże = 2?
pbreitenbach

36

Chociaż posiadanie parametrów na ścieżce ma pewne zalety, istnieją, według IMO, pewne czynniki przeważające.

  • Nie wszystkie znaki potrzebne do wyszukiwania są dozwolone w adresie URL. Większość znaków interpunkcyjnych i Unicode musiałaby być zakodowana w adresie URL jako parametr ciągu zapytania. Walczę z tym samym problemem. Chciałbym użyć XPath w adresie URL, ale nie cała składnia XPath jest zgodna ze ścieżką URI. W przypadku prostych ścieżek /cars/doors/driver/lock/combinationwskazane byłoby zlokalizowanie elementu „ combination” w dokumencie XML drzwi kierowcy. Ale /car/doors[id='driver' and lock/combination='1234']nie jest taki przyjazny.

  • Istnieje różnica między filtrowaniem zasobu na podstawie jednego z jego atrybutów a określaniem zasobu.

    Na przykład od

    /cars/colors zwraca listę wszystkich kolorów dla wszystkich samochodów (zwrócony zasób to zbiór obiektów kolorowych)

    /cars/colors/red,blue,green zwróci listę obiektów kolorowych, które są czerwone, niebieskie lub zielone, a nie kolekcję samochodów.

    Ścieżka byłaby do zwrotu samochodów

    /cars?color=red,blue,green lub /cars/search?color=red,blue,green

  • Parametry na ścieżce są trudniejsze do odczytania, ponieważ pary nazwa / wartość nie są odizolowane od reszty ścieżki, która nie jest parą nazwa / wartość.

Ostatni komentarz. Wolę /garages/yyy/cars(zawsze w liczbie mnogiej) niż /garage/yyy/cars(być może literówka w oryginalnej odpowiedzi), ponieważ pozwala to uniknąć zmiany ścieżki między liczbą pojedynczą a liczbą mnogą. W przypadku słów z dodanym „s” zmiana nie jest taka zła, ale zmiana /person/yyy/friendsna /people/yyywydaje się niewygodna.


2
tak, zgadzam się ... poza tym, że struktura adresów URL powinna odzwierciedlać naturalne relacje między bytami, jakąś mapę moich zasobów, jak garaż ma wiele samochodów, samochód należy do garażu i tak ... i niech parametry filtru, bo o to właśnie rozmawiamy, do kolejkowania zapytań ... co myślisz?
otwiera się

31

Aby rozwinąć odpowiedź Petera - możesz sprawić, aby Search był zasobem najwyższej klasy:

POST    /searches          # create a new search
GET     /searches          # list all searches (admin)
GET     /searches/{id}     # show the results of a previously-run search
DELETE  /searches/{id}     # delete a search (admin)

Zasób wyszukiwania będzie zawierał pola koloru, modelu, statusu garażowanego itp. I może być określony w formacie XML, JSON lub innym formacie. Podobnie jak zasób Samochód i garaż, możesz ograniczyć dostęp do wyszukiwań na podstawie uwierzytelnienia. Użytkownicy, którzy często uruchamiają te same wyszukiwania, mogą przechowywać je w swoich profilach, aby nie trzeba było ich ponownie tworzyć. Adresy URL będą na tyle krótkie, że w wielu przypadkach można je łatwo handlować za pośrednictwem poczty elektronicznej. Te przechowywane wyszukiwania mogą być podstawą niestandardowych kanałów RSS i tak dalej.

Istnieje wiele możliwości korzystania z Wyszukiwań, gdy myślisz o nich jako o zasobach.

Pomysł został wyjaśniony bardziej szczegółowo w tym Railscast .


6
czy to podejście nie jest sprzeczne z ideą pracy z niespokojnym protokołem? Chodzi mi o to, że kontynuowanie wyszukiwania do bazy danych jest rodzajem połączenia stanowego ... prawda?
otwiera się

5
To bardziej jak stanowa służba. Zmieniamy również stan usługi za każdym razem, gdy dodajemy nowy samochód lub garaż. Wyszukiwanie to po prostu kolejny zasób, którego można używać z pełnym zakresem czasowników HTTP.
Rich Apodaca

2
Jak powyższe definiuje konwencję URI?
Rich Apodaca

3
REST nie ma nic wspólnego z ładnymi URI lub zagnieżdżaniem URI itp. Jeśli zdefiniujesz URI jako część interfejsu API, nie będzie to REST.
aehlke

2
Kłóciłem się już wcześniej. Nie jest to w żaden sposób stanowcze, ale jest to okropne. „Usuń” wyszukiwania nie jest całkowicie jasne, tutaj mówisz, że usuwa ten podmiot wyszukiwania, ale chciałbym go użyć, aby usunąć wyniki znalezione podczas tego wyszukiwania. Nie dodawaj „wyszukiwań” jako zasobu.
thecoshman

12

Odpowiedź Justina jest prawdopodobnie właściwą drogą, chociaż w niektórych aplikacjach sensowne może być rozważenie konkretnego wyszukiwania jako zasobu jako takiego, na przykład jeśli chcesz obsługiwać nazwane zapisane wyszukiwania:

/search/{searchQuery}

lub

/search/{savedSearchName}

11
Nie. nigdy nie ma sensu mieć akcji jako zasobu.
thecoshman

3
@ thecoshman, jak wspomniano w komentarzu powyżej, wyszukiwanie to także rzeczownik.
andho

6

Używam dwóch podejść do realizacji wyszukiwania.

1) Najprostszy przypadek - zapytanie powiązanych elementów i nawigacja.

    /cars?q.garage.id.eq=1

Oznacza to zapytanie samochodów, które mają identyfikator garażu równy 1.

Możliwe jest również tworzenie bardziej złożonych wyszukiwań:

    /cars?q.garage.street.eq=FirstStreet&q.color.ne=red&offset=300&max=100

Samochody we wszystkich garażach w FirstStreet, które nie są czerwone (3. strona, 100 elementów na stronie).

2) Złożone zapytania są uważane za zwykłe zasoby, które są tworzone i można je odzyskać.

    POST /searches  => Create
    GET  /searches/1  => Recover search
    GET  /searches/1?offset=300&max=100  => pagination in search

Treść POST do tworzenia wyszukiwania jest następująca:

    {  
       "$class":"test.Car",
       "$q":{
          "$eq" : { "color" : "red" },
          "garage" : {
             "$ne" : { "street" : "FirstStreet" }
          }
       }
    }

Opiera się na Grails (kryteria DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html


5

To nie jest REST. Nie można zdefiniować identyfikatorów URI dla zasobów w interfejsie API. Nawigacja po zasobach musi być sterowana hipertekstem. W porządku, jeśli chcesz ładnych identyfikatorów URI i dużych ilości sprzężeń, ale nie nazywaj go REST, ponieważ bezpośrednio narusza ograniczenia architektury RESTful.

Zobacz ten artykuł twórcy REST.


28
Masz rację, że to nie jest REST, to projekt adresu URL dla systemu RESTful. Jednak niesłusznie twierdzisz, że narusza architekturę RESTful. Hipertekstowe ograniczenie REST jest prostopadłe do dobrego projektu adresu URL dla systemu RESTful; Pamiętam, że kilka lat temu była dyskusja z Royem T. Fieldingiem na liście REST, w której uczestniczyłem, gdzie tak wyraźnie stwierdził. Innym sposobem jest hipertekst i projektowanie adresów URL. Projektowanie adresów URL dla systemów RESTful jest jak wcięcie w programowaniu;
niewymagane,

2
Przepraszam, masz rację. Właśnie miałem wrażenie, że OP ma zamiar uświadomić klientom, jak konstruować adresy URL - sprawi, że „układy” adresów URL będą częścią jego interfejsu API. To byłoby naruszenie REST.
aehlke,

@aehlke, powinieneś zaktualizować swoją odpowiedź, aby pasowała do twojego komentarza.
James McMahon,

1
Jest zgodny z modelem dojrzałości Richardsona na poziomie 2 . Masz na myśli poziom 3. Po prostu zaakceptuj REST jako coś, co można stopniowo adaptować.
Jules Randolph

1
@Jules Randolph - przepraszam, moja odpowiedź została napisana zaledwie kilka miesięcy po stworzeniu modelu dojrzałości Richardsona, a zanim popularyzował go Martin Fowler i inni autorzy :) Rzeczywiście, jest to pouczający model do naśladowania. Edytuj odpowiedź.
aehlke

1

Chociaż podoba mi się odpowiedź Justina, uważam, że dokładniej reprezentuje ona filtr, a nie wyszukiwanie. Co jeśli chcę wiedzieć o samochodach o nazwach rozpoczynających się od kamery?

Z mojego punktu widzenia możesz go wbudować w sposób, w jaki obsługujesz określone zasoby:
/ cars / cam *

Lub możesz po prostu dodać go do filtra:
/ cars / doors / 4 / name / cam * / colors / red, niebieski, zielony

Osobiście wolę ten drugi, jednak w żadnym wypadku nie jestem ekspertem od REST (pierwszy raz słyszałem o nim zaledwie 2 tygodnie temu ...)


W ten sposób:/cars?name=cam*
DanMan

1

RESTful nie zaleca używania czasowników w adresach URL / samochody / wyszukiwanie nie jest spokojny. Właściwym sposobem filtrowania / wyszukiwania / paginacji interfejsu API jest skorzystanie z parametrów zapytania. Mogą jednak zdarzyć się przypadki, gdy będziesz musiał złamać normę. Na przykład, jeśli przeszukujesz wiele zasobów, musisz użyć czegoś takiego jak / search? Q = zapytanie

Możesz przejść przez http://saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices/, aby poznać najlepsze praktyki projektowania RESTful API


1
Wyszukiwanie też jest rzeczownikiem 😀
jith912

1

Ponadto sugerowałbym również:

/cars/search/all{?color,model,year}
/cars/search/by-parameters{?color,model,year}
/cars/search/by-vendor{?vendor}

Tutaj Searchjest uważany za podrzędny zasób Carszasobu.


1

Tutaj jest wiele dobrych opcji dla twojego przypadku. Nadal powinieneś rozważyć użycie ciała POST.

Ciąg zapytania jest idealny dla twojego przykładu, ale jeśli masz coś bardziej skomplikowanego, np. Dowolną długą listę elementów lub logiczne warunki, możesz zdefiniować post jako dokument, który klient wysyła za pomocą POST.

Pozwala to na bardziej elastyczny opis wyszukiwania, a także pozwala uniknąć limitu długości adresu URL serwera.


-4

Moja rada będzie następująca:

/garages
  Returns list of garages (think JSON array here)
/garages/yyy
  Returns specific garage
/garage/yyy/cars
  Returns list of cars in garage
/garages/cars
  Returns list of all cars in all garages (may not be practical of course)
/cars
  Returns list of all cars
/cars/xxx
  Returns specific car
/cars/colors
  Returns lists of all posible colors for cars
/cars/colors/red,blue,green
  Returns list of cars of the specific colors (yes commas are allowed :) )

Edytować:

/cars/colors/red,blue,green/doors/2
  Returns list of all red,blue, and green cars with 2 doors.
/cars/type/hatchback,coupe/colors/red,blue,green/
  Same idea as the above but a lil more intuitive.
/cars/colors/red,blue,green/doors/two-door,four-door
  All cars that are red, blue, green and have either two or four doors.

Mam nadzieję, że to daje ci pomysł. Zasadniczo Twój interfejs API Rest powinien być łatwo wykrywalny i powinien umożliwiać przeglądanie danych. Kolejną zaletą korzystania z adresów URL, a nie ciągów zapytań, jest możliwość korzystania z natywnych mechanizmów buforowania istniejących na serwerze WWW dla ruchu HTTP.

Oto link do strony opisującej zło łańcuchów zapytań w REST: http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsoveredHarmful

Użyłem pamięci podręcznej Google, ponieważ normalna strona nie działała dla mnie, oto również ten link: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsoveredHarmful


1
Dziękuję za szczegółową odpowiedź. Co do ostatniego, jeśli chcę wyszukiwać według koloru i liczby drzwi? / samochody / kolory / czerwony, niebieski, zielony / drzwi / 4 To nie wydaje się właściwe.
Parand

2
Przecinki w adresie URL nie są dla mnie odpowiednie, ale nadal są ważne. Myślę, że to tylko zmiana paradygmatu.
Justin Bozonier

21
Nie podoba mi się ta sugestia. Skąd znasz różnicę między /cars/colors/red,blue,greeni /cars/colors/green,blue,red? Element ścieżki identyfikatora URI powinien być hierarchiczny i nie wydaje mi się, żeby tak było w tym przypadku. Myślę, że jest to sytuacja, w której ciąg zapytania jest najbardziej odpowiednim wyborem.
troelskn

62
To kiepska odpowiedź. W rzeczywistości właściwym sposobem na zaimplementowanie wyszukiwania są ciągi zapytań. Ciągi zapytań nie są złe w najmniejszym stopniu, jeśli są właściwie używane. Cytowany artykuł nie odnosi się do wyszukiwania. Podane przykłady są wyraźnie torturowane i nie wytrzymają dobrze przy większej liczbie parametrów.
pbreitenbach,

4
kwerendy zostały stworzone przede wszystkim w celu rozwiązania problemu zapytania o zasób, nawet przy wielu parametrach. Odwracanie URI w celu włączenia interfejsu API „RESTful” wydaje się niebezpieczne i krótkowzroczne - zwłaszcza, że ​​trzeba będzie pisać własne złożone odwzorowania, aby obsłużyć różne kombinacje parametrów w URI. Jeszcze lepiej, użyj już istniejącej koncepcji używania średników w twoich URI: doriantaylor.com/policy/http-url-path-parameter-syntax
Anatoly G
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.