← Índice del curso

Internet de las Cosas · Tema 19

HTTP desde un dispositivo

HTTP es el idioma de la web, y probablemente ya lo hablás desde tus aplicaciones: un navegador, fetch, requests. Un microcontrolador puede hablarlo igual, y con eso entra por la puerta ancha a casi cualquier servicio. Pero lo que en una aplicación pasa inadvertido (cabeceras, códigos, tiempos de espera) en un dispositivo con batería hay que mirarlo con lupa.

01 · Punto de partida

El idioma de la web

Con el bloque de redes cerrado, el dato ya puede salir del dispositivo y llegar a un servidor por TCP/IP. Ahora hace falta que ambos se entiendan: que el servidor sepa qué es lo que llegó y qué se espera de él. Ése es el trabajo de los protocolos de aplicación, y el primero que vemos es HTTP (HyperText Transfer Protocol), el que usa la web.

HTTP tiene una virtud decisiva para IoT: es el lenguaje que hablan casi todos los servicios. Si querés guardar un dato en una base de datos en la nube, publicar una lectura en una plataforma, consultar la hora o descargar una configuración, lo más probable es que haya una interfaz HTTP. Atraviesa cualquier firewall (el puerto 443 casi siempre está abierto), se puede probar con un navegador o curl, y hay bibliotecas en cualquier plataforma. Tiene también sus costos: es un protocolo de texto con cabeceras pesadas, pensado para páginas web, no para sensores con batería. Este tema enseña a usarlo con los ojos abiertos.

Los fragmentos de C++ siguen el estilo Arduino para ESP32; los nombres de las clases pueden variar entre versiones de la biblioteca.

02 · Estructura

Anatomía de una conversación HTTP

HTTP es un protocolo de pedido y respuesta. El cliente (el dispositivo) envía una petición; el servidor devuelve una respuesta. Ambas tienen la misma forma, en texto plano:

  1. Una línea inicial. En la petición: el método, la ruta y la versión. En la respuesta: la versión, el código de estado y su texto.
  2. Las cabeceras: pares Nombre: valor, una por línea, con metadatos del mensaje.
  3. Una línea en blanco que marca el fin de las cabeceras.
  4. El cuerpo (opcional): los datos en sí.
Una petición y una respuesta HTTP A la izquierda, una petición POST con su línea inicial, cinco cabeceras, una línea en blanco y un cuerpo JSON con una lectura. A la derecha, la respuesta con la línea de estado 200 OK, tres cabeceras, la línea en blanco y el cuerpo con ok verdadero. POST /api/v1/lecturas HTTP/1.1 Host: datos.ejemplo.com Content-Type: application/json Content-Length: 42 Authorization: Bearer k7Qz3Wm9… Connection: close (línea en blanco) {"d":"c07","t":1794061920,"i":60,"v":[41]} HTTP/1.1 200 OK Content-Type: application/json Content-Length: 11 Connection: close (línea en blanco) {"ok":true} Petición del dispositivoRespuesta del servidor Línea inicial · cabeceras · cuerpo Celeste: línea inicial · gris: cabeceras · claro: cuerpo El cuerpo (los datos) es una fracción de los bytes

Observá lo que el diagrama muestra: los datos de la lectura ([41]) ocupan unos pocos bytes dentro de un mensaje de más de doscientos. Todo lo demás es el «sobre» necesario para que el servidor sepa a dónde va, qué formato tiene y quién lo envía.

Una aclaración de versiones. Lo que ves es HTTP/1.1, un protocolo de texto que es el más sencillo de implementar en un microcontrolador y el que soporta casi todo. Las versiones más nuevas (HTTP/2 y HTTP/3) son binarias, más eficientes y multiplexan varias conversaciones, pero exigen más memoria y bibliotecas más complejas; en dispositivos pequeños, HTTP/1.1 sigue siendo lo habitual.

03 · Qué pedir y dónde

Métodos y direcciones

El método dice qué se quiere hacer con el recurso. Los cinco más usados:

Métodos HTTP habituales, su significado y si son seguros e idempotentes.
MétodoSignificado¿Cambia algo en el servidor?¿Idempotente?Uso en IoT
GETLeer un recursoNoSíDescargar configuración, consultar la hora o un comando pendiente
POSTCrear un recurso o enviar datos para procesarSíNoEnviar una lectura
PUTReemplazar un recurso completoSíSíFijar el estado de un dispositivo (tema 11)
PATCHModificar una parte de un recursoSíDependeCambiar un parámetro de la configuración
DELETEEliminar un recursoSíSíBorrar un registro o una suscripción

