RESTful diseño de URL para búsqueda

427

Estoy buscando una forma razonable de representar las búsquedas como URL RESTful.

La configuración: tengo dos modelos, Cars y Garages, donde Cars puede estar en Garages. Entonces mis URL se ven así:

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

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

Un automóvil puede existir solo (de ahí el / automóvil), o puede existir en un garaje. ¿Cuál es la forma correcta de representar, por ejemplo, todos los autos en un garaje determinado? Algo como:

/garage/yyy/cars     ?

¿Qué tal la unión de autos en garaje yyy y zzz?

¿Cuál es la forma correcta de representar una búsqueda de automóviles con ciertos atributos? Diga: muéstrame todos los sedanes azules con 4 puertas:

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

o debería ser / cars en su lugar?

El uso de "buscar" parece inapropiado allí: ¿cuál es una mejor forma / término? Debería ser solo:

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

¿Deben los parámetros de búsqueda ser parte de PATHINFO o QUERYSTRING?

En resumen, estoy buscando orientación para el diseño de URL REST entre modelos y para la búsqueda.

[Actualización] Me gusta la respuesta de Justin, pero no cubre el caso de búsqueda de múltiples campos:

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

o algo así. ¿Cómo pasamos de

/cars/color/blue

al caso de campo múltiple?

Desfile
fuente
16
A pesar de que se ve mejor en Inglés, mezcla /carsy /carno es semántica y por lo tanto una mala idea. Utilice siempre el plural cuando haya más de un elemento en esa categoría.
Zaz
44
Estas son malas respuestas. La búsqueda debe usar cadenas de consulta. Las cadenas de consulta son 100% RESTful cuando se usan correctamente (es decir, para la búsqueda).
pbreitenbach

Respuestas:

435

Para la búsqueda, use cadenas de consulta. Esto es perfectamente RESTful:

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

Una ventaja de las cadenas de consulta regulares es que son estándar y ampliamente entendidas, y que se pueden generar desde form-get.

Pbreitenbach
fuente
42
Esto es correcto. El objetivo de las cadenas de consulta es hacer cosas como la búsqueda.
aehlke
22
De hecho, esto es correcto ya que, según RFC3986 , la ruta y la cadena de consulta identifican el recurso. Lo que es más, simplemente sería un nombre apropiado /cars?color=whatever.
Lloeki
35
¿Qué pasa con los casos en que desea comparadores (>, <, <=,> =)? / coches? rating <= 3?
Jesse
3
¿Qué sucede si desea acceder a recursos anidados debajo de la cadena de consulta? Por ejemplo /cars?color=blue&type=sedan&doors=4/engines, no funcionará
Abe Voelker
99
@mjs /cars?param=valuees para el filtrado simple en la lista de autos y /cars/search?param=valuepara crear una búsqueda (sin persistencia) donde el resultado puede contener puntaje de búsqueda, categorización, etc. También puede crear / eliminar una búsqueda con nombre como /cars/search/mysearch. Mira eso: stackoverflow.com/a/18933902/1480391
Yves M.
121

El diseño de URL bonita RESTful se trata de mostrar un recurso basado en una estructura (estructura similar a un directorio, fecha: artículos / 2005/5/13, objeto y sus atributos, ..), la barra /indica la estructura jerárquica, use el-id lugar.

Estructura jerarquica

Yo personalmente preferiría:

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

Si un usuario elimina la /car-idpieza, trae la carsvista previa, intuitiva. El usuario sabe exactamente en qué parte del árbol está, qué está mirando. Él sabe desde el primer vistazo, que los garajes y los automóviles están en relación. /car-idTambién denota que pertenece junto a diferencia /car/id.

buscando

La búsqueda está bien tal como está , solo existe su preferencia, lo que debe tenerse en cuenta. La parte divertida viene cuando se unen búsquedas (ver más abajo).

/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 &*

O básicamente cualquier cosa que no sea una barra como se explicó anteriormente.
La fórmula:, /cars[?;]color[=-:]blue[,;+&]* aunque no usaría el &signo ya que es irreconocible del texto a primera vista.

** ¿Sabías que pasar un objeto JSON en URI es RESTful? ** **

Listas de opciones

/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

posibles características?

Negar cadenas de búsqueda (!)
Para buscar cualquier automóvil, pero no negro y rojo :
?color=!black,!red
color:(!black,!red)

Búsquedas unidas
Búsqueda rojos o azules o negras coches con 3 puertas de garajes Identificación 1..20 o 101..103 o 999 , pero no 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
a continuación, usted puede construir más complejas consultas de búsqueda. (Observe la coincidencia de atributos CSS3 para la idea de hacer coincidir subcadenas. Por ejemplo, buscar usuarios que contengan "barra" user*=bar).

