Métodos HTTP (REST)

Métodos HTTP (REST)Los métodos HTTP definen la acción que se realizará sobre un determinado recurso. Los métodos HTTP, también suelen ser llamados HTTP Verbs. Aunque el nombre correcto es Verbs, la realidad es que, en la práctica, casi siempre son llamados “métodos”, por lo que utilizaremos el nombre “métodos” para referirnos a ellos.

 

NOTA: Este artículo es parte de un tutorial completo para crear API REST con JAX-RS, si quieres ver el índice completo entra aquí.

 

Entender los métodos HTTP es fundamental para comprender la forma en que funciona la arquitectura REST, pues mediante los métodos le indicamos al servidor la forma en que debe de tratar una determinada petición, dicho esto, una misma URL puede ser tratada de forma diferente por el servidor.

 

HTTP define una gran cantidad de métodos que son utilizados para diferentes circunstancias, por lo que trataremos de listar lo más relevantes y más utilizamos en la construcción de servicios REST, los métodos son los siguientes:

 

  • GET: Es utilizado únicamente para consultar información al servidor, muy parecidos a realizar un SELECT a la base de datos. No soporta el envío del payload
  • POST: Es utilizado para solicitar la creación de un nuevo registro, es decir, algo que no existía previamente, es decir, es equivalente a realizar un INSERT en la base de datos. Soporta el envío del payload.
  • PUT: Se utiliza para actualizar por completo un registro existente, es decir, es parecido a realizar un UPDATE a la base de datos. Soporta el envío del payload.
  • PATCH: Este método es similar al método PUT, pues permite actualizar un registro existente, sin embargo, este se utiliza cuando actualizar solo un fragmento del registro y no en su totalidad, es equivalente a realizar un UPDATE a la base de datos. Soporta el envío del payload
  • DELETE: Este método se utiliza para eliminar un registro existente, es similar a DELETE a la base de datos. No soporta el envío del payload.
  • HEAD: Este método se utilizar para obtener información sobre un determinado recurso sin retornar el registro. Este método se utiliza a menudo para probar la validez de los enlaces de hipertexto, la accesibilidad y las modificaciones recientes.

 

Hasta aquí los métodos más utilizados en la construcción de servicios REST con JAX-RS, sin embargo, existen algunos métodos más que son interesantes conocer, pues no los encontraremos al momento de depurar o analizar el tráfico de red.

  • CONNECT: Se utiliza para establecer una comunicación bidireccional con el servidor. En la práctica no es necesario ejecutarlo, si no el mismo API de HTTP se encarga de ejecutarlo para establecer la comunicación previo a lanzar alguna solicitud al servidor.
  • OPTIONS: Este método es utilizado para describir las opciones de comunicación para el recurso de destino. Es muy utilizado con CORS (Cross-Origin Resource Sharing) para validar si el servidor acepta peticiones de diferentes origines.

 

A pesar de que los métodos están diseñados para realizar ciertas acciones, la realidad es que nada impide que los utilices de forma errónea, es decir, fácilmente podrías utilizar el método DELETE para consultar o el POST para eliminar un recurso, si bien, el API funcionará, el desarrollador se volverá loco al intentar entender como funciona el API, es por este motivo que debemos entender y tener mucho cuidado en la forma en que implementamos los métodos.

 

A pesar de que existe una gran cantidad de método HTTP, la implementación de JAX-RS solo implementa los que son realmente utilizados para crear servicios REST. Para implementar un método HTTP con JAX-RS solo es necesario anotar un método con cualquiera de las siguientes anotaciones del paquete javax.ws.rs :

  • @GET
  • @POST
  • @PUT
  • @DELETE
  • @HEAD
  • @OPTION

 

Por ejemplo, para crear un servicio que retorne todos los Usuarios, podríamos crear un método como el siguiente:

@GET
public Response findAllUsers() {
    List<Users> users = userDAO.getUsers();
    return Response.ok(users ).build();
}

 

Antes de comenzar, te cuento que puedes descargar el código completo en https://github.com/oscarjb1/blog-tutorial-jaxrs/tree/master/M%C3%A9todos%20HTTP/api

 