La idempotencia (el mismo pedido repetido deja el mismo resultado) es la clave para decidir si un método se puede reintentar sin riesgo. Un GET o un PUT repetidos son inocuos. Un POST repetido puede crear dos lecturas iguales: por eso conviene que el cuerpo lleve un identificador único para que el servidor descarte duplicados (tema 18). El tema 20 profundiza en GET y POST.

La URL dice dónde está el recurso. Se compone de partes que ya conocés del tema 17:

https://datos.ejemplo.com:443/api/v1/lecturas?dispositivo=c07
  • https es el esquema: HTTP sobre TLS (puerto 443 por defecto); http es sin cifrar (puerto 80).
  • datos.ejemplo.com es el servidor, que se resuelve por DNS; opcionalmente va un puerto.
  • /api/v1/lecturas es la ruta del recurso. Incluir la versión (v1) permite cambiar la interfaz sin romper a los equipos instalados.
  • ?dispositivo=c07 es la consulta: parámetros en pares nombre=valor.

04 · Metadatos

Las cabeceras que importan

Las cabeceras son información sobre el mensaje. Hay decenas definidas; para un dispositivo IoT alcanzan unas pocas, y conviene enviar solo las necesarias, porque cada una cuesta bytes en cada petición.

Cabeceras más relevantes para un cliente IoT.
CabeceraDirecciónQué dice¿Imprescindible?
HostPeticiónEl nombre del servidor (permite varios sitios en una misma dirección)Sí, en HTTP/1.1
Content-TypeAmbasEl formato del cuerpo (application/json)Sí, si hay cuerpo
Content-LengthAmbasCuántos bytes tiene el cuerpo, para saber dónde terminaSí, si hay cuerpo
AuthorizationPeticiónCredencial del cliente (un token o una clave)Si la interfaz exige autenticación
ConnectionAmbasclose cierra tras la respuesta; keep-alive mantiene la conexiónRecomendable
AcceptPeticiónQué formatos acepta el clienteNo
User-AgentPeticiónQuién es el cliente (modelo y versión del firmware)No, pero útil para diagnóstico
Retry-AfterRespuestaCuántos segundos esperar antes de reintentar (con 429 y 503)Hay que respetarla si llega
LocationRespuestaLa nueva dirección, en una redirecciónSi el código es de redirección
DateRespuestaLa hora del servidorNo, pero sirve para sincronizar el reloj

La Authorization merece atención especial. Es la forma habitual de identificar al dispositivo (un token por equipo, nunca uno compartido por toda la flota), y solo es seguro sobre HTTPS: sobre HTTP, el token viaja a la vista de cualquiera. Y la cabecera Date es un regalo: cada respuesta trae la hora del servidor, una forma gratuita de poner en hora el reloj del dispositivo, algo necesario para validar certificados.

05 · Interpretar la respuesta

Códigos de estado: qué hacer con cada uno

La primera cosa que mira un cliente al recibir la respuesta es el código de estado: un número de tres cifras cuya primera cifra indica la familia. Un cliente de dispositivo no se limita a «anduvo o no anduvo»: decide qué hacer según el código.

Códigos de estado habituales y la acción recomendada para un dispositivo.
CódigoSignificadoAcción del dispositivo
200 OKTodo bien, con cuerpoProcesar la respuesta; borrar del buffer lo enviado
201 Created / 204 No ContentCreado / bien, sin cuerpoIgual que 200
301, 302, 307, 308Redirección: el recurso está en otra dirección (Location)Seguir la nueva dirección, con un máximo de saltos; nunca bajar de HTTPS a HTTP
400 Bad RequestEl mensaje está mal formadoNo reintentar: hay un error de formato; registrar y reportar
401 UnauthorizedCredencial ausente o inválidaNo reintentar sin parar; renovar la credencial o reportar
403 ForbiddenAutenticado, pero sin permisoNo reintentar; reportar
404 Not FoundEl recurso no existeNo reintentar; revisar la dirección
408 Request TimeoutEl servidor se cansó de esperarReintentar con espera creciente
429 Too Many RequestsEl cliente envía demasiadoEsperar lo que indique Retry-After; reducir la frecuencia
500 Internal Server ErrorFalla del servidorReintentar unas pocas veces con espera creciente; si persiste, reportar
502, 503, 504Servidor o intermediario caído, saturado o sin respuestaReintentar con espera creciente y retardo aleatorio