Conclusión

De todos modos, esta podría ser la parte más importante para usted, porque usted puede hacerlo sin embargo te gusta después de todo, sólo tener en cuenta que REST URI representa una estructura que es fácil de entender por ejemplo directory-como /directory/file, /collection/node/item, fechas/articles/{year}/{month}/{day} .. Y cuando se omite cualquiera de los últimos segmentos, inmediatamente sabes lo que obtienes.

Entonces, todos estos caracteres están permitidos sin codificar :

  • no reservado: a-zA-Z0-9_.-~
    normalmente permitido tanto codificado como no, ambos usos son equivalentes.
  • caracteres especiales: $-_.+!*'(),
  • reservado: se ;/?:@=&
    puede usar sin codificar para el propósito que representan; de lo contrario, se debe codificar.
  • inseguro: por <>"#%{}|\^~[]`
    qué inseguro y por qué debería codificarse: RFC 1738 ver 2.2

    Consulte también RFC 1738 # página-20 para obtener más clases de caracteres.

RFC 3986 ver 2.2
A pesar de lo que dije anteriormente, aquí hay una distinción común de delimitadores, lo que significa que algunos "son" más importantes que otros.

  • delimitadores genéricos: :/?#[]@
  • subdelímetros: !$&'()*+,;=

Más información:
Jerarquía: consulte 2.3 , consulte 1.2.3
sintaxis de parámetro de ruta de URL url que
coincide con el atributo CSS3
IBM: servicios web RESTful - Aspectos básicos
Nota: RFC 1738 fue actualizado por RFC 3986

QWERTY
fuente
3
No creo haber pensado en usar JSON en la cadena de consulta. Es la respuesta a un problema que enfrentaba: estructura de búsqueda compleja sin usar POST. Además, otras ideas que dio en su respuesta también son muy apreciables. ¡Muchas gracias!
gustavohenke
44
@Qwerty: ¡gran publicación! Me preguntaba: ¿la única razón para usar ;en lugar de &es la legibilidad? Porque si es así, creo que preferiría el, &ya que es el delimitador más común ... ¿verdad? :) ¡Gracias!
Flo
3
@Flo Sí exactamente :), pero tenga en cuenta que &como delimitador solo lo conocen los desarrolladores. Los padres, abuelos y la población no educada acepta delimitadores tal como se usan en el texto escrito común.
Qwerty
17
¿Por qué crear un esquema no estándar cuando las cadenas de consulta son bien entendidas y estándar?
pbreitenbach
1
@Qwerty nada que lo detenga / search? Cars = red, blue, green & garages = 1,2,3 O si usa un formulario <multiselect>: / search? Cars = red & cars = blue & garages = 1 & garages = 2
pbreitenbach
36

Aunque tener los parámetros en el camino tiene algunas ventajas, existen, en mi opinión, algunos factores que superan.

  • No todos los caracteres necesarios para una consulta de búsqueda están permitidos en una URL. La mayoría de los signos de puntuación y Unicode tendrían que estar codificados en URL como un parámetro de cadena de consulta. Estoy luchando con el mismo problema. Me gustaría usar XPath en la URL, pero no toda la sintaxis de XPath es compatible con una ruta URI. Entonces, para rutas simples, /cars/doors/driver/lock/combinationsería apropiado ubicar el combinationelemento ' ' en el documento XML de la puerta del conductor. Pero /car/doors[id='driver' and lock/combination='1234']no es tan amigable.

  • Hay una diferencia entre filtrar un recurso en función de uno de sus atributos y especificar un recurso.

    Por ejemplo, desde

    /cars/colors devuelve una lista de todos los colores para todos los automóviles (el recurso devuelto es una colección de objetos de color)

    /cars/colors/red,blue,green devolvería una lista de objetos de color rojo, azul o verde, no una colección de automóviles.

    Para devolver los autos, el camino sería

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

  • Los parámetros en la ruta son más difíciles de leer porque los pares de nombre / valor no están aislados del resto de la ruta, que no son pares de nombre / valor.

Un ultimo comentario. Prefiero /garages/yyy/cars(siempre en plural) a /garage/yyy/cars(tal vez fue un error tipográfico en la respuesta original) porque evita cambiar la ruta entre singular y plural. Para palabras con un añadido 's', el cambio no es tan malo, pero cambiando /person/yyy/friendsde /people/yyyparecer engorroso.

