¿Cómo diseñar una búsqueda / filtrado RESTful? [cerrado]

457

Actualmente estoy diseñando e implementando una API RESTful en PHP. Sin embargo, no he podido implementar mi diseño inicial.

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

Hasta ahora bastante estándar, ¿verdad?

Mi problema es con el primero GET /users. Estaba considerando enviar parámetros en el cuerpo de la solicitud para filtrar la lista. Esto se debe a que quiero poder especificar filtros complejos sin obtener una URL muy larga, como:

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

En cambio, quería tener algo como:

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

que es mucho más legible y le brinda grandes posibilidades para configurar filtros complejos.

De todos modos, file_get_contents('php://input')no devolvió el cuerpo de la GETsolicitud para las solicitudes. También lo intenté http_get_request_body(), pero el alojamiento compartido que estoy usando no tiene pecl_http. No estoy seguro de que hubiera ayudado de todos modos.

Encontré esta pregunta y me di cuenta de que GET probablemente no debería tener un cuerpo de solicitud. Fue un poco inconcluso, pero desaconsejaron.

Así que ahora no estoy seguro de qué hacer. ¿Cómo se diseña una función de búsqueda / filtrado RESTful?

Supongo que podría usar POST, pero eso no parece muy RESTANTE.

Erik B
fuente
77
posible duplicado del diseño RESTful URL para la búsqueda
outis
6060
¡¡¡Ten cuidado!!! El método GET debe ser IDEMPOTENTE y debe ser "almacenable en caché". Si envía información en el cuerpo ¿Cómo puede el sistema almacenar en caché su solicitud? HTTP permite almacenar en caché la solicitud GET utilizando solo la URL, no el cuerpo de la solicitud. Por ejemplo, estas dos solicitudes: example.com {test: "some"} example.com {anotherTest: "some2"} son consideradas iguales por el sistema de caché: ambas tienen exactamente la misma URL
jfcorugedo
15
Solo para agregar, debe PUBLICAR en / users (colección) y no / user (usuario único).
Mladen B.
1
Otro punto a tener en cuenta es que la mayoría de los servidores de aplicaciones tienen registros de acceso que registran la URL y, por lo tanto, pueden ser algo intermedio. Por lo tanto, puede haber una fuga de información no deseada en GET.
user3206144
2
Posible duplicado del diseño de URL RESTful para búsqueda
ivan_pozdeev

Respuestas:

396

La mejor manera de implementar una búsqueda RESTful es considerar la búsqueda en sí misma como un recurso. Luego puede usar el verbo POST porque está creando una búsqueda. No tiene que crear literalmente algo en una base de datos para utilizar una POST.

Por ejemplo:

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

Está creando una búsqueda desde el punto de vista del usuario. Los detalles de implementación de esto son irrelevantes. Es posible que algunas API RESTful ni siquiera necesiten persistencia. Ese es un detalle de implementación.

Jason Harrelson
fuente
209
Una limitación importante para usar una solicitud POST para un punto final de búsqueda es que no se puede agregar a favoritos. Los resultados de búsqueda de marcadores (particularmente consultas complejas) pueden ser bastante útiles.
Couchand
73
El uso de POST para realizar búsquedas puede romper la restricción de caché REST. whatisrest.com/rest_constraints/cache_excerps
Filipe
56
Las búsquedas, por su naturaleza, son transitorias: los datos evolucionan entre dos búsquedas con los mismos parámetros, por lo que creo que una solicitud GET no se asigna correctamente al patrón de búsqueda. En cambio, la solicitud de búsqueda debe ser POST (/ Resource / search), luego puede guardar esa búsqueda y redirigirla a un resultado de búsqueda, por ejemplo / Resource / search / iyn3zrt. De esa manera, las solicitudes GET tienen éxito y tienen sentido.
sleblanc
32
No creo que la publicación sea un método adecuado para la búsqueda, los datos para solicitudes GET normales también pueden variar con el tiempo.
pregunto
82
Esta es absolutamente la peor respuesta posible. No puedo creer que tenga tantos votos a favor. Esta respuesta explica por qué: programmers.stackexchange.com/questions/233164/…
richard
141