La regla de fondo, que enlaza con el tema 18: los códigos 4xx son errores del cliente (el dispositivo hizo algo mal) y reintentar el mismo pedido dará el mismo resultado, con el costo de energía y de tráfico; los 5xx son errores del servidor y suelen ser transitorios. La excepción son 408 y 429, que son 4xx pero se recuperan con tiempo. Y el 429 trae una instrucción explícita: obedecé el Retry-After. Ignorarla es la mejor forma de que el servidor bloquee al dispositivo.

06 · Programar el cliente

Un cliente HTTP en el microcontrolador

Las plataformas con Wi-Fi traen una biblioteca de cliente HTTP que se ocupa de abrir la conexión, armar las cabeceras y leer la respuesta. El siguiente fragmento envía una lectura por POST sobre HTTPS, con tiempo máximo y manejo de los resultados:

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include "secretos.h"                          // define TOKEN_DISPOSITIVO y el certificado raíz CERT_RAIZ

// Devuelve: 0 = enviado, 1 = reintentar más tarde, 2 = error permanente
int enviarLectura(const String &json) {
  WiFiClientSecure cliente;
  cliente.setCACert(CERT_RAIZ);                // verificar el certificado del servidor (tema 41)
  HTTPClient http;
  http.setTimeout(5000);                       // 5 s como máximo en cada espera
  if (!http.begin(cliente, "https://datos.ejemplo.com/api/v1/lecturas")) return 1;
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", String("Bearer ") + TOKEN_DISPOSITIVO);

  int codigo = http.POST(json);
  int resultado;
  if (codigo < 0) {                            // la biblioteca no pudo completar la petición
    Serial.print("Error de red: ");
    Serial.println(HTTPClient::errorToString(codigo));
    resultado = 1;                             // sin red, sin DNS, sin conexión o sin respuesta: reintentar
  } else if (codigo >= 200 && codigo < 300) {
    resultado = 0;                             // éxito
  } else if (codigo == 408 || codigo == 429 || codigo >= 500) {
    resultado = 1;                             // falla transitoria
  } else {
    Serial.printf("Rechazado con código %d\n", codigo);
    resultado = 2;                             // 400, 401, 403, 404...: no reintentar igual
  }
  http.end();                                  // liberar la conexión y la memoria
  return resultado;
}

Tres decisiones de este fragmento reflejan lo aprendido. Una: los códigos negativos son errores de la biblioteca, no del servidor (conexión rechazada, tiempo agotado, sin respuesta) y se tratan como fallas de red: el dato queda en el buffer y se reintenta. Otra: cada código se traduce en una de tres acciones: listo, reintentar o descartar el reintento. Y otra más: se llama a http.end() siempre, porque la memoria de un microcontrolador es escasa y una conexión abierta olvidada la agota.

Dos cuidados de memoria y de seguridad. Primero, no cargar respuestas grandes completas en RAM: una respuesta de 100 KB no entra en un microcontrolador chico. Se lee por trozos y se procesa a medida que llega (o se limita el tamaño máximo aceptado). Segundo, las respuestas pueden venir en transferencia fragmentada (Transfer-Encoding: chunked) cuando el servidor no conoce de antemano el largo; las bibliotecas lo manejan, pero hay que contar con que no hay Content-Length. Y si se usa HTTPS, se verifica el certificado del servidor: desactivar la verificación «para que ande» deja la puerta abierta a quien se interponga (bloque de seguridad).

07 · Elegir con criterio

Cuándo conviene HTTP y cuánto cuesta

HTTP es el punto de partida más simple para llevar datos a un servidor, pero no siempre el más eficiente. Pesan tres costos que ya conocés de temas anteriores:

  • Bytes. Las cabeceras de una petición suelen ser mucho más grandes que unos pocos bytes de datos. Una lectura de 53 bytes útiles puede viajar en unos 270 a 880 bytes.
  • Viajes. Cada petición sobre una conexión nueva paga DNS, TCP y TLS (tema 17): hasta cinco viajes de ida y vuelta para un dato.
  • Energía. Todo lo anterior es tiempo de radio encendida (tema 14).
