Design RESTful query API con una larga lista de parámetros de consulta

Entonces, necesito diseñar una API de consulta RESTful, que devuelve un conjunto de objetos basado en algunos filtros. El método HTTP habitual para esto es GET. El único problema es que puede tener al menos una docena de filtros, y si pasamos todos ellos como parámetros de consulta, la URL puede ser bastante larga (lo suficiente como para ser bloqueada por algún firewall).

Reducir el número de parámetros no es una opción.

Una alternativa que podría pensar es hacer uso del método POST en el URI y enviar los filtros como parte del cuerpo POST. ¿Es esto contra ser RESTfull (Hacer una llamada POST para consultar datos).

¿Alguien tiene mejores sugerencias de diseño?

Gracias

Recuerde que con una API REST, todo es una cuestión de su punto de vista.

Los dos conceptos clave en una API REST son los puntos finales y los recursos (entidades). En términos generales, un punto final devuelve recursos a través de GET o acepta recursos a través de POST y PUT, y así sucesivamente (o una combinación de los anteriores).

Se acepta que con POST, los datos que envíe pueden o no dar como resultado la creación de un nuevo recurso y su (s) punto (s) extremo (s) asociado (s), que probablemente no “vivan” bajo la url POSTed. En otras palabras, cuando realiza una POST, envía datos a algún lugar para su manejo. El punto final POST no es donde normalmente se puede encontrar el recurso.

Citando de RFC 2616 (con las partes irrelevantes omitidas, y las partes relevantes resaltadas):

9.5 POST

El método POST se utiliza para solicitar que el servidor de origen acepte la entidad incluida en la solicitud como un nuevo subordinado del recurso identificado por el URI de solicitud en la línea de solicitud. POST está diseñado para permitir un método uniforme para cubrir las siguientes funciones:

  • Proporcionar un bloque de datos, como el resultado de enviar un formulario, a un proceso de manejo de datos;

La acción realizada por el método POST podría no dar como resultado un recurso que pueda identificarse mediante un URI . En este caso, 200 (OK) o 204 (Sin contenido) es el estado de respuesta apropiado, dependiendo de si la respuesta incluye o no una entidad que describa el resultado .

Si se ha creado un recurso en el servidor de origen, la respuesta DEBERÍA ser 201 (Creado) …

Nos hemos acostumbrado a los puntos finales y los recursos que representan “cosas” o “datos”, ya sea un usuario, un mensaje o un libro, sea lo que sea que dicte el dominio del problema. Sin embargo, un punto final también puede exponer un recurso diferente, por ejemplo, resultados de búsqueda.

Considere el siguiente ejemplo:

GET /books?author=AUTHOR POST /books PUT /books/ID DELETE /books/ID 

Este es un REST CRUD típico. Sin embargo, qué sucede si agregamos:

 POST /books/search { "keywords": "...", "yearRange": {"from": 1945, "to": 2003}, "genre": "..." } 

No hay nada sin RESTful sobre este punto final. Acepta datos (entidad) en la forma del cuerpo de la solicitud. Esa información es el criterio de búsqueda , una DTO como cualquier otra. Este punto final produce un recurso (entidad) en respuesta a la solicitud: Resultados de la búsqueda . El recurso de resultados de búsqueda es temporal, se sirve inmediatamente al cliente, sin una redirección, y sin estar expuesto desde alguna otra URL canónica.

Sigue siendo REST, excepto que las entidades no son libros, la entidad de solicitud es el criterio de búsqueda de libros y la entidad de respuesta son los resultados de búsqueda de libros.

Mucha gente ha aceptado la práctica de que un GET con una cadena de consulta demasiado larga o demasiado compleja (por ejemplo, las cadenas de consulta no manejan datos nesteds fácilmente) puede enviarse como un POST en su lugar, con los datos complejos / largos representados en el cuerpo de la solicitud

Busque las especificaciones para POST en la especificación HTTP. Es increíblemente amplio. (Si quieres navegar un barco de guerra por un resquicio en REST … usa POST.)

Perderá algunos de los beneficios de la semántica GET … como rebashs automáticos porque GET es idempotente, pero si puede vivir con eso, podría ser más fácil simplemente aceptar el procesamiento de consultas realmente largas o complicadas con POST.

(jajaja larga digresión … Recientemente descubrí que según las especificaciones de HTTP, GET puede contener un cuerpo de documento. Hay una sección que dice, parafraseando, “Cualquier solicitud puede tener un cuerpo de documento excepto los enumerados en esta sección” … y la sección a la que se refiere no enumera ninguna. Busqué y encontré un hilo donde los autores HTTP estaban hablando de eso, y fue intencional, por lo que los enrutadores y demás no tendrían que diferenciar entre diferentes mensajes. practique muchas piezas de infraestructura, deje caer el cuerpo de un GET. Así podría GET con filtros representados en el cuerpo, como POST, pero estaría tirando los dados).

En pocas palabras: realice un POST pero anule el método HTTP usando el encabezado X-HTTP-Method-Override .

Solicitud real

POST / libros

Cuerpo de la entidad

{“título”: “Ipsum”, “año”: 2017}

Encabezados

X-HTTP-Method-Override: GET

En el lado del servidor, compruebe si existe el encabezado X-HTTP-Method-Override y luego tome su valor como el método para construir la ruta hasta el punto final final en el back-end. Además, tome el cuerpo de entidad como la cadena de consulta. Desde un punto de vista de back-end, la solicitud se convirtió en un simple GET.

De esta forma, mantendrá el diseño en armonía con los principios de REST.

Editar: Sé que esta solución fue originalmente pensada para resolver el problema del verbo PATCH en algunos navegadores y servidores pero también funciona para mí con el verbo GET en el caso de una URL muy larga que es el problema descrito en la pregunta.

Si está desarrollando en Java y JAX-RS, le recomiendo que use @QueryParam con @GET

Tenía la misma pregunta cuando necesitaba revisar una lista.

Ver ejemplo:

 import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; @Path("/poc") public class UserService { @GET @Path("/test/") @Produces(MediaType.APPLICATION_JSON) public Response test(@QueryParam("code") final List code) { Integer int0 = codigo.get(0); Integer int1 = codigo.get(1); return Response.ok(new JSONObject().put("int01", int0)).build(); } } 

Patrón de URI: “poc / test? Code = 1 & code = 2 & code = 3

@QueryParam convertirá automáticamente el parámetro de consulta “orderBy = age & orderBy = name” en java.util.List.