Response y ResponseBuilder

Con Response y ResponseBuilder podemos fabricar respuestas con códigos de estado personalizados y agregando cabeceras de respuesta específicas. Es algo que querremos hacer cuando tengamos que entregar algo que no sea HTTP 200, como por ejemplo prevenir errores en nuestra aplicación.

Quarkus (JAX-RS, mejor dicho) tiene las clases Response y ResponseBuilder para fabricar de una manera más precisa objetos de tipo Response, con los que podemos controlar la respuesta que se envía al consumidor de la API o navegador web, tal como el código de estado que se está usando, y cabeceras como Content-Type, Variant o similares, incluyendo la posibilidad de insertar la nuestra propia.

Cualquiera de los métodos de un controlador puede devolver un objeto de tipo Response en vez de un objeto de lógica de negocio de nuestra aplicación, y Quarkus generará una respuesta HTTP a partir de la forma en la que ese Response haya sido configurado.

La manera típica de fabricar una Response será usar alguno de los métodos estáticos de esa clase.

  • Tenemos métodos como Response.status(), que aceptan como parámetro directamente el número de estado que queremos devolver, como Response.status(200), Response.status(400) o Response.status(422).
  • También tenemos el enumerado Response.Status por si queremos especificarlo con constantes, como Response.status(Status.ACCEPTED) o Response.status(Status.UNAUTHORIZED).
  • Y si no, la propia clase Response tiene más métodos estáticos para empezar a fabricar directamente una respuesta sin especificar el código de estado, como Response.notModified(), que es lo mismo que llamar a Response.status(Response.NOT_MODIFIED) o a Response.accepted(), que es lo mismo que llamar a Response.status(Response.ACCEPTED).

Todos estos métodos devuelven una instancia de ResponseBuilder. Se trata de una clase que nos va a permitir acoplar datos a la respuesta.

El cuerpo de la respuesta se lo podemos dar con el método entity() de ResponseBuilder. Este método acepta un objeto que será convertido al tipo de datos de salida usando Jackson o JSON-B y que representa la entidad de la respuesta. Por ejemplo, en el siguiente caso fabricaríamos una respuesta de tipo 200 OK que codifique una lista de valores:

List<Temperatura> lista = temperaturas.obtener();
var respuesta = Response.status(200).entity(lista);

En el siguiente caso, la entidad es un string:

var respuesta = Response.status(401).entity("No tienes permiso")

Y en el siguiente caso, la entidad podría ser un objeto DTO:

var error = new ErrorDto(
  /* title: */ "Error de validación"
  /* detail: */ "Se esperaba un nombre de ciudad, pero estaba vacío"
);
var respuesta = Response.status(422).entity(error);

Además del cuerpo, podemos codificar cabeceras adicionales con el método header():

Response.status(200)
  .encode(medicion)
  .header("X-RateLimit-Limit", 1000)
  .header("X-RateLimit-Remaining", 900)
  .header("X-RateLimit-Reset", 1690198990)

En este caso le agregaríamos a la respuesta tres cabeceras con el nombre dado como primer parámetro y con valor dado como segundo parámetro. Fíjate que la interfaz es fluent y se puede encadenar. Los métodos devuelven el mismo Builder así que meter más cabeceras es cosa de seguir llamando métodos.

También se puede ir más rápido usando alguno de los métodos predefinidos que registran cabeceras:

var response = Response.ok()
  .cacheControl(new CacheControl())
  .expires(expirationTimestamp);

La ventaja de estos métodos es que normalmente dejan trabajar con clases más útiles. expires() acepta un objeto de tipo java.util.Date y lo formatea para colocarlo en la cabecera Expires con el formato que especifica el estándar HTTP. De otro modo lo tendríamos que hacer a mano. Lo mismo se puede decir, por ejemplo, de las cookies.

Finalmente, llamamos a .build() para generar un objeto Response ya constituido. Este objeto es lo que podemos devolver en nuestro controlador.

Por ejemplo, esto lo podríamos usar para condicionalmente fabricar una respuesta u otra:

@GET
@Path("/maxima")
public Response maxima() {
  if (temperaturas.vacio()) {
    return Response.status(404).entity("No hay mediciones").build();
  } else {
    return Response.ok(temperaturas.maxima()).build();
  }
}

Como ves, otro alias para codificar una entidad es Response.ok(e), que es equivalente a hacer Response.status(200).entity(e). En este caso, según si el método vacio() del servicio de temperaturas devuelve verdadero o falso, se codfiica un HTTP 404 que sólo tiene como cuerpo No hay mediciones, o bien un HTTP 200 que tiene como cuerpo la representación a JSON (o XML) de la ciudad con la mayor medición.

Lista de reproducción
  1. 1
    Qué es Quarkus y cómo crear un proyecto
    8 minutos
  2. 2
    ¿Cómo crear endpoints en Quarkus?
    7 minutos
  3. 3
    Paso de parámetros con PathParam y QueryParam
    8 minutos
  4. 4
    Retorno de objetos JSON
    7 minutos
  5. 5
    Definir un endpoint POST
    7 minutos
  6. 6
    Servicios e inyección de dependencia
    8 minutos
  7. 7
    Response y ResponseBuilder
    8 minutos
  8. 8
    ExceptionMapper y tratamiento de errores
    9 minutos