Cuándo HTTP es una buena opción y cuándo conviene otro protocolo.
HTTP conviene cuando…Conviene otro protocolo (como MQTT, tema 25) cuando…
El dispositivo envía de vez en cuando, con alimentación eléctrica o ritmo bajoEl envío es frecuente y la batería es crítica
Hay que integrar con servicios web existentes (APIs REST, plataformas en la nube)El dispositivo debe recibir órdenes del servidor con poca demora
Se necesita atravesar firewalls corporativos restrictivosHay muchos dispositivos que publican y muchos consumidores que escuchan
La simplicidad y la facilidad de depuración pesan más que los bytesEl ancho de banda cuesta (celular, LoRa) o es muy limitado
El intercambio es de pedido y respuesta (consultar la configuración, enviar un lote)Hace falta una conversación bidireccional continua

El truco que hace razonable a HTTP en muchos casos es el que ya vimos: pocos envíos, con lotes de lecturas, cabeceras mínimas y conexión cifrada reutilizada. Un POST por hora con sesenta lecturas pesa casi lo mismo en cabeceras que un POST con una sola, y reparte esos bytes entre sesenta medidas.

08 · Analogía con programación

Medir una petición en Python

El programa construye peticiones y respuestas HTTP reales, línea por línea, y cuenta sus bytes. Prueba tres juegos de cabeceras: mínimas (solo las imprescindibles), habituales (agregan el agente de usuario, el formato aceptado y la credencial) y pesadas (suman idioma, cookie, control de caché e identificador de petición, como una aplicación web típica). El cuerpo es el mensaje compacto del tema 12 con 1, 10 o 60 lecturas. Al final imprime una petición completa y una función accion que traduce cada código de estado a una decisión.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

import json

HOST = "datos.ejemplo.com"
RUTA = "/api/v1/lecturas"
TOKEN = "k7Qz3Wm9Xr2Lp5Ty8Vb1Nc4Jd6Hf0Gs3Aa5Ze7Uo9Ii2Eu4Ow"[:43]
COOKIE = "a1b2c3d4" * 8
UUID = "123e4567-e89b-12d3-a456-426614174000"


def cuerpo(n):
    """Mensaje compacto con n lecturas de temperatura (décimas de grado)."""
    valores = [41 + (i * 3) % 8 for i in range(n)]
    return json.dumps({"d": "c07", "t": 1794061920, "i": 60, "v": valores}, separators=(",", ":"))


def peticion(n, juego):
    cuerpo_json = cuerpo(n)
    lineas = [f"POST {RUTA} HTTP/1.1",
              f"Host: {HOST}",
              "Content-Type: application/json",
              f"Content-Length: {len(cuerpo_json)}",
              "Connection: close"]
    if juego in ("habituales", "pesadas"):
        lineas += ["User-Agent: sensor-esp32/1.2.0", "Accept: application/json", f"Authorization: Bearer {TOKEN}"]
    if juego == "pesadas":
        lineas += ["Accept-Language: es-AR,es;q=0.9", f"Cookie: sesion={COOKIE}", "Cache-Control: no-cache", f"X-Request-Id: {UUID}"]
    return "\r\n".join(lineas) + "\r\n\r\n" + cuerpo_json


def respuesta(juego):
    cuerpo_json = '{"ok":true}'
    lineas = ["HTTP/1.1 200 OK",
              "Content-Type: application/json",
              f"Content-Length: {len(cuerpo_json)}",
              "Connection: close"]
    if juego in ("habituales", "pesadas"):
        lineas += ["Date: Sat, 07 Nov 2026 14:32:01 GMT", "Server: nginx/1.25.3"]
    if juego == "pesadas":
        lineas += [f"Set-Cookie: sesion={COOKIE}; Path=/; HttpOnly; Secure", "Cache-Control: no-store",
                   f"X-Request-Id: {UUID}", "Strict-Transport-Security: max-age=31536000"]
    return "\r\n".join(lineas) + "\r\n\r\n" + cuerpo_json