Si usa el cuerpo de la solicitud en una solicitud GET, está rompiendo el principio REST, porque su solicitud GET no podrá almacenarse en caché, porque el sistema de caché usa solo la URL.

Y lo que es peor, su URL no se puede agregar a favoritos, porque la URL no contiene toda la información necesaria para redirigir al usuario a esta página

Utilice los parámetros URL o Consulta en lugar de los parámetros del cuerpo de la solicitud.

p.ej:

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

De hecho, el HTTP RFC 7231 dice que:

Una carga útil dentro de un mensaje de solicitud GET no tiene semántica definida; El envío de un cuerpo de carga útil en una solicitud GET puede causar que algunas implementaciones existentes rechacen la solicitud.

Para más información mira aquí

jfcorugedo
fuente
29
Aprenda de mi error: diseñé una API usando la sugerencia de respuesta aceptada (POSTing json), pero me estoy moviendo a los parámetros de URL. La capacidad de marcador puede ser más importante de lo que piensas. En mi caso, era necesario dirigir el tráfico a ciertas consultas de búsqueda (campaña publicitaria). Además, el uso de la API de historial tiene más sentido con los parámetros de URL.
Jake
2
Depende de cómo se use. Si está vinculando a una URL que carga la página en función de esos parámetros, tiene sentido, pero si la página principal está haciendo una llamada AJAX solo para obtener los datos basados ​​en parámetros de filtro, no puede marcar eso de todos modos porque es un Llamada ajax y no tiene rumbo. Naturalmente, también podría marcar un URL que cuando vaya allí construya un filtro y lo PUBLICE en la llamada ajax y funcionaría bien.
Daniel Lorenz
@DanielLorenz Para la mejor experiencia de usuario, la URL aún debe cambiarse a través de la API de historial en ese caso. No puedo soportar cuando un sitio web no permite usar la funcionalidad de retroceso del navegador para navegar a páginas anteriores. Y si se trata de una página estándar generada por el lado del servidor, la única forma de hacerla como favorita sería utilizar una solicitud GET. Parece que los buenos parámetros de consulta son la mejor solución.
Nathan
@Nathan Creo que leí mal esta respuesta. Estaba hablando de usar parámetros de cadena de consulta en un get. Nunca debe usar parámetros de cuerpo en una llamada GET porque eso sería completamente inútil. Estaba hablando más sobre un GET con una cadena de consulta que podría usarse / marcarse y luego, al inicio de la página, puede usar esos parámetros para construir un filtro para POST, usando esos parámetros para obtener los datos. La historia aún funcionaría bien en ese escenario.
Daniel Lorenz
@DanielLorenz Ah, está bien, eso tiene sentido. Creo que entendí mal lo que estabas diciendo.
Nathan
70

Parece que el filtrado / búsqueda de recursos puede implementarse de una manera RESTful. La idea es introducir un nuevo punto final llamado /filters/o /api/filters/.

El uso de este filtro de punto final se puede considerar como un recurso y, por lo tanto, se puede crear mediante un POSTmétodo. De esta forma, por supuesto, el cuerpo se puede usar para transportar todos los parámetros, así como también se pueden crear estructuras complejas de búsqueda / filtro.

Después de crear dicho filtro, hay dos posibilidades para obtener el resultado de búsqueda / filtro.

  1. Se devolverá un nuevo recurso con ID único junto con el 201 Createdcódigo de estado. Luego, utilizando esta ID, GETse puede hacer una solicitud para que le /api/users/guste:

    GET /api/users/?filterId=1234-abcd
    
  2. Después de crear un nuevo filtro a través de POSTél, no responderá con, 201 Createdpero de inmediato con 303 SeeOtherjunto con el Locationencabezado apuntando a /api/users/?filterId=1234-abcd. Esta redirección se gestionará automáticamente a través de la biblioteca subyacente.

En ambos escenarios, se deben realizar dos solicitudes para obtener los resultados filtrados; esto puede considerarse como un inconveniente, especialmente para aplicaciones móviles. Para aplicaciones móviles, usaría una sola POSTllamada a /api/users/filter/.

