Introducción al diseño de una API HTTP (Parte 1 de 4) : HTTP Requests

Este primer artículo abarca una breve introducción sobre las solicitudes HTTP (HTTP Requests).

¿Qué es una Solicitud HTTP?

Creo que primero habría que definir que es HTTP (Hipertext Transfer Protocol). El protocolo HTTP no le debe ser muy indiferente. Si leíste sobre el Modelo OSI, recordarás que este protocolo de comunicación pertenece al nivel de aplicación, que a su vez, esta por encima de otro famoso protocolo, el protocolo TCP (Transmission Control Protocol) que se sitúa en la capa transporte de este modelo. Como todo protocolo de comunicación, el protocolo HTTP define unas normas que debe seguir el emisor que desea transmitir información a un receptor. HTTP utiliza el patrón de request / response (petición / respuesta), lo cual quiere decir, que un emisor (sistema X) realiza una solicitud (request) y un servidor (Sistema Y)  responde (response) a dicha solicitud.

En la siguiente imagen se puede apreciar como un computador (emisor) realiza una solicitud HTTP (HTTP Request) a un servidor (receptor) y este último le responde (HTTP Response) el pedido.

A continuación un ejemplo de las reglas que debe seguir el cliente para que el servidor comprenda su petición HTTP.

La primera línea se llama Línea de Solicitud y contiene tres elementos;

1. El método HTTP (para este ejemplo es POST).

2. Una ruta de acceso (también denominada como endpoint en una API).

3. La versión HTTP (para este ejemplo es HTTP/1.1)

Existen otros métodos HTTP, más adelante se mencionarán.

El contenido que le sigue a la “Línea de Solicitud” se denomina Headers (en español encabezados). Los headers son pares de key – value (clave – valor), donde la clave es la primera palabra, seguido de dos puntos y un espacio y, a continuación, el valor. Los headers están separados por saltos de línea. Es recomendable que las claves de los headers inicien con su primera letra en mayúscula. Los headers pueden repetirse en situaciones en las que es necesario enviar valores duplicados.

Una solicitud HTTP (HTTP Request) solo necesita tener una Línea de Solicitud y algunos headers para ser válido. Esto se aplica principalmente a las solicitudes que utilizan los métodos HTTP GET, DELETE, HEAD y OPTIONS. Sin embargo, también podemos proporcionar un cuerpo de solicitud (request body), que requiere dos líneas nuevas después del último header antes del contenido del cuerpo. Los cuerpos se utilizan principalmente con los métodos HTTP POST, PUT y PATCH.

Hablemos de los Endpoints

Al diseñar una API HTTP RESTful, es importante poder abstraer la funcionalidad de su servicio de tal manera que todas las operaciones puedan representarse realizando operaciones CRUD (Create, Read, Update, Delete) para los diferentes recursos (entidades). Las acciones (verbos HTTP ó métodos HTTP) que se realizan nunca deben ser parte del endpoint.

El enfoque más utilizado es exponer diferentes colecciones de recursos relacionados. Por ejemplo, si su servicio contiene información sobre diferentes empresas y empleados, podría tener una colección llamada empresas y otra colección llamada empleados. Podemos obtener información más específica y hacer referencia a una empresa individual o a sus empleados dentro de cada una de estas colecciones. Como por ejemplo, un empleado puede ser empleado en una empresa. Lo siguiente es cómo podemos representar estos datos y sus relaciones:

Por lo tanto, podemos decir que si queremos obtener la lista de empresas debemos solicitar el endpoint /companies o si queremos obtener información de solo una empresa debemos solicitar el endpoint /companies/{company_id}

Headers HTTP

Habíamos dicho que los headers son un conjunto de pares de clave-valor. Hay muchos headers diferentes que podemos utilizar. Los navegadores web, por lo general, envían varios headers en sus solicitudes HTTP (requests) y los nuevos navegadores están siendo estandarizados todo el tiempo. A continuación una lista de los headers más comunes y su significado, que además debe tener en cuenta para que sean compatibles con el API HTTP que construyas.