print("cabeceras    lecturas  útil   petición  respuesta  total   % útil")
for juego in ("mínimas", "habituales", "pesadas"):
    clave = "minimas" if juego == "mínimas" else juego
    for n in (1, 10, 60):
        util = len(cuerpo(n)) + len('{"ok":true}')
        pet = len(peticion(n, clave))
        res = len(respuesta(clave))
        total = pet + res
        print(f"{juego:<12} {n:>7} {util:>6} {pet:>9} {res:>10} {total:>7} {100 * util / total:>7.1f} %")

print()
print(peticion(1, "habituales"))


def accion(codigo):
    if 200 <= codigo < 300:
        return "éxito: seguir"
    if codigo in (301, 302, 307, 308):
        return "redirección: seguir la nueva dirección, con límite de saltos"
    if codigo in (408, 429, 500, 502, 503, 504):
        return "falla transitoria: reintentar con espera creciente"
    if 400 <= codigo < 500:
        return "error del cliente: no reintentar, corregir y reportar"
    return "otro: registrar y reportar"


print()
for codigo in (200, 201, 204, 302, 400, 401, 404, 408, 429, 500, 503):
    print(codigo, "->", accion(codigo))

Resultado con los valores iniciales. Con cabeceras mínimas, una lectura por petición necesita 172 bytes de petición y 101 de respuesta (273 en total, de los cuales 53 son datos: 19,4 %); con 10 lecturas, 300 bytes (26,7 % útil); con 60, 451 bytes (51,0 %). Con cabeceras habituales: 457, 484 y 635 bytes (11,6 %, 16,5 % y 36,2 % útil). Con cabeceras pesadas: 881, 908 y 1059 bytes (6,0 %, 8,8 % y 21,7 %). En cuanto a los códigos, 200, 201 y 204 se consideran éxito; el 302 es una redirección; 408, 429, 500 y 503 son transitorios y se reintentan con espera creciente; y 400, 401 y 404 son errores del cliente que no se reintentan.

Las cifras dicen tres cosas. Una: con una lectura por petición, la mayor parte del tráfico son cabeceras, incluso con las mínimas. Otra: las cabeceras de más se pagan en cada envío: las «pesadas» triplican el tamaño de la conversación sin aportar nada que el dispositivo use. Y la tercera: agrupar sesenta lecturas baja el costo por lectura de 273 a 7,5 bytes, 36 veces menos, con las mismas cabeceras mínimas.

09 · Exploración interactiva

¿Cuánto pesa una petición?, de extremo a extremo

Este laboratorio construye las mismas peticiones y respuestas que el código anterior y cuenta sus bytes. Elegí cuántas lecturas lleva el cuerpo y qué conjunto de cabeceras usa el cliente. Cada barra muestra el tráfico total de una conversación completa (petición y respuesta) dividido en: la línea inicial con las cabeceras mínimas, las cabeceras adicionales de la petición, las cabeceras de la respuesta y los datos útiles.

Peso de una petición POST con una respuesta

Tamaño de la petición—
Tamaño de la respuesta—
Tráfico total—
Parte que son datos—
Bytes por lectura—
Resultado—

Elegí los parámetros y presioná «Armar la petición».

Recorrido guiado

  1. Con los valores iniciales (10 lecturas, cabeceras habituales), armá la petición. 324 bytes de petición y 160 de respuesta: 484 en total, 16,5 % de datos y 48,4 bytes por lectura: «Aceptable».
  2. Cambiá a 1 lectura: 457 bytes en total y apenas 11,6 % de datos. «Casi todo es sobre»: 457 bytes de tráfico por una sola medida.
  3. Pasá a cabeceras mínimas con 1 lectura: 273 bytes y 19,4 %. Quitar cabeceras que el servidor no necesita ahorró 184 bytes por envío.
  4. Elegí Con cookies y rastreo y 1 lectura: 881 bytes y 6,0 % de datos. Las cabeceras de una aplicación web pesan más del triple que las mínimas.
  5. Con cabeceras mínimas, subí a 60 lecturas: 451 bytes en total, 51,0 % de datos y 7,5 bytes por lectura: «Eficiente». El mismo cliente, 36 veces menos tráfico por lectura.
  6. Con 60 lecturas y cabeceras pesadas: 1059 bytes y 21,7 %. Aun así 17,7 bytes por lectura, menos que una lectura suelta con cabeceras mínimas. Agrupar es más rentable que podar cabeceras, y lo mejor es hacer ambas cosas.
Desafío: el costo de una cookie

La cookie del juego pesado ocupa 81 bytes en la petición (Cookie: sesion=… más el salto de línea) y 111 en la respuesta (Set-Cookie con sus atributos): 192 bytes por intercambio. Si el dispositivo envía cada 15 minutos (96 veces por día), son 18.432 bytes diarios, unos 0,55 MB al mes por equipo, solo para llevar una cookie que el dispositivo ni siquiera usa. En una flota de 10.000 equipos son 5,5 GB al mes. Con un plan celular de US$ 2 por MB, ese único campo innecesario costaría unos US$ 11.000 al mes. En IoT cada cabecera se multiplica por todos los envíos de todos los equipos: conviene preguntarse, para cada una, quién la usa.

10 · Método de trabajo

Lista de verificación de un cliente HTTP

  1. HTTPS siempre para credenciales y datos, con verificación del certificado.
  2. Cabeceras mínimas: Host, Content-Type, Content-Length, Authorization y poco más.
  3. Un token por dispositivo, revocable, guardado en memoria no volátil.
  4. Tiempos máximos en la conexión y en la lectura de la respuesta.
  5. Decisión por código: éxito, reintento con espera creciente o descarte del reintento, según la tabla de estados; respetar Retry-After.
  6. Lotes: varias lecturas por petición, con identificador para descartar duplicados.
  7. Memoria: limitar el tamaño de las respuestas aceptadas y liberar siempre la conexión.
  8. Redirecciones con límite de saltos y sin bajar de HTTPS a HTTP.
  9. Hora: aprovechar la cabecera Date de las respuestas para mantener el reloj.
  10. Pruebas: contra un servidor que responda cada código (200, 401, 404, 429, 500, 503), que tarde de más y que corte a mitad de la respuesta.

Una herramienta valiosa en el escritorio: probá primero la misma petición con curl -v y comprobá que el servidor responde lo esperado. Si anda en curl y no en el dispositivo, el problema está en el firmware o en la red del dispositivo, no en la interfaz.

11 · Comprensión

Confusiones frecuentes

«Si el código es distinto de 200, hay que reintentar»

Solo los errores transitorios (408, 429, 5xx) se reintentan. Un 400, 401, 403 o 404 volverá a fallar igual: reintentar solo gasta batería y tráfico, y puede hacer que el servidor bloquee al dispositivo.

«HTTP es solo para páginas web»

También es el transporte de la mayoría de las APIs de servicios en la nube. Un dispositivo que envía datos con POST usa HTTP igual que cualquier aplicación; lo que cambia son las restricciones de energía y de memoria.

«Las cabeceras no importan, son unos pocos bytes»

Con mensajes de decenas de bytes, las cabeceras son la mayor parte del tráfico, y se pagan en cada envío de cada equipo. Multiplicadas por una flota, son costo real en datos y en energía.

«Un token en una cabecera es seguro porque va oculto»

No va oculto: es texto plano. Solo está protegido si la conexión es HTTPS. Sobre HTTP, cualquiera en el camino lo lee y lo reutiliza.

«Para ahorrar tiempo puedo desactivar la verificación del certificado»

Funciona hasta que alguien se interpone en la red: sin verificación, el dispositivo envía sus credenciales y sus datos a un impostor sin darse cuenta. Si no anda con la verificación, se corrige la causa (certificado raíz, reloj), no se la desactiva.

«Las respuestas siempre caben en memoria»

Una respuesta inesperadamente grande (una página de error, una descarga) puede agotar la RAM y colgar el dispositivo. Se limita el tamaño aceptado y se lee por trozos.

12 · Práctica guiada

Predecí antes de comprobar

1. Armar una cabecera

El cuerpo de una petición es {"t":22.3}. ¿Qué valor lleva la cabecera Content-Length? ¿Y qué cabecera indica que es JSON?

Ver solución

El cuerpo tiene 10 caracteres, así que Content-Length: 10. El formato se indica con Content-Type: application/json. Si Content-Length no coincide con el tamaño real, el servidor puede cortar el mensaje o quedarse esperando bytes que no llegan.

2. Qué hacer con cada código

Indicá si reintentarías y cómo: (a) 503; (b) 401; (c) 429 con Retry-After: 120; (d) 404; (e) 500 por tercera vez seguida.

Ver solución

(a) Sí, con espera creciente y retardo aleatorio. (b) No sin cambios: la credencial es inválida; renovarla o reportar. (c) Sí, pero esperando 120 segundos como indica el servidor, y reducir la frecuencia de envíos. (d) No: la dirección no existe; revisar la configuración y reportar. (e) Un 500 repetido ya no parece transitorio: espaciar los intentos (disyuntor), conservar los datos en el buffer y reportar.

3. Idempotencia

Un POST de una lectura llega al servidor pero la respuesta se pierde, y el dispositivo lo reenvía. ¿Qué ocurre y cómo se evita el daño?

Ver solución

El POST no es idempotente: el servidor recibe dos veces la misma lectura y, sin protección, la guarda duplicada. Se evita incluyendo en el cuerpo un identificador único (dispositivo y momento o secuencia) para que el servidor la descarte, o usando un método idempotente como PUT sobre un recurso con identificador propio.

4. Costo de las cabeceras

Una lectura suelta con cabeceras mínimas pesa 273 bytes en total (53 de datos). Sesenta lecturas en un lote con las mismas cabeceras pesan 451. ¿Cuántos bytes por lectura en cada caso y cuántas veces menos?

Ver solución

Suelta: 273 bytes por lectura. En lote: 451 / 60 ≈ 7,5 bytes por lectura. Es 36 veces menos tráfico por lectura con las mismas cabeceras, sin perder ninguna medida (y con una sola conexión en lugar de sesenta).

5. Redirección

Una petición a http://datos.ejemplo.com/api recibe un 301 con Location: https://datos.ejemplo.com/api. ¿Qué debe hacer el dispositivo? ¿Y si el Location apuntara a un servidor http:// distinto?

Ver solución

Seguir la redirección hacia HTTPS, con un límite de saltos (por ejemplo, 3), y guardar la nueva dirección para usarla directamente la próxima vez (cada redirección cuesta un viaje más). Si el destino bajara de HTTPS a http:// o a un servidor no previsto, no debe seguirlo: podría enviar la credencial en claro o a un impostor.

6. Errores de la biblioteca

Una llamada a http.POST devuelve −1. ¿Qué significa en general y qué hace el firmware con la lectura?

Ver solución

Un valor negativo no es un código HTTP: es un error de la biblioteca, por ejemplo conexión rechazada o tiempo agotado (sin red, sin DNS, servidor inaccesible). El firmware lo trata como una falla de red: conserva la lectura en el buffer, aplica la espera creciente y reintenta más tarde, sin descartar el dato.

13 · Síntesis

Ideas para recordar

  • HTTP es el idioma de la web y de la mayoría de las APIs: simple, universal, fácil de depurar, aunque pensado para páginas y no para sensores con batería.
  • Una conversación es un pedido y una respuesta, cada uno con una línea inicial, cabeceras, una línea en blanco y un cuerpo opcional; en HTTP/1.1 es texto plano.
  • El método dice qué hacer: GET lee, POST envía datos, PUT fija un recurso, DELETE lo borra; GET y PUT son idempotentes, POST no.
  • Hay que enviar solo las cabeceras necesarias; Authorization debe ir siempre sobre HTTPS, con un token distinto por dispositivo.
  • Los códigos 2xx son éxito; los 4xx son errores del cliente que no se reintentan igual (salvo 408 y 429); los 5xx suelen ser transitorios y se reintentan con espera creciente.
  • El Retry-After de un 429 o un 503 se obedece.
  • Un cliente HTTP en un microcontrolador pone tiempos máximos, traduce cada código en una acción, trata los códigos negativos como fallas de red, limita el tamaño de las respuestas y libera siempre la conexión.
  • Se verifica el certificado del servidor; desactivarlo deja al dispositivo expuesto a impostores.
  • Las cabeceras son la mayor parte del tráfico de un mensaje corto; agrupar lecturas en lotes y podar cabeceras reduce el costo por lectura decenas de veces.

¿Listo para el tema 20?

Antes de avanzar, probá con curl -v una petición POST a un servicio de prueba y mirá cada línea de la conversación. Contá los bytes de petición y respuesta, marcá cuáles cabeceras el servicio realmente necesita y calculá cuánto ahorrarías en una flota de mil equipos si las demás se quitaran.

En el próximo tema trabajamos con las peticiones GET y POST: cómo el dispositivo lee su configuración del servidor, cómo envía lecturas y qué diferencias hay en el diseño de uno y otro.