¿Cómo mantener los filtros creados?

Se pueden almacenar en DB y usar más adelante. También se pueden almacenar en algún almacenamiento temporal, por ejemplo, redis y tienen algo de TTL, después de lo cual caducarán y se eliminarán.

¿Cuáles son las ventajas de esta idea?

Los filtros, los resultados filtrados se pueden almacenar en caché e incluso se pueden agregar a favoritos.

Ópalo
fuente
2
bueno, esta debería ser la respuesta aceptada. No infringe los principios REST y puede realizar consultas largas y complejas a los recursos. Es agradable, limpio y compatible con marcadores. El único inconveniente adicional es la necesidad de almacenar pares clave / valor para los filtros creados, y los dos pasos de solicitud ya mencionados.
dantebarba
2
La única preocupación con este enfoque es si tiene filtros de fecha y hora en la consulta (o un valor que cambia constantemente). Entonces, la cantidad de filtros para almacenar en db (o caché) son innumerables.
Rvy Pandey
17

Creo que debería ir con los parámetros de solicitud, pero solo mientras no haya un encabezado HTTP apropiado para lograr lo que desea hacer. La especificación HTTP no dice explícitamente que GET no puede tener un cuerpo. Sin embargo, este documento establece:

Por convención, cuando se utiliza el método GET, toda la información requerida para identificar el recurso se codifica en el URI. No existe una convención en HTTP / 1.1 para una interacción segura (por ejemplo, recuperación) en la que el cliente suministre datos al servidor en un cuerpo de entidad HTTP en lugar de en la parte de consulta de un URI. Esto significa que para operaciones seguras, los URI pueden ser largos.

Narciso
fuente
66
¡ElasticSearch también hace GET con cuerpo y funciona bien!
Tarun Sapra
Sí, pero controlan la implementación del servidor puede que no sea tne xase en los interwebs.
user432024
7

Como estoy usando un backend laravel / php tiendo a ir con algo como esto:

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

PHP convierte automáticamente los []parámetros en una matriz, por lo que en este ejemplo terminaré con una $filtervariable que contiene una matriz / objeto de filtros, junto con una página y cualquier recurso relacionado que quiera cargar con ansias.

Si usa otro idioma, esto podría ser una buena convención y puede crear un analizador para convertirlo []en una matriz.

el-un-tren
fuente
Este enfoque se ve bien, pero podría haber problemas con el uso de corchetes en las URL, vea what-characters-can-one-use-in-a-url
Sky
2
@Sky Esto podría evitarse codificando el URI [y ]. Usar representaciones codificadas de estos caracteres para agrupar parámetros de consulta es una práctica bien conocida. Incluso se usa en JSON: especificación API .
jelhan
6

No se preocupe demasiado si su API inicial es completamente RESTful o no (especialmente cuando solo está en las etapas alfa). Haga que la plomería de fondo funcione primero. Siempre puede hacer algún tipo de transformación / reescritura de URL para mapear las cosas, refinando iterativamente hasta que obtenga algo lo suficientemente estable como para realizar pruebas generalizadas ("beta").

Puede definir los URI cuyos parámetros están codificados por posición y convención en los mismos URI, precedidos por una ruta que sabe que siempre asignará a algo. No conozco PHP, pero supongo que existe tal facilidad (como existe en otros idiomas con marcos web):

.es decir. Realice una búsqueda de tipo "usuario" con param [i] = value [i] para i = 1..4 en la tienda # 1 (con value1, value2, value3, ... como una abreviatura para los parámetros de consulta URI):

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

o

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

o de la siguiente manera (aunque no lo recomendaría, más sobre eso más adelante)

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