Doug Domeny
fuente
2
sí, estoy de acuerdo ... además, creo que la estructura de la ruta debe reflejar las relaciones naturales entre entidades, algún tipo de mapa de mis recursos, como un garaje tiene muchos automóviles, un automóvil pertenece a un garaje y así ... y dejar los parámetros de filtro, porque eso es de lo que estamos hablando, para hacer una cadena de consulta ... ¿qué piensas?
abre
31

Para ampliar la respuesta de Peter, puede hacer que la búsqueda sea un recurso de primera clase:

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)

El recurso de búsqueda tendría campos para color, modelo, estado de garajes, etc. y podría especificarse en XML, JSON o cualquier otro formato. Al igual que el recurso Coche y Garaje, puede restringir el acceso a las búsquedas en función de la autenticación. Los usuarios que ejecutan con frecuencia las mismas búsquedas pueden almacenarlos en sus perfiles para que no necesiten volver a crearlos. Las URL serán lo suficientemente cortas como para que en muchos casos puedan intercambiarse fácilmente por correo electrónico. Estas búsquedas almacenadas pueden ser la base de fuentes RSS personalizadas, etc.

Existen muchas posibilidades para utilizar las búsquedas cuando las considera recursos.

La idea se explica con más detalle en este Railscast .

Rich Apodaca
fuente
66
¿Este enfoque no va en contra de la idea de trabajar con un protocolo inquieto? Quiero decir, persistir en la búsqueda de un db es como tener una conexión con estado ... ¿no?
abre
55
Es más como tener un servicio con estado. También estamos cambiando el estado del servicio cada vez que agregamos un nuevo automóvil o garaje. Una búsqueda es solo otro recurso que se puede utilizar con la gama completa de verbos HTTP.
Rich Apodaca
2
¿Cómo define lo anterior una convención URI?
Rich Apodaca
3
REST no tiene nada que ver con URI bonitas o anidamiento de URI, etc. Si define URI como parte de su API, no es REST.
aehlke
2
He discutido este antes. Esto no tiene sentido, pero es algo terrible. La 'eliminación' de la búsqueda no está perfectamente clara, aquí está diciendo que elimina esta entidad de búsqueda, pero me gustaría usarla para eliminar los resultados que encontré a través de esa búsqueda. No añada 'búsquedas' como recurso.
thecoshman
12

La respuesta de Justin es probablemente el camino a seguir, aunque en algunas aplicaciones puede tener sentido considerar una búsqueda particular como un recurso en sí mismo, como por ejemplo si desea admitir búsquedas guardadas con nombre:

/search/{searchQuery}

o

/search/{savedSearchName}
Peter Hilton
fuente
11
No. nunca tiene sentido que una acción sea un recurso.
thecoshman
3
@thecoshman como se menciona en un comentario anterior, la búsqueda también es un sustantivo.
andho
6

Utilizo dos enfoques para implementar búsquedas.

1) Caso más simple, para consultar elementos asociados y para navegación.

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

Esto significa, consultar automóviles que tengan una ID de garaje igual a 1.

También es posible crear búsquedas más complejas:

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

Autos en todos los garajes en FirstStreet que no son rojos (3ra página, 100 elementos por página).

2) Las consultas complejas se consideran recursos regulares que se crean y pueden recuperarse.

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

El cuerpo POST para la creación de búsqueda es el siguiente:

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

Está basado en Grails (criterio DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html

usuario2108278
fuente
5

Esto no es REST. No puede definir URI para recursos dentro de su API. La navegación de recursos debe ser impulsada por hipertexto. Está bien si desea URI bonitos y grandes cantidades de acoplamiento, pero simplemente no lo llame REST, porque viola directamente las restricciones de la arquitectura RESTful.

Ver este artículo del inventor de REST.

aehlke
fuente
28
Tiene razón en que no es REST, es un diseño de URL para un sistema RESTful. También, sin embargo, es incorrecto al decir que viola la arquitectura RESTful. La restricción de hipertexto de REST es ortogonal a un buen diseño de URL para un sistema RESTful; Recuerdo que hubo una discusión con Roy T. Fielding en la lista REST hace varios años en la que participé en donde él declaró tan explícitamente. Dicho de otra manera, es posible tener hipertexto y diseño de URL. El diseño de URL para sistemas RESTful es como una sangría en la programación; no es obligatorio, pero es una muy buena idea (ignorar Python, etc.)
MikeSchinkel
2
Lo siento, tienes razón. El OP me dio la impresión de que iba a informar a los clientes sobre cómo construir URL: haría que los "diseños" de URL formaran parte de su API. Eso sería una violación de REST.
aehlke
@aehlke, debe actualizar su respuesta para que coincida con su comentario.
James McMahon
1
Cumple con el modelo de madurez Richardson de nivel 2 . Te estás refiriendo al nivel 3. Simplemente acepta REST como algo progresivamente adoptable.
Jules Randolph
1
@Jules Randolph: disculpas, mi respuesta se escribió solo unos meses después de que se acuñó el modelo de madurez de Richardson y antes de que Martin Fowler y otros autores lo popularizaran :) De hecho, es un modelo instructivo a seguir. Siéntase libre de editar la respuesta.
aehlke
1