Accept: Puede contener una lista de “Tipos de Contenido” (Content-Types) que son aceptados por el cliente (navegador web por ejemplo). Un tipo de contenido muy usado en las APIs  HTTP es application/json. Es decir, el cliente acepta recibir información del servidor en formato JSON.

Accept-Language: Puede contener una lista de idiomas que son aceptados por el cliente. Por ejemplo podemos indicar que el cliente acepta recibir información del servidor en idioma ingles en-US, o en español es-pe.

Content-Length: Es la longitud en bytes del cuerpo de la solicitud. Si se conoce el tamaño del cuerpo de la solicitud, se debe enviar este valor.

Content-Type: Representa al tipo de contenido proporcionado en el cuerpo de la solicitud. Sólo enviar si la solicitud contiene un cuerpo. Por ejemplo podemos indicar en nuestra solicitud HTTP que la información que vamos a enviar al servidor estará en formato JSON.

Host: Este header siempre se debe enviar en las solicitudes HTTP. Representa al Host HTTP del servidor. Las aplicaciones probablemente ignorarán esto, sin embargo, es sobre todo útil para los hosts virtuales y el enrutamiento de solicitudes.

User-Agent: Este header incluye información de identificación del cliente, como por ejemplo el nombre del navegador web de donde se envía la solicitud HTTP.

Estos son solo algunos de los headers estandarizados que existen, pero también hay un montón de headers que puedes agregar de manera ad-hoc. Como regla general, al inventar sus propios headers, el patrón común es prefijarlo con una X por adelante, como por ejemplo X-API-Version para representar la versión del API.

Método GET

El método GET hace referencia a la acción de Leer (Read) del CRUD. Las solicitudes GET (GET requests) se consideran seguras, lo que significa que no deben alterar el estado del servidor. Las solicitudes GET se consideran idempotentes, lo que significa que deben ser repetibles sin causar efectos secundarios. Las solicitudes GET no deben tener un cuerpo (body request).

Cuando se usa el método GET contra una colección (por ejemplo /empresas) significa que deseas recuperar una lista de recursos dentro de esa colección (es decir una lista de empresas). Podemos recuperar todos los recursos o filtrar recursos específicos según los criterios definidos. El siguiente ejemplo es para obtener obtener una lista de empleados:

La solicitud anterior podría traducirse en “Obtener una lista de 10 empleados que trabajan en Twitter que estén en el rango salarial entre 3000 y 8000 (dólares por ejemplo), omitiendo los primeros 20“. El endpoint contiene parámetros de consulta (filtros) que inician luego del símbolo ? (? Key1 = Value1 & Key2 = Value2). Si no se especifican las propiedades de filtrado (parámetros de consulta), el servidor debe responder con todos los recursos coincidentes. Es común que los servidores tengan límites por defecto en los datos devueltos, de lo contrario sería muy abrumador devolver todos los datos de una colección, imagina que hubiesen millones de registros :0.

Método POST

El método POST hace referencia a la acción de Crear (Create) del CRUD. Las solicitudes POST (POST requests) se consideran Inseguras ya que alteran el estado del servidor. Se consideran no idempotentes ya que las solicitudes repetidas darán como resultado la creación de varios recursos. Las solicitudes POST deben tener un cuerpo (body request).

Cuando se usa el método POST contra una colección, el servidor debe crear un nuevo recurso dentro de esa colección.

La primera Linea de Solicitud podemos usarla para agregar una nueva empresa a nuestro servicio.

La segunda Linea de Solicitud podemos usarla para agregar un nuevo empleado a la empresa Twitter.

El cuerpo de la solicitud POST (body request) contendría información sobre el recurso que estamos agregado. Por ejemplo:

Método DELETE