Con la opción 1, asigna todos los URI prefijados /store1/search/useral controlador de búsqueda (o la designación de PHP) de manera predeterminada para realizar búsquedas de recursos en store1 (equivalente a /search?location=store1&type=user.

Por convención documentada y aplicada por la API, los valores de los parámetros 1 a 4 están separados por comas y se presentan en ese orden.

La opción 2 agrega el tipo de búsqueda (en este caso user) como parámetro posicional # 1. Cualquiera de las opciones es solo una elección cosmética.

La opción 3 también es posible, pero no creo que me gustaría. Creo que la capacidad de búsqueda dentro de ciertos recursos debe presentarse en el URI mismo que precede a la búsqueda en sí (como si indicara claramente en el URI que la búsqueda es específica dentro del recurso).

La ventaja de esto sobre el paso de parámetros en el URI es que la búsqueda es parte del URI (por lo tanto, trata una búsqueda como un recurso, un recurso cuyo contenido puede, y cambiará, con el tiempo). La desventaja es que el orden de los parámetros es obligatorio .

Una vez que haga algo como esto, puede usar GET, y sería un recurso de solo lectura (ya que no puede PUBLICAR ni PONER - se actualiza cuando se GET). También sería un recurso que solo llega a existir cuando se invoca.

También se podría agregar más semántica al almacenar en caché los resultados durante un período de tiempo o con un DELETE que hace que se elimine el caché. Sin embargo, esto podría ir en contra de lo que la gente suele usar DELETE (y porque las personas generalmente controlan el almacenamiento en caché con los encabezados de almacenamiento en caché).

La forma en que lo haga sería una decisión de diseño, pero esta sería la forma en que lo haría. No es perfecto, y estoy seguro de que habrá casos en los que hacer esto no sea lo mejor (especialmente para criterios de búsqueda muy complejos).

luis.espinal
fuente
77
Yo, si usted (alguien, quien sea / lo que sea) cosas apropiadas para desestimar mi respuesta, ¿le haría daño a su ego al menos poner un comentario que indique exactamente con qué no está de acuerdo? Sé que es el interweebz, pero ...;)
luis.espinal
107
No voté en contra, pero el hecho de que la pregunta comience con: "Actualmente estoy diseñando e implementando una API RESTful" y su respuesta comienza con "No se preocupe demasiado si su API inicial es completamente RESTful o no" mal para mi Si está diseñando una API, está diseñando una API. La pregunta es cómo diseñar mejor la API, no si la API debe diseñarse.
gardarh
14
La API es el sistema, funciona primero en la API, no en la tubería de back-end, la primera implementación podría / debería ser una imitación. HTTP tiene un mecanismo para pasar parámetros, está sugiriendo que se reinvente, pero peor (parámetros ordenados en lugar de pares de nombre y valor). De ahí el voto negativo.
Steven Herod
14
@gardarh: sí, se siente mal, pero a veces es pragmático. El objetivo principal es diseñar una API que funcione para el contexto empresarial en cuestión. Si un enfoque completamente RESTFULL es apropiado para el negocio en cuestión, entonces hágalo. Si no es así, entonces no lo intentes. Es decir, diseñe una API que cumpla con sus requisitos comerciales específicos. Intentar hacer que sea RESTfull como su requisito principal no es muy diferente de preguntar "¿cómo uso el patrón del adaptador en el problema X / Y"? No paradigmas de bocina a menos que resuelvan problemas reales y valiosos.
luis.espinal 01 de
1
Veo un recurso como una colección de estados y parámetros como un medio para manipular la representación de ese estado paramétricamente. Piénselo de esta manera, si pudiera usar perillas e interruptores para ajustar cómo se muestra el recurso (mostrar / ocultar ciertos bits, ordenarlo de manera diferente, etc.) esos controles son parámetros. Si en realidad es un recurso diferente ('/ álbumes' vs '/ artistas', por ejemplo), es entonces cuando debería representarse en la ruta. Eso es lo que es intuitivo para mí, de todos modos.
Eric Elliott
2

FYI: Sé que esto es un poco tarde, pero para cualquiera que esté interesado. Depende de cuán RESTful desee ser, tendrá que implementar sus propias estrategias de filtrado ya que la especificación HTTP no es muy clara al respecto. Me gustaría sugerir la codificación url de todos los parámetros de filtro, por ejemplo

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

Sé que es feo, pero creo que es la forma más RESTful de hacerlo y debería ser fácil de analizar en el lado del servidor :)

vástagos
fuente