Estás viendo una versión antigua de esta página. Ve a la versión actual.

Comparar con el actual Ver el historial de la página

« Anterior Versión 22 Siguiente »


Obtener token en métodos REST

Para ver cómo obtener tanto el token POSE como el token OAuth, podemos verlo en la guía de login: Login con OAuth en Spring Boot

Documentar APIs

Se deben documentar las APIs con OpenAPI, para lo que tenemos otra guía: Documentar APIs con OPENAPI

Buenas prácticas

Para crear nuestros servicios REST debemos seguir las buenas prácticas que están definidas en esta página de la wiki: Buenas prácticas con servicios REST

Controlador REST en Spring Boot

En Spring Boot, las peticiones HTTP se gestionan por medio de controladores. En nuestro caso la clase que hará de controlador REST deberá estar precedida por la anotación @RestController.

Para los métodos que contendrán el código a ejecutar al recibir peticiones disponemos de las etiquetas @GetMapping@PostMapping@PutMapping @DeleteMapping, para los diferentes tipos de peticiones. En estas anotaciones pondremos la ruta a la que tendrá que hacerse cada petición. Por defecto, estas rutas irán después del dominio. Si queremos añadir una ruta base de la que partan todas las rutas de estos métodos podemos añadirlo en el archivo application.properties:

server.servlet.context-path=/rest/v1.0

Veamos un ejemplo sencillo:

package com.example.restservice;
 
import java.util.concurrent.atomic.AtomicLong;
 
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class GreetingController {
 
	private static final String template = "Hello, %s!";
	private final AtomicLong counter = new AtomicLong();
 
	@GetMapping("/greeting")
	public ResponseEntity<Greeting> greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
		Greeting greeting = new Greeting(counter.incrementAndGet(), String.format(template, name));
                return new ResponseEntity<>(greeting, HttpStatus.OK);
	}
}

Parámetros en la URL

@PathVariable

Para añadir parámetros en la ruta de la petición, tenemos dos opciones. En primer lugar, tenemos PathVariable. Cuando especifiquemos la ruta, pondremos entre corchetes estas variables, y como parámetro del método Java lo especificaremos con el mismo nombre, precedido de la anotación @PathVariable:

@GetMapping("/titulaciones/{dni}")
public List<AlumnoTitulacionDTO> getTitulaciones(@PathVariable String dni) {
    // ...
}

Un ejemplo de petición quedaría así (suponiendo que tenemos la aplicación corriendo en localhost:8080):

http://localhost:8080/titulaciones/12312300A

@RequestParam

La otra opción es RequestParam, que funciona de manera similar, pero sólo se indica en los parámetros de la función Java. Si queremos que sea obligatorio incluir esa variable en la petición, pondremos required = true:

@GetMapping("/titulaciones")
public List<AlumnoTitulacionDTO> getTitulaciones(@RequestParam(required = true) String dni) {
    // ...
}

Y ahora la petición sería la siguiente:

http://localhost:8080/titulaciones?dni=12312300A

Estos parámetros irán después de ?, y si tenemos varios los separaremos con &.

Métodos REST con mismo nombre pero diferentes parámetros

Podemos definir diferentes métodos Java con la misma ruta de método REST, pero con diferentes @RequestParam. Para ello, no nos vale sólo con tener diferentes argumentos, hay que indicarlos dentro de la anotación @GetMapping, con params, y tendremos que tener la ruta en value:

@GetMapping("/usuarios")
public List<UsuarioDTO> usuarios() {
  // ...
}
 
@GetMapping(value = "/usuarios", params = "login")
public UsuarioDTO usuariosLogin(@RequestParam login) {
  // ...
}

De este modo podemos evitar tener métodos Java excesivamente largos por tener que comprobar diferentes parámetros y ejecutar el código correspondiente.

Headers de la petición: @RequestHeader

Para utilizar los valores de los headers de la petición, disponemos de la anotación @RequestHeader. Dentro de ésta pondremos el nombre del header en value, y podemos especificar si es obligatorio que se incluya dicho header con required. Por ejemplo, podemos utilizarlo para obtener el token de autenticación:

@GetMapping( "/dniAlumno" )
public ResponseEntity<String> dniAlumno( @RequestHeader( value = "auth-token", required = true ) String tokenCodificado ) {
	final Claims claims = jwtTokenUtil.comprobarToken( tokenCodificado );
	if(claims == null)
		return null;
 
	return new ResponseEntity<>(alumnoTitulacionRepository.findDni( claims.getSubject() ), HttpStatus.OK);
}

Datos en el body: @RequestBody

En caso de que hagamos un post, el cuerpo del mensaje se especificará con ''@RequestBody'':

@PostMapping("/borrarUsuario")
public void borrarUsuario(@RequestBody UsuarioDTO usuario) {
    usuarioRepo.delete(new Usuario(usuario));
}


La respuesta que se enviará a la petición, si la hay, será el objeto, con sus atributos y valores, automáticamente en formato JSON.

Devolver ResponseEntity o ResponseStatusException

En los métodos en los que devolvamos datos, si se devuelven los datos correctamente devolveremos objetos tipo ResponseEntity. Este tipo nos da más flexibilidad, permitiendo modificar la respuesta HTTP entera, controlando el código que se devuelve, los headers y los datos del body.

Un ejemplo de uso sería el siguiente. Para crear un ResponseEntity que devuelva un objeto UsuarioDTO, y código OK 200:

@GetMapping(value = "/usuarios", params = "login")
public ResponseEntity<UsuarioDTO> usuariosLogin(@RequestParam login) {
  // ...
  return new ResponseEntity<>(usuarioDto, HttpStatus.OK);
}

En el caso de que queramos devolver un error, por ejemplo, un Forbidden porque el usuario que ha solicitado un recurso no tiene acceso, lanzaremos una excepción ResponseStatusException, pues esta nos ofrece más información en la respuesta que se da. 

} catch (Exception e) {
    throw new ResponseStatusException( HttpStatus.FORBIDDEN, "FORBIDDEN con ResponseStatusException", e);
}

Devolver diferentes propiedades

La primera opción que tenemos para personalizar la respuesta es la anotación @JsonIgnore que le podemos poner a los getters. Con ella, esa propiedad directamente no se incluirá en el JSON de respuesta. Si lo que queremos es devolver propiedades según la ocasión, podemos hacer uso de las vistas.

Para esto, en primer lugar, debemos crear una clase con los diferentes nombres de vistas que queramos utilizar como clases estáticas vacías:

public class Views {
    public static class Id {}
    public static class Tabla extends Id {}
    public static class Detalle extends Id {}
}

Después, en el modelo, en el getter de cada propiedad que queramos mostrar con cada vista, debemos añadir la etiqueta @JsonView(Views.Detalle.class), poniendo la clase correspondiente:

@JsonView(Views.Detalle.class)
public ResponseEntity<String> getChkExcepciones() {
    // ...
}

Por último, en el controlador REST, debemos etiquetar el método en el que queramos devolver las propiedades de una vista de la misma manera. Un ejemplo de código de un método completo sería este:

@JsonView(Views.Detalle.class)
public ResponseEntity<ChkLogDTO> getChkLogId (@PathVariable(required = true) Long id) {
    // ...
}

Como en los servicios REST debemos devolver el DTO, y no la entidad, sólo será necesario incluir la etiqueta @JsonView en las clases DTO.

Más información sobre las Views aquí.

Cliente REST en Spring Boot

Para consumir otro servicio REST desde el propio backend, a falta de realizar más pruebas, la forma que tendremos de hacerlo será utilizando RestTemplate:, que lo tendremos como un bean en una clase de configuración que tendremos que crear:

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
	return new RestTemplate();
    }
}

Después, en la clase en la que vayamos a utilizarlo, sólo tendremos que cargarlo con Autowired:

@Autowired
private RestTemplate restTemplate;

Para utilizarlo, formamos el URI con UriComponentsBuilder:

// Uri
final URI uri = UriComponentsBuilder.fromHttpUrl( url ).path( "/" + "pidAplicationid" ).path( "/firma" )
		.queryParam( "parametro", "valor" ).queryParam( "otroParametro", "otroValor" ).build().encode().toUri();

Con .path, se concatena lo que indiquemos al path, y con .queryParam indicamos los parámetros que se pasan por la propia url.

Podemos añadir headers con HttpHeaders:

// HEADERS
final HttpHeaders headers = new HttpHeaders();
// Ponemos el tipo del contenido que irá en el body
headers.setContentType( MediaType.APPLICATION_JSON );
// Para indicar el accept, pide una lista de MediaType
final ArrayList<MediaType> listaAccept = new ArrayList<>();
listaAccept.add( MediaType.APPLICATION_JSON );
headers.setAccept( listaAccept );
// Añadimos los headers que queramos con nombre-valor de esta manera
headers.add( "X-Client-Id", ficha.getAplicacionId );

Los headers los incluiremos en un objeto HttpEntity, junto con el objeto a pasar si se pasa alguno:

// HttpEntity
final HttpEntity<String> httpEntity = new HttpEntity<>( objeto, headers );

En el caso de que sólo queramos pasar los headers, HttpEntity dispone de un constructor para ello, con el que no haría falta parametrizar el tipo del objeto que se le pasa como cuerpo del mensaje, pues no habrá ninguno. La sintaxis sería la siguiente:

final HttpEntity<?> httpEntity = new HttpEntity<>( headers );

Para lanzar las peticiones tenemos diferentes opciones. En primer lugar, disponemos de cuatro métodos básicos para hacer GET, POST, PUT, DELETE:

ResponseEntity<String> response1 = restTemplate.getForEntity( uri, String.class );
ResponseEntity<String> response2 = restTemplate.postForEntity( uri, httpEntity, String.class );
restTemplate.put( uri, httpEntity );
restTemplate.delete( uri );

Para las operaciones básicas estos métodos pueden ser útiles, pero como vemos, solo podemos pasar el objeto HttpEntity haciendo POST, pero podemos necesitar añadir headers en un GET para, por ejemplo, pasar un token de autenticación. Para hacer esto, podemos utilizar el método .exchange, al que tenemos que indicarle la Uri, el método http a utilizar, el objeto HttpEntity a pasar (puede ser null) y la clase del objeto que se espera recibir:

final ResponseEntity<String> response = restTemplate.exchange( uri, HttpMethod.GET, httpEntity, String.class );

Como vemos, estamos utilizando el tipo ResponseEntity. RestTemplate tiene métodos para obtener directamente el objeto del tipo que se espera, pero ResponseEntity nos permite explorar todos los detalles del mensaje, y podemos obtener el objeto correspondiente con .getBody().

  • Sin etiquetas