El método DELETE hace referencia a la acción de Eliminar (Delete) del CRUD. Las solicitudes DELETE (DELETE requests) se consideran Inseguras ya que alteran el estado del servidor. Sin embargo, se consideran idempotentes, ya que al repetir esta acción DELETE, no debe tener ningún efecto secundario (es decir si por casualidad vuelves a invocar al endpoint con el verbo DELETE, no va eliminar un recurso que ya fue eliminado). Una solicitud DELETE no debe tener un cuerpo (body request).

La primera Linea de Solicitud es cómo podemos eliminar a un empleado (gchacaltana) de la empresa Twitter.

La segunda Linea de Solicitud es como podemos eliminar a un empleado con identificador gchacaltana.

Métodos PUT/PATCH

Los métodos PUT y PATCH hacen referencia a la acción de Actualizar (Update) del CRUD. Ambos se consideran Inseguros ya que alteran el estado del servidor. Por lo general se consideran Idempotent, es decir si invocamos a la misma solicitud varias veces debe tener el mismo resultado. Esto es cierto en la situación en la que está proporcionando propiedades de recursos y valores que se reemplazarán en un mismo recurso. Sin embargo, si su servicio tiene la capacidad de modificar una propiedad, como incrementar una propiedad, estas solicitudes se convertirían en no idempotentes. Los métodos PUT y PATCH deben tener un cuerpo.

¿Cuál es la diferencia entre PUT y PATCH?

Generalmente, una solicitud PUT realiza una actualización completa, las propiedades faltantes se actualizarán con valores nulos. Por ejemplo, un recurso de empleado podría tener como propiedades un nombre y un rol, pero realizar un PUT y sólo proporcionar un nombre, tendríamos un rol que falta. A diferencia de una solicitud PATCH, estas solicitudes se usan para realizar actualizaciones parciales. Sólo se establecerán las propiedades proporcionadas en el cuerpo de solicitud, las propiedades no listadas conservarán su valor existente. Algunas API sólo admiten PUT o PATCH para realizar cualquier actualización.

Métodos HEAD y OPTIONS

Los métodos HEAD y OPTIONS son un poco especiales. Se podría decir que hacen referencia a la acción Leer (Read) del CRUD pero en realidad no recuperan recursos. Se consideran seguros ya que no alteran el estado de la colección y además son idempotentes, ya que pueden volver a invocarse sin producir malos efectos. Tampoco deben tener un cuerpo de solicitud (body request).

La solicitud HEAD se utiliza para obtener sólo los headers (encabezados) de un recurso. Estos headers pueden contener metadatos útiles como cuando el recurso debe haber caducado. Si el costo de recuperar un recurso es alto (tal vez es un documento grande que es lento para transmitir o tal vez es una costosa llamada de base de datos), entonces podría tener sentido usar una solicitud HEAD en lugar de un GET.

La respuesta (response) del servidor sería:

En una siguiente publicación, hablaremos más a detalle de los HTTP Response.

La solicitud OPTIONS es útil para servicios expuestos a un navegador, por ejemplo, un navegador cliente puede enviar una solicitud OPTIONS y el servidor responde con headers que especifican que métodos HTTP puede utilizar el navegador y que otras funciones son soportadas por el servidor web. Por ejemplo:

El servidor podría enviar una respuesta así (HTTP Response).

El segundo artículo trata sobre una introducción a los HTTP Responses.

Para conocer la lista completa de artículos, dirigirse a Introducción al diseño de una API HTTP.

Introducción al diseño de una API HTTP

Esta publicación está influenciada sobre el libro Advanced Microservices, el cual describe estándares y buenas prácticas para el diseño de una API HTTP. Esta publicación se divide en 4 artículos:

Parte 1: Introducción a HTTP Requests

Parte 2: Introducción a HTTP Responses

Parte 3: Introducción a HTTP Body

Parte 4: Estándares de un API HTTP