Aunque me gusta la respuesta de Justin, creo que representa con mayor precisión un filtro en lugar de una búsqueda. ¿Qué pasa si quiero saber sobre autos con nombres que comienzan con cam?

A mi modo de ver, puede incorporarlo a la forma en que maneja recursos específicos:
/ cars / cam *

O simplemente puede agregarlo al filtro:
/ cars / doors / 4 / name / cam * / colors / red, azul, verde

Personalmente, prefiero este último, sin embargo, de ninguna manera soy un experto en REST (lo escuché por primera vez hace solo 2 o más semanas ...)


fuente
Así:/cars?name=cam*
DanMan
1

RESTful no recomienda usar verbos en URL's / cars / search no es tranquilo. La forma correcta de filtrar / buscar / paginar sus API es a través de los parámetros de consulta. Sin embargo, puede haber casos en los que tenga que romper la norma. Por ejemplo, si está buscando en múltiples recursos, entonces debe usar algo como / search? Q = query

Puede visitar http://saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices/ para comprender las mejores prácticas para diseñar API RESTful

java_geek
fuente
1
La búsqueda también es un sustantivo 😀
jith912
1

Además, también sugeriría:

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

Aquí, Searchse considera como un recurso secundario de Carsrecurso.

Aux
fuente
1

Aquí hay muchas buenas opciones para su caso. Aún así, debería considerar usar el cuerpo POST.

La cadena de consulta es perfecta para su ejemplo, pero si tiene algo más complicado, por ejemplo, una larga lista arbitraria de elementos o condicionales booleanos, es posible que desee definir la publicación como un documento que el cliente envía a través de POST.

Esto permite una descripción más flexible de la búsqueda, así como también evita el límite de longitud de URL del servidor.

estani
fuente
-4

Mi consejo sería este:

/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 :) )

Editar:

/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.

Esperemos que eso te de la idea. Esencialmente, su API Rest debería ser fácilmente detectable y debería permitirle navegar a través de sus datos. Otra ventaja de utilizar URL y no cadenas de consulta es que puede aprovechar los mecanismos de almacenamiento en caché nativos que existen en el servidor web para el tráfico HTTP.

Aquí hay un enlace a una página que describe los males de las cadenas de consulta en REST: http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Utilicé el caché de Google porque la página normal no funcionaba para mí, aquí está ese enlace también: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Justin Bozonier
fuente
1
Gracias por la respuesta detallada. En el último, ¿qué pasa si quiero buscar tanto por color como por número de puertas? / carros / colores / rojo, azul, verde / puertas / 4 Eso no parece correcto.
Desfile del
2
Las comas en la URL no me parecen adecuadas, pero siguen siendo válidas. Creo que es solo un cambio de paradigma.
Justin Bozonier el
21
No me gusta esta sugerencia. ¿Cómo sabrías la diferencia entre /cars/colors/red,blue,greeny /cars/colors/green,blue,red? El elemento de ruta del URI debe ser jerárquico, y realmente no veo que ese sea el caso aquí. Creo que esta es una situación en la que la cadena de consulta es la opción más adecuada.
troelskn
6262
Esta es una mala respuesta. De hecho, la forma correcta de implementar la búsqueda es con cadenas de consulta. Las cadenas de consulta no son malas en lo más mínimo cuando se usan correctamente. El artículo citado no se refiere a la búsqueda. Los ejemplos proporcionados están claramente torturados y no se mantendrían bien con más parámetros.
pbreitenbach
44
Las cadenas de consulta se realizaron principalmente para resolver el problema de consultar un recurso, incluso con múltiples parámetros. Pervertir el URI para habilitar una API "RESTful" parece peligroso y miope, especialmente porque tendría que escribir sus propias asignaciones complejas solo para manejar las diversas permutaciones de parámetros en el URI. Mejor aún, use la noción ya existente de usar punto y coma en sus URI: doriantaylor.com/policy/http-url-path-parameter-syntax
Anatoly G