Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado del repositorio vacío a una url pública. En Servidor continúas la implementación del mismo producto.
Se explica
25 minutos · explicación y demostración
Ya tienes rutas que devuelven un texto fijo. Hoy el cliente enviará datos en la URL y un mismo método responderá según esos datos. Un parámetro es un valor de entrada del método; Spring lo obtiene de la ruta o de la consulta. Todavía devolveremos texto, sin buscar registros reales.
Recibir datos distintos en una misma ruta
El endpoint de ayer siempre responde lo mismo:
@GetMapping("/hola")
public String hola() {
return "Hola, mundo. Te responde mi servidor.";
}
Si mantuviéramos una respuesta fija, no podríamos elegir a quién saludar ni qué incidencia consultar. Para reutilizar el método, el cliente enviará un dato dentro de la petición y el método lo utilizará al construir su respuesta.
Hoy vemos las dos formas de hacerlo con un GET.
- En la ruta:
/usuarios/3— el dato forma parte del camino - En la query string:
/usuarios?rol=admin— el dato va detrás de la interrogación
Se parecen, pero no significan lo mismo, y elegir mal es el origen de la mitad de las APIs incómodas de usar. Al final de la sesión tendrás una regla para decidir.
La query string, por dentro
Es lo que va después del ?. Son pares clave=valor separados por &:
/incidencias?estado=abierta&prioridad=alta&pagina=2
? empieza la query string
estado=abierta primer par
& separador
prioridad=alta segundo par
& separador
pagina=2 tercer par
Tres cosas que conviene saber desde hoy:
- El orden no importa:
?a=1&b=2y?b=2&a=1son la misma petición. - Todo llega como texto.
pagina=2no constituye un número, sino la cadena"2". Que acabe siendo uninten tu método es trabajo de Spring, no del navegador. - Los caracteres raros se codifican. Un espacio viaja como
%20o como+, y unañcomo%C3%B1. Lo verás en el panel de red y no debe alarmarte.
La regla para decidir dónde va cada dato
Ruta o query string
En la ruta va lo que identifica al recurso. Sin ese dato, la petición no tiene sentido: /usuarios/3 pregunta por un usuario concreto, y /usuarios/ a secas ya es otra cosa distinta.
En la query string va lo que modifica una consulta. Filtros, orden, paginación, búsqueda. Si lo quitas, la petición sigue teniendo sentido: solo devuelve más resultados o en otro orden.
La prueba rápida: ¿puedo borrar este dato de la URL y que siga significando algo? Si sí, es query string. Si no, es ruta.
| URL | Correcto | Por qué |
|---|---|---|
/usuarios/3 |
Sí | El 3 identifica al usuario |
/usuarios?id=3 |
Mejorable | Un identificador no es un filtro |
/incidencias?estado=abierta |
Sí | Es un filtro sobre una lista |
/incidencias/abierta |
No | Parece una incidencia llamada «abierta» |
/proyectos/7/incidencias?prioridad=alta |
Sí | Identifica el proyecto y filtra sus incidencias |
Esa última fila combina las dos ideas, y es la forma que tendrá casi toda tu API a partir de la UD3.
Se trabaja
140 minutos · implementación guiada sobre el proyecto propio
Paso 1 · Abrir el proyecto y comprobar el punto de partida · 10 min
Antes de añadir rutas, comprueba que puedes ejecutar la versión de la sesión anterior. En este paso trabajarás con HolaController; crearás SaludoController en el paso 2.
- En el IDE, abre la carpeta del proyecto que contiene
pom.xml. Usa tu carpeta habitual de trabajo. Si hiciste la tarea de clonar el repositorio para comprobarlo, evita abrir a la vez las dos copias: podrías editar una y ejecutar la otra. - Dentro de
src/main/java, despliega tu paquete base y su carpetacontroller. AbreHolaController.java. Localiza el método con@GetMapping("/hola")y lee sureturn: ese es el texto que vas a comprobar. Si elegiste otro paquete base, utiliza su carpeta y conserva elpackagede tus clases. - Arranca la aplicación desde la terminal situada en la carpeta de
pom.xml, con el comando correspondiente a tu sistema:
Windows · PowerShell
.\mvnw.cmd spring-boot:run
Linux o macOS
./mvnw spring-boot:run
Si ya está ejecutándose, detén esa ejecución antes de volver a arrancar. Puedes usar Stop si la lanzaste desde el IDE o Ctrl+C en la terminal donde se está ejecutando.
- Espera al mensaje
Started ...Applicationy abrehttp://localhost:8080/holaen el navegador. Debe aparecer la frase delreturnde tu método, incluida la personalización que hiciste en la sesión 1. Si cambiaste el puerto, úsalo en todas las URL de esta sesión. - En la tabla de comprobaciones que guardaste con el proyecto, anota la URL y el texto observado. Para ver también el estado HTTP, abre F12 → Red/Network, recarga la página y selecciona la petición a
hola: debe mostrar 200.
Antes de continuar: el servidor sigue ejecutándose y /hola responde con tu texto. Si no conecta, revisa el arranque; si devuelve 404, revisa la ruta y que estés ejecutando el proyecto correcto. Los identificadores y filtros de tu aplicación se elegirán al aplicar los ejemplos en el paso 5.
Paso 2 · @RequestParam · leer la query string · 30 min
Vas a realizar tres cambios en orden: crear una clase con un saludo, modificar ese saludo para darle un valor por defecto y añadir una consulta con dos parámetros. Prueba cada versión antes de pasar a la siguiente. HolaController se conserva.
2.1 · Crear el saludo con un parámetro obligatorio
Crea SaludoController.java en la misma carpeta que HolaController.java. Con el paquete del ejemplo, la ruta completa es:
src/main/java/com/ejemplo/gestor/controller/SaludoController.java
Copia esta clase completa; si tu paquete base es distinto, adapta la primera línea para que coincida con el de HolaController. Si el archivo ya existe, revisa su contenido en lugar de crear otra clase con el mismo nombre.
package com.ejemplo.gestor.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SaludoController {
@GetMapping("/saludo")
public String saludo(@RequestParam(name = "nombre") String nombre) {
return "Hola, " + nombre + ".";
}
}
Guarda el archivo, detén la aplicación y vuelve a arrancarla como en el paso 1. Repite esa secuencia después de cada cambio de Java: recargar el navegador solo envía otra petición, no ejecuta el código que acabas de editar.
Abre estas URL, una cada vez, y registra el estado HTTP y la respuesta. Cambiar la URL no requiere reiniciar el servidor.
| URL completa | Estado esperado | Respuesta o motivo |
|---|---|---|
http://localhost:8080/saludo?nombre=Marc |
200 | Hola, Marc. |
http://localhost:8080/saludo?nombre=Ana |
200 | Hola, Ana. |
http://localhost:8080/saludo |
400 | Falta el parámetro obligatorio nombre. |
http://localhost:8080/saludo?nombre= |
200 | Hola, . El parámetro está presente, pero su texto está vacío. |
- Llega
GET /saludo?nombre=Marc - Spring localiza el método de
/saludo @RequestParam(name = "nombre")indica qué dato leer- Spring entrega
"Marc"al parámetro Javanombre - El método devuelve
Hola, Marc.
@RequestParam es obligatorio por defecto. Cuando falta nombre, Spring responde 400 Bad Request antes de ejecutar saludo. En el navegador puedes ver una página HTML de error; otro cliente que solicite JSON puede recibir un objeto con status: 400. Comprueba el estado en Red/Network, sin esperar una apariencia concreta del cuerpo. La consola del servidor también puede indicar que falta nombre.
La petición con ?nombre= permite distinguir ausencia de texto vacío: exigir que el parámetro exista no exige que contenga un nombre válido.
Nombre del parámetro de consulta
En @RequestParam(name = "nombre"), name indica la clave que Spring debe buscar en la URL. Escribirla explícitamente evita depender de que la compilación conserve el nombre del parámetro Java.
2.2 · Sustituir el método para añadir un valor por defecto
En el mismo archivo SaludoController.java, sustituye únicamente el bloque que empieza en @GetMapping("/saludo") y termina en la llave de cierre del método saludo por este:
@GetMapping("/saludo")
public String saludo(
@RequestParam(name = "nombre", defaultValue = "mundo") String nombre) {
return "Hola, " + nombre + ".";
}
Conserva package, los tres imports, @RestController, la declaración public class SaludoController y su última llave. Debe quedar un solo método para GET /saludo. Guarda, detén y arranca de nuevo la aplicación; después repite estas pruebas:
| URL completa | Estado esperado | Respuesta |
|---|---|---|
http://localhost:8080/saludo?nombre=Marc |
200 | Hola, Marc. |
http://localhost:8080/saludo |
200 | Hola, mundo. |
http://localhost:8080/saludo?nombre= |
200 | Hola, mundo. |
Compara con la versión anterior: la petición sin nombre deja de producir 400 y la petición con nombre= deja de devolver un saludo vacío. defaultValue proporciona "mundo" en ambos casos y hace que el parámetro sea opcional. Este comportamiento está definido en la documentación de RequestParam. Un texto formado por espacios requiere comprobar su contenido; no equivale a enviar nombre=.
Consulta · parámetro opcional sin valor por defecto
La opción required = false permite omitir el parámetro sin asignarle un texto alternativo:
@RequestParam(name = "nombre", required = false) String nombre
Este fragmento representa la declaración del parámetro de un método. Si no llega, su valor será null y el método tendrá que decidir cómo tratarlo. Es otra opción de la anotación; para continuar esta práctica conserva la versión con defaultValue = "mundo".
2.3 · Añadir una consulta con dos parámetros
Ahora añade el siguiente método buscar dentro de SaludoController, debajo del método saludo y antes de la última llave de la clase. Conserva saludo: atienden rutas distintas. Los imports que ya tienes sirven para ambos métodos. En esta práctica reunimos los ejemplos en la misma clase; el paso 4 enseñará a agrupar las rutas por recurso.
@GetMapping("/incidencias")
public String buscar(
@RequestParam(name = "estado", defaultValue = "todas") String estado,
@RequestParam(name = "pagina", defaultValue = "1") int pagina) {
return "Buscando incidencias con estado " + estado
+ ", página " + pagina;
}
Si ya programaste un GET /incidencias en otro controlador, elige cuál conservar antes de arrancar: dos métodos registrados para la misma petición pueden impedir el arranque por un mapeo ambiguo (ambiguous mapping).
Guarda, detén y vuelve a arrancar. Comprueba las tres peticiones:
| URL completa | Estado esperado | Respuesta o motivo |
|---|---|---|
http://localhost:8080/incidencias?estado=abierta&pagina=3 |
200 | Buscando incidencias con estado abierta, página 3 |
http://localhost:8080/incidencias |
200 | Buscando incidencias con estado todas, página 1 |
http://localhost:8080/incidencias?pagina=abc |
400 | Spring no puede convertir abc al tipo int de pagina. |
En la primera petición, & separa los dos parámetros: estado recibe "abierta" y pagina recibe el entero 3. En la última, la conversión falla antes de ejecutar buscar. Un entero negativo sí puede convertirse a int; decidir si es una página admisible requiere una validación adicional.
Al terminar el paso 2: HolaController conserva /hola. SaludoController contiene dos métodos: saludo, con valor por defecto, y buscar, con estado y pagina. Guarda en tu tabla las comprobaciones de ambas versiones del saludo y de la consulta. Estos métodos devuelven texto construido con los parámetros recibidos; todavía no consultan incidencias ni aplican paginación sobre datos reales.
Paso 2-bis · Otros tipos que Spring convierte · 15 min
El paso anterior ha convertido "3" en un int. El mecanismo no se limita a los números: Spring construye el tipo declarado siempre que sepa interpretar el texto recibido. Añade este método a SaludoController, con import java.time.LocalDate; entre los imports.
@GetMapping("/informes")
public String informes(
@RequestParam(name = "desde") LocalDate desde,
@RequestParam(name = "activo", defaultValue = "true") boolean activo) {
return "Desde " + desde + " (día " + desde.getDayOfMonth()
+ " del mes " + desde.getMonthValue() + "), activo=" + activo;
}
Reinicia y comprueba las cuatro peticiones:
| Petición | Respuesta |
|---|---|
/informes?desde=2026-09-13 |
200 · el booleano toma su valor por defecto |
/informes?desde=2026-09-13&activo=false |
200 · el booleano llega convertido desde el texto |
/informes?desde=13-09-2026 |
400 · el formato no es el que LocalDate interpreta |
/informes |
400 · falta un parámetro declarado como obligatorio |
Dos observaciones sobre lo que acaba de ocurrir:
desde.getMonthValue()devuelve9. Lo que recibe el método no es el texto de la URL, sino un objetoLocalDatecon sus operaciones disponibles. La conversión no es un cambio de apariencia.- El formato que se interpreta por defecto es el de la norma ISO 8601,
aaaa-mm-dd. Una fecha escrita como13-09-2026produce un400por el mismo motivo quepagina=abc: la petición no encaja con lo declarado y se rechaza antes de ejecutar el método. Cuando un cliente imponga otro formato, habrá que declararlo de forma explícita en el parámetro.
Paso 3 · @PathVariable · leer un trozo de la ruta · 20 min
Crea UsuarioController.java con el bloque completo. Las llaves de {id} pertenecen a la anotación Java: en el navegador escribe un valor concreto, como /usuarios/3. El método de rutas anidadas se añade dentro de esa clase, antes de su última llave; conserva los métodos existentes mientras las combinaciones de método y ruta sean distintas. Prueba un número y luego texto para observar la conversión a int.
package com.ejemplo.gestor.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UsuarioController {
@GetMapping("/usuarios/{id}")
public String usuario(@PathVariable(name = "id") int id) {
return "Ficha del usuario " + id;
}
}
Prueba /usuarios/3, /usuarios/41, /usuarios/999. Un solo método admite esos tres números y devuelve un texto con cada uno. Todavía no consulta una colección, por lo que no comprueba si existe ese usuario.
Las llaves marcan un hueco: {id} no es texto literal, es una variable. Lo que aparezca ahí se captura y se entrega al parámetro que lleva ese mismo nombre.
El nombre tiene que coincidir
El texto entre llaves de la ruta y el name de la anotación deben ser idénticos. Si escribes /usuarios/{id} y anotas @PathVariable(name = "identificador"), Spring no podrá resolver ese argumento al atender la petición. Corrige la coincidencia entre ambos nombres; no lo confundas con un error de conversión del valor.
Puede haber varias, y se anidan con toda naturalidad:
@GetMapping("/proyectos/{proyectoId}/incidencias/{incidenciaId}")
public String incidenciaDeProyecto(
@PathVariable(name = "proyectoId") int proyectoId,
@PathVariable(name = "incidenciaId") int incidenciaId) {
return "Incidencia " + incidenciaId + " del proyecto " + proyectoId;
}
/proyectos/7/incidencias/41 se lee de un vistazo: la incidencia 41, que pertenece al proyecto 7. Esa legibilidad no es casualidad, y es justo lo que vamos a convertir en regla ahora.
Paso 4 · Agrupar rutas con @RequestMapping · 15 min
Sustituye la versión anterior de UsuarioController por la clase que aparece aquí. Hemos movido el prefijo común a @RequestMapping: comprueba que los métodos ya no repiten /usuarios, o la dirección resultante sería /usuarios/usuarios/.... Después de reiniciar, vuelve a probar listado y detalle para comprobar que la agrupación no ha cambiado sus direcciones.
package com.ejemplo.gestor.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/usuarios")
public class UsuarioController {
@GetMapping
public String lista(
@RequestParam(name = "rol", defaultValue = "todos") String rol) {
return "Lista de usuarios con rol " + rol;
}
@GetMapping("/{id}")
public String detalle(@PathVariable(name = "id") int id) {
return "Ficha del usuario " + id;
}
}
La ruta final es la del @RequestMapping de la clase más la del método:
| Método | Ruta que atiende |
|---|---|
lista |
/usuarios |
detalle |
/usuarios/3 |
Un @GetMapping sin argumento designa la ruta declarada en la clase, sin segmento adicional.
Curiosidad · qué pasa si dos rutas encajan a la vez
Imagina que añades /usuarios/nuevo y ya tienes /usuarios/{id}. Una petición a /usuarios/nuevo encaja con las dos.
Spring no elige al azar ni por orden de escritura: prefiere siempre la ruta más específica, y un texto literal es más específico que una variable. Gana /usuarios/nuevo.
Aun así, mezclar identificadores y palabras en el mismo nivel envejece mal. Cuando lleguemos al diseño REST de la UD3 veremos por qué se evita.
Paso 5 · Las rutas del gestor · 30 min
Construye el controlador de tu entidad principal siguiendo este orden: crea la clase en controller, anótala con @RestController y @RequestMapping, añade el listado y compruébalo; después añade el detalle y compruébalo; por último incorpora los parámetros opcionales y las rutas anidadas. El controlador ProyectoController de la tabla es el patrón: en reservas sería ReservaController y /reservas. Mantén tus nombres en todos los pasos posteriores. Hoy cada método devuelve una frase con los parámetros, no una lista de registros reales.
Escribe un ProyectoController que atienda estas cuatro:
| Ruta | Qué devuelve |
|---|---|
GET /proyectos |
Lista de proyectos |
GET /proyectos?estado=activo |
Lista de proyectos con estado activo |
GET /proyectos/{id} |
Ficha del proyecto 7 |
GET /proyectos/{id}/incidencias |
Incidencias del proyecto 7 |
Condiciones:
- Usa
@RequestMappinga nivel de clase. No repitas/proyectosen cada método. - El parámetro
estadoes opcional, contodoscomo valor por defecto. - El
iddebe ser unint, no unString. Después comprueba qué pasa con/proyectos/abcy anótalo.
Amplía tu controlador con dos rutas más, decidiendo tú dónde va cada dato:
- Una para consultar una incidencia concreta dentro de un proyecto concreto.
- Una para buscar incidencias filtrando por prioridad y por página.
Para cada una, escribe en un comentario del código la respuesta a esto: qué datos has puesto en la ruta, cuáles en la query string, y qué prueba de la regla has aplicado para decidirlo.
Paso 5-bis · Revisión cruzada del diseño de rutas · 10 min
La regla de esta sesión —lo que identifica al recurso va en la ruta, lo que acota una consulta va en la query string— se verifica cuando alguien ajeno al código interpreta la URL. Intercambia con tu pareja únicamente las URL de las dos rutas que has diseñado, sin mostrar el controlador.
Responde por escrito sobre las que recibas:
- Qué devuelve cada una, deducido solo de su forma.
- Qué dato actúa como identificador y cuál como filtro.
- Si trasladarías alguno al otro sitio, y qué prueba de la regla lo justifica.
Contrastad después las respuestas con el código. Una URL que la otra persona no logra interpretar sin leer el controlador señala un problema de diseño, no de comunicación: dentro de tres semanas su autor estará en la misma situación.
Paso 6 · Comprobar y registrar el resultado del proyecto · 10 min
- Prueba el saludo con el parámetro ausente, vacío y con un nombre. Con
defaultValue="mundo", los dos primeros deben devolverHola, mundo.. - Consulta el detalle con
7y conabc: el primero devuelve el texto que has programado y el segundo produce 400 al no poder convertirse a entero. Un número como999todavía no permite saber si existe un registro: esa búsqueda aún no está implementada.
Criterios de diseño de las consultas GET
La regla de la ruta y la query string resuelve dónde colocar cada dato, pero no agota el diseño de una consulta. El método GET tiene un significado fijado por el protocolo, y de ese significado salen los criterios que aplicarás a cada ruta de consulta que escribas durante el curso.
Para qué se usa un GET en una aplicación real
Las pantallas de una aplicación de gestión se sostienen sobre un repertorio corto de consultas. Las cuatro rutas del ProyectoController cubren las tres primeras filas, y la búsqueda por prioridad y página que has añadido en el paso 5 corresponde a las dos últimas.
| Necesidad de la aplicación | Petición | Forma de consulta |
|---|---|---|
| Mostrar la pantalla de listado | GET /proyectos |
colección |
| Abrir la ficha de un elemento | GET /proyectos/7 |
recurso individual |
| Recorrer lo que pertenece a ese elemento | GET /proyectos/7/incidencias |
subcolección |
| Acotar una lista extensa | GET /incidencias?estado=abierta&prioridad=alta |
filtro |
| Rellenar un desplegable de un formulario | GET /usuarios?rol=tecnico |
filtro |
| Recorrer una lista larga por partes | GET /incidencias?pagina=2&tamano=20 |
paginación |
Las seis comparten una característica que conviene nombrar antes de seguir: ninguna modifica nada.
GET es un método seguro
Método seguro
Un método HTTP es seguro (safe) cuando la petición no solicita ningún cambio en el estado del servidor y se limita a pedir una representación de algo que ya existe. GET, HEAD y OPTIONS son seguros; POST, PUT, PATCH y DELETE no lo son. La definición figura en la RFC 9110, la norma vigente de la semántica de HTTP.
De esa propiedad depende el comportamiento de toda la infraestructura situada entre el cliente y tu servidor.
Consecuencias de la seguridad del método
Un GET se repite sin consultar a nadie. El navegador lo reemite al recargar la página y al usar el botón de retroceso, la caché conserva su respuesta, un intermediario lo reintenta cuando la red falla, el navegador lo precarga cuando anticipa que el enlace se va a seguir y los rastreadores de los buscadores recorren cuantas direcciones encuentran publicadas.
Por ese motivo, una dirección como GET /incidencias/41/cerrar constituye un defecto de diseño y no un atajo cómodo. La incidencia se cerraría al recargar la página, al volver atrás o al abrir el enlace guardado en marcadores, sin que nadie hubiera solicitado esa operación. Las acciones que modifican datos se envían con los métodos que aparecen en la sesión 3.
La URL es el identificador de la consulta
Un GET no lleva cuerpo, de modo que la URL contiene todo lo que determina la respuesta. De ahí se derivan dos prácticas.
La primera: una misma URL debe devolver la misma respuesta mientras los datos no cambien. Una consulta cuyo resultado dependa de un estado oculto, como el orden en que se hayan pedido otras rutas antes, no se puede cachear, ni guardar en marcadores, ni compartir con otra persona, ni reproducir en un informe de error.
La segunda afecta a la privacidad:
Datos que no pueden viajar en la query string
La URL queda escrita en el historial del navegador, en los registros de acceso del servidor y de los intermediarios, y en la cabecera Referer que el navegador envía al sitio que se visite a continuación. HTTPS la cifra durante el tránsito, pero no impide que el destino y esos registros la conserven en claro de forma indefinida.
Por tanto, una contraseña, un token de sesión o un dato personal identificable no se envían como parámetro de consulta. Su lugar son las cabeceras de la petición, que se tratan en la UD9.
Criterios para declarar los parámetros
Tres decisiones se repiten cada vez que escribes la firma de un método de consulta:
- Obligatorio solo lo imprescindible. Un parámetro se declara obligatorio cuando la consulta carece de sentido sin él. Para el resto existe
defaultValue, y cada400evitable es una petición que el cliente había construido con una intención razonable. - El mismo concepto recibe el mismo nombre en toda la API. Si la página se llama
paginaen un controlador, no puede llamarsepageen el siguiente. Quien consume la API aprende ese vocabulario una sola vez y lo aplica a todos los recursos. - Toda colección acaba necesitando un límite superior. Hoy tus listados devuelven una frase fija y el problema no se manifiesta. Cuando los datos procedan de PostgreSQL en la UD5, un
GET /incidenciassin acotar recorrerá la tabla entera; la paginación se introduce en la UD7.
Lista de verificación del diseño de una consulta GET
- Ninguna ruta contiene un verbo ni produce cambios en los datos.
- Lo que identifica al recurso está en la ruta, y lo que acota la consulta, en la query string.
- Cada parámetro obligatorio lo es porque la consulta no significa nada sin él.
- Los parámetros opcionales declaran un valor por defecto, o su ausencia se comprueba dentro del método.
- El mismo concepto recibe el mismo nombre en todos los controladores.
- Ninguna credencial ni dato personal aparece en la URL.
Repasa con esta lista las rutas escritas hoy antes de pasar al cierre.
Ampliación si has completado el trabajo
Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.
Reto · Cuatro peticiones sin ejecutar nada
Con este controlador delante:
@RestController
@RequestMapping("/tareas")
public class TareaController {
@GetMapping("/{id}")
public String detalle(
@PathVariable(name = "id") int id,
@RequestParam(name = "formato", defaultValue = "corto") String formato) {
return "Tarea " + id + " en formato " + formato;
}
}
Predice, antes de probarlo, qué devuelve cada petición: el código de estado y el cuerpo si lo hay. Escribe también por qué.
GET /tareas/5GET /tareas/5?formato=largoGET /tareas/cincoGET /tareasPOST /tareas/5
Después copia el controlador en tu proyecto y comprueba las cuatro primeras en el navegador. La quinta exige enviar un POST, y la barra de direcciones solo emite GET. Para esa, abre la terminal integrada del editor y lanza una única orden:
curl.exe -i -X POST http://localhost:8080/tareas/5
En PowerShell debe escribirse curl.exe, con su extensión: curl a secas es un alias de Invoke-WebRequest, que no admite -X y produce otra salida. En Linux y macOS basta curl. La opción -i muestra la línea de estado y las cabeceras además del cuerpo, que es justo lo que hay que leer aquí. El cliente HTTP que se instala en la sesión 3 hace esto mismo con una interfaz encima. De las cinco, dos suelen fallarse. Cuando una predicción resulte equivocada, no basta con corregirla: escribe qué regla habías aplicado mal.
/saludo, /saludo?nombre=Marc y /usuarios/3 funcionando, y sabes provocar el 400.ProyectoController completo con sus cuatro rutas y las dos que has diseñado tú, justificadas.Ver respuestas
1 · 200, con el cuerpo Tarea 5 en formato corto. El parámetro no llega en la petición, de modo que defaultValue lo sustituye; mientras esté declarado, formato nunca es nulo.
2 · 200, con el cuerpo Tarea 5 en formato largo. El valor recibido en la query string sustituye al valor por defecto.
3 · 400 Bad Request. Spring convierte el texto de la ruta al tipo declarado antes de invocar el método; cinco no admite conversión a int, así que la petición se rechaza y el método no llega a ejecutarse.
4 · 404 Not Found, y es una de las dos que suelen fallarse, porque se espera un listado vacío. El controlador declara /tareas/{id} y ninguna ruta para /tareas: el prefijo declarado en la clase no atiende peticiones por sí mismo.
5 · 405 Method Not Allowed, que es la otra. La ruta existe, pero únicamente para GET. El servidor distingue entre no encontrar un recurso y encontrarlo con un método que no admite, distinción sobre la que se construye toda la UD2.
Cierre
15 minutos · resultado comprobable y explicación individual
Al terminar la sesión:
Las rutas de listado y detalle devuelven textos que incorporan los parámetros recibidos. Debe ser posible localizar el método que atendió cada petición; la consulta de objetos reales comienza en la sesión 3.
Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.
Antes de cerrar, sube el trabajo a GitHub. Los controladores de hoy no existen fuera de tu equipo mientras no los publiques, y el historial forma parte de lo que se evalúa en Proyecto Intermodular: una sesión sin rastro en el repositorio no se distingue de una sesión sin trabajo.
git status
git add .
git commit -m "Completar sesión 02: parámetros de ruta y de consulta"
git push
git status antes de confirmar evita subir el trabajo a medias: comprueba que aparecen los controladores escritos hoy y que no aparece target/, que el .gitignore excluye. Si el push es el primero desde otro equipo, Git pedirá de nuevo tus credenciales.