De la misma forma, podríamos crear cualquier otro método y anotarlo con el método que necesitemos. Para comprobar cómo funcionan los métodos, vamos a crear un servicio REST que nos permite tener las operaciones básicas para realizar un CRUD (Altas, Bajas, Cambio, Consulta en inglés).

package api.services;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import api.domain.User;


@Path("/users")
@Consumes(value= MediaType.APPLICATION_JSON)
@Produces(value = MediaType.APPLICATION_JSON)
public class UserService {
	
	//User database pre-initialization
	private static final List<User> users = new ArrayList<>();
	
	static {
		users.add(new User(1L, "oscar", "1234"));
		users.add(new User(2L, "juan", "1234"));
		users.add(new User(3L, "maria", "1234"));
	}
	
	
	@GET
	public Response findAllUsers() {
		return Response.ok(this.users).build();
	}
	
	@POST
	public Response createUser(User userRequest) {
		userRequest.setId(users.size()+1l);
		this.users.add(userRequest);
		return Response.ok(userRequest).build();
	}
	
	@PUT
	public Response updateUser(User userRequest) {
		List<User> found = this.users.stream().filter(
                    x -> userRequest.getId() == x.getId()).collect(Collectors.toList());
		
		//Throws error in case of the user not found
		if(found.isEmpty()) 
                    return Response.status(Status.BAD_REQUEST).entity("User not found").build();
		
		User updateUser = found.get(0);
		updateUser.setPassword(userRequest.getPassword());
		updateUser.setUsername(userRequest.getUsername());
		return Response.ok(updateUser).build();
	}
	
	@DELETE
	@Path("{userId}")
	public Response deleteUser( @PathParam("userId") long userId) {
		System.out.println("userId ==> " + userId);
		List<User> found = this.users.stream().filter(
                    x -> userId == x.getId().longValue()).collect(Collectors.toList());
		
		//Throws error in case of the user not found
		if(found.isEmpty()) 
                    return Response.status(Status.BAD_REQUEST).entity("User not found").build();
		
		User updateUser = found.get(0);
		this.users.remove(updateUser);
		return Response.noContent().build();
	}
	
	
	@HEAD
	public Response pingUsersService() {
		return Response.noContent().header("running", true).build();
	}
	
}

Como podrás apreciar, no estamos utilizando una base de datos real para guardar los cambios, pues no es el propósito en este punto, más bien, queremos enfocarnos en la forma en que los métodos son declarados, dicho esto, pasemos a analizar cómo funciona:

 

Inicialización del servicio

En primer lugar, podemos apreciar que el servicio responde en el path /users, lo cual podemos comprobar en la anotación @Path  a nivel de clase. Por el momento no profundizaremos en esto, pues más adelante tendremos una sección especial para explicar cómo funcionan los paths. Por otra parte, tenemos una lista llamada users, la cual utilizaremos como un sustituto a la DB para ir guardando los nuevos usuarios, actualizar o borrar los existentes. De entrada, iniciamos la lista con 3 usuarios.

Las anotaciones @Consumes  y @Produces  las utilizamos para indicar que él payload recibido y enviado como respuesta serán en formato JSON respectivamente. En otra sección de esta guía profundizaremos en el tema, por lo que por ahora no es necesario entender del todo esta parte.

 

Consulta de todos los usuarios (@GET)

El método findAllUsers es utilizado fue desarrollado para retornar todos los usuarios que tengamos registrados. Como ya hablamos anteriormente, de inicio tendremos 3 usuarios precargados, por lo que podremos comprobar si realmente funciona. Para la prueba utilizaremos el plugin de Chrome llamado Restlet, pero podrías utilizar otros programas como SOAPUI.

Métodos HTTP GET

En la imagen podemos apreciar claramente que, al ejecutar el servicio, este nos regresa un array con los 3 usuarios que se pre-cargaron.

 

 

Crear un nuevo usuario (@POST)

De la misma forma en la que consultamos los usuarios existentes, podemos crear nuevos mediante el método POST. La única diferencia, es que es necesario enviarle en el payload el username  y el password  con el que se deberá crear, el ID será auto generado.

Métodos HTTP POST

Como resultado de la ejecución, tenemos un nuevo usuario creado, podemos comprobar que es nuevo por el ID que se va generando de forma secuencial.

 

 

Actualización de un usuario existente (@PUT)

De la misma forma en que acabamos de crear un usuario, podemos actualizar sus datos mediante el método PUT. Para comprobar de que el usuario es actualizado, podremos observar que el ID del mismo no cambiara, y en su lugar, solo se actualizara el username  y el password . Como vamos a actualizar el registro, es necesario enviarle el ID como parámetro.

Métodos HTTP PUT

 

 

Eliminar un usuario (@DELETE)

El caso del delete es un poco diferente, pues como este método no soporta enviarle el payload, es necesario enviarle el ID del usuario a eliminar de otra forma. En este caso, vamos a enviarle el ID como parte de la URL, es por ello, que agregaremos “/1” al final y agregaremos la anotación @PathParam  para indicarle a JAX-RS como debe de recuperar el ID. Más adelante en otra sección de este tutorial analizaremos cómo funcionan estos parámetros, por ahora no nos preocupemos por esto.

Métodos HTTP DELETE

En este caso no hay necesidad de retornar nada, es por ello que vemos el mensaje de “NO CONTENT”.

 

Comprobar disponibilidad del servicio (@HEAD)

Finalmente, validaremos si el servicio está activo realizando una petición HEAD, el cual nos regresará el header “running” si está actualmente en funcionamiento.

Métodos HTTP HEAD

 

Conclusiones

Como hemos podido comprobar, crear servicios REST que respondan en los diferentes métodos es sumamente simple, y solo falta anotar el método con la anotación adecuada para crear un nuevo servicio. Solo quedo pendiente comprobar el funcionamiento del método OPTION (@OPTION ) el cual tu podrías implementar como tarea.

 

47 thoughts to “Métodos HTTP (REST)”

  1. Hola Oscar,
    Cuando lo pruebo me contesta siempre con un 404, estoy casi seguro que es la configuración del servidor, pero no sé muy bien como hacerlo para que funcione, en principio no me da ningún error el servidor al iniciar.

  2. Buenas Oscar,
    Cada vez que intento probarlo, me devuelve un 404. Estoy casi seguro que es por la configuración del servidor. Estoy usando el WildFly 11, tal y como en el tutorial y no me da ningún error a la hora de iniciarlo.
    Gracias

  3. Hola Oscar,
    Estoy iniciando en el tema y desconozco como crear el directorio services para sacar el error que me da al momento de crear la clase y poner package api.services;

    Actualmente al momento de crear la clase, me genera automáticamente:
    package api;
    public class UserService {
    }

    De antemano, muchas gracias.

  4. Buenas, en el metodo get si deseo que el array del json tenga un nombre ¿Cómo sería?
    Es decir que sea algo asi:
    {“usuarios”:[{“id”:1,”username”:”rene”,”password”:”1234″]}

    1. Hola Alexander, la respues del servicio está determinado por el tipo de datos que retornes, en este caso, yo estoy retornando una instancia de la clase User, la cual tiene un ID, username, y password, si quieres que la respuesta esté dentro de una estructura “usuarios”, tendrías que crear una clase llamada Usuario y que dentro tenga una lista de usuarios, así al momento de hacer un parse a JSON, te creará la estructura deseada.
      saludos.

  5. Hola excelente tutorial, muy educativo, sin embargo al ejecutarlo me arroja error 500, no se si tiene algo que ver con las expresiones lambda, tu ayuda me serviria de mucho.

    Saludos

  6. Hola muy bien tutorial,
    Tambien experimente un error al ejecutar, lo solucione agregando al pom:
    com.sun.jersey
    jersey-json
    1.8

    talvez a alguien le sirva esta opcion, esta dependencia es para el manejo del Json.

  7. Hola tutorial muy bien,
    ¿Como seria el código del metodo PUT si puedo sólo modificar un elemento de la entidad de usuario, por ejemplo, si modifico el username me da OK y si modifico la password me da por ejemplo : bad request?
    Saludos

          1. quiero cambiar el código de PUT para que me dejara solo cambiar un elemento de la entidad usurai. por ejemplo si uso postman me deja cambiar solo un elemento de la entidad usuraio

  8. Hola Oscar felicidades por el tutorial!

    Tengo una duda sobre los metodos de cual utilizar en el caso de que se un proceso mas complejo.

    Por ejemplo un método donde el proceso realice creación, actualización, consultas y retornar algún dato.

    ¿Que método seria mas el correcto de usar un GET, POST o PUT?

    1. Si el servicio realiza muchos pasos, analiza todos los pasos como una solo transacción y velo como si fueras un usuario externo que no conoce los detalles técnicos, dicho eso, como lo verías desde fuera, como una creación, una actualización, ese es mi consejo, analiza siempre como si fueras un usuario del API que no conoce los detalles internos.

  9. En payload en Android la unica forma de hacer un túnel con proxy en una app como http custom es el metodo CONNECT o ahí otra forma o de que formas o metodo se puede estabilizar un payload

  10. Hola… Viendo la pregunta inconclusa del amigo Lionel…..Mediante estos “métodos” se puede lograr vulnerar las redes de las compañías telefonicas en muchos países…. Eso es lo que dice Lionel… Solo quería aclararlo por si no se entendía…..Muchas gracias

  11. Oscar. Ante todo gracias por compartir el conocimiento.
    Con tus guias he logrado aprender y clarificar muchas cosas. Tengo una duda que no he logrado solucionar con todo lo que he buscado. Nos enseñaste que podemos usar JAX-RS para crear el servicio sin pensar en cual implementación usará el contenedor, en mi caso uso tomee y en otros tomcat.
    Solo con javaee-api puedo usar las anotaciones.
    Lo he probado y funciona bien.
    Para crear un cliente de esos webservices, también puedo usar solo anotaciones de jax-rs o ahí si debo usar alguna implementación como jersey o cxf?

    1. Hola Kevin, siempre puedes utilizar las anotaciones propias de cada librería, incluso, hay ocaciones donde es necesario utilizarlas por que hay funcionalidad no estandar que solo se puede utilizar usando anotaciones propias de la librería, sin embargo, siempre que se pueda, es recomendable utilizar las anotaciones estandares de JAX-RS.

      saludos.

      1. Mi ultima duda… solo con la dependencia de javaee-api creé este servicio y el navegador con la url me lo muestra normal. Es decir el servicio funciona..
        package webservices;

        import javax.ws.rs.GET;
        import javax.ws.rs.Path;
        import javax.ws.rs.core.Response;

        @Path(“/ws”)
        public class Servicios {

        @GET
        @Path(“get2”)
        public Response saludar() {
        System.out.println(“Entré al webservice”);
        return Response.ok(“texto”).build();
        }
        }

        Intento consumirlo en una clase con un metodo main, solo con anotaciones de jax-rs, es decir, nada de jersey, nada de apache cxf. con este codigo
        import javax.ws.rs.client.Client;
        import javax.ws.rs.client.ClientBuilder;

        public class Cliente {

        public static void main(String[] args) {
        Client client = ClientBuilder.newClient();
        String res = client.target(“http://localhost:8080/app/ws/ws/get2”).request().get(String.class);
        System.out.println(“res ” + res);
        }
        }
        Y obtengo un error que segun lo que he buscado se refiere a que no tengo una implementación para el cliente.
        este es el error
        Exception in thread “main” java.lang.RuntimeException: java.lang.ClassNotFoundException: org.glassfish.jersey.client.JerseyClientBuilder
        at javax.ws.rs.client.ClientBuilder.newBuilder(ClientBuilder.java:103)
        at javax.ws.rs.client.ClientBuilder.newClient(ClientBuilder.java:114)

        De ahi mi pregunta original de arriba, si para consumirlo si debo tener obligado una libreria de jersey o apache o si solo con jax-rs se puede.

        De nuevo gracias.

        1. La clave en la respuesta esta en el entorno de ejecución, cuando tu despliegas tu aplicación en el servidor, este ya cuenta con una serie de librerías para ejecutar los webservices, en este caso, cuenta con al menos, la espesificación JAX-RS y una implementación, por lo tanto, solo necesitas la espesifficación JAX-RS para desarrollar, pues la implementación ya la tiene el servidor, sin embargo, cuando corres la aplicación como una aplicación java standar edición (método main), las únicas librerías que estarán disponibles son las que agregues en el classpath, en este caso, solo contarás con JAX-RS, pero no tendrás ninguna implementación.
          Si lo que buscas en consumir un servicios desde una clase Main, necesitas importar JAX-RS y al menos una implementación.

  12. Como estas Oscar?, me estoy iniciando en esto de REST, no entiendo esto de ” No soporta el envío del payload”

    GET: Es utilizado únicamente para consultar información al servidor, muy parecidos a realizar un SELECT a la base de datos. No soporta el envío del payload.

    Gracias y Saludos.

  13. Ahora viene lo interesante y es quien es capaz de hacer un Get en la que tengas que interpretar un texto en json con la limitación de los 2048 caracteres, lo que quiero decir es que esto esta pensado mas para CRUD y no para situaciones reales, como por ejemplo una integración de sistemas, la lógica seria usar Get pero dada las limitaciones y a que los sistemas se comunican por json los json suelen ser mas grandes que 2048 con lo que no te queda mas remedio que usar mal los verbos http si no te quieres complicar de forma totalmente absurda la existencia.

    1. ¿Y quien dijo que está límitado a 2048? puedes mandar más información, el problema real aquí es que si tu requieres mandar más de 2MB es por que estas diseñando mal la aplicación. Estos request o respuestas tan grandes es sintoma de mala arquitectura.

  14. Hola Oscar

    Espero que este bien, me podrias aclarar a que te refieres con el siguiente párrafo que publicaste

    “A pesar de que los métodos están diseñados para realizar ciertas acciones, la realidad es que nada impide que los utilices de forma errónea, es decir, fácilmente podrías utilizar el método DELETE para consultar o el POST para eliminar un recurso, si bien, el API funcionará, el desarrollador se volverá loco al intentar entender como funciona el API, es por este motivo que debemos entender y tener mucho cuidado en la forma en que implementamos los métodos.”

    Entiendo bien que si uso un DELETE pero en el desarrollo indico que si llega con este metodo realiace un update o cualquier accion va a funcionar, pero lo que no entiendo es la parte cuando te refieres que “el desarrollo se volvera loco al entender como funciona la API”, ¿podrias ser mas detallista y tecnico con esto por favor?.

    Si bien tengo claro que los metodos que usamos son mas bien de concepto y arquitectura pero no se en que afecta el desarrollo o la respuesta si uso cualquier método para cualquier acción.

    Quedo Atendo

    1. Esto es muy simple, imagina que estás en una base de datos, y que cuando le lanzas un SELECT, te borre toda la tabla, ¿no te volverías loco?, cuando digo esto me refiero a que los desarrolladores ya tenemos claro para que es cada método, pero si cambias lo que hace, vas a crear un caos.

  15. Muchas gracias por tu publicación, nos da un buen panorama a los que iniciamos con estos servicios,
    tengo una duda, si requiero mas de un método get, ¿es posible/recomendable?
    por ejemplo, tomando como base tu ejemplo de usuarios, si tuvieran un id, y requiero solo el usuario al que corresponde el id? pero tambien requiero toda la lista de usuarios, o todos los usuarios que fueron creados a partir de una fecha, etc, es decir pasar un parámetro o varios, para obtener resultados acotados/específicos, no solo la lista completa de usuarios.

    1. En caso de requerir múltiples filtros puedes usar los Query Params, es por ejemplo /users?name=”oscar” o por ejemplo /users?regDate[gt]=01/01/2018, el gt es greater than (mayor que), así para cada compo, podrías crear cuantos Query param sean mecesarios por ejemplo /users?regDate[gt]=01/01/2018&email[like]=@gmail

  16. Buen día. No soy experto en el tema, me estoy iniciando.
    Tengo una consulta, cuando hago una petición GET hacia un X servicio que tengo me regresa la información de una X tabla de una base de datos. Que tengo que verificar, porque cuando yo hago una modificación, un UPDATE digamos directo desde cuando vuelvo a solicitar la información por medio de GET este me regresa la información anterior sin mostrar el cambio.

    de Antemano muchas gracias

    1. Uff, pues seguramente no estás haciendo commit a los cambios y no se reflejan en la base de datos ó, estas cachando los resultados y por eso no te refleja

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *