01 · Punto de partida
El contrato entre firmware y servidor
Los últimos tres temas armaron las piezas: HTTP como protocolo (19), GET y POST como los dos movimientos del dispositivo (20) y JSON como formato de los datos (21). Ahora las juntamos en un diseño: la API (Application Programming Interface), el conjunto de rutas, verbos y formatos con que el firmware y el servidor se entienden.
El estilo más difundido para diseñarlas se llama REST (Representational State Transfer). No es un protocolo sino un conjunto de convenciones sobre HTTP: organizar la interfaz alrededor de recursos (cosas con nombre, como un dispositivo o una lectura), identificarlos con rutas, y manipularlos con los verbos estándar. Su gran virtud es que cualquiera que conozca HTTP la entiende sin leer demasiada documentación. Pero REST «de manual» fue pensado para aplicaciones con una conexión permanente y recursos de sobra, y un dispositivo con batería necesita adaptaciones. Ése es el tema.
Una advertencia sobre los números: los costos del laboratorio vienen de un modelo con valores didácticos, y la API del código de Python es una simulación mínima en memoria, no un servidor de producción.
02 · Sustantivos
Recursos y rutas
La idea central de REST es que la API habla de cosas, no de acciones. Las rutas son sustantivos que forman una jerarquía, y la acción la pone el verbo HTTP. Se evita /obtenerLecturas o /enviarLectura y se escribe GET /lecturas o POST /lecturas.
Las convenciones útiles para nombrar y organizar:
- Plural para las colecciones (
/dispositivos) y el identificador para el elemento (/dispositivos/c07). Los recursos que pertenecen a otro cuelgan de él (/dispositivos/c07/lecturas). - Minúsculas, sin tildes ni espacios, con guiones o guiones bajos: las rutas viajan en cada petición y se codifican (tema 20).
- Jerarquía poco profunda: dos o tres niveles. Más es difícil de recordar y de enrutar.
- El identificador en la ruta, no en el cuerpo, para lo que se refiere a un elemento concreto.
| Operación | Método y ruta | Éxito | Qué hace |
|---|---|---|---|
| Enviar una lectura | POST /api/v1/dispositivos/{id}/lecturas | 201 Created (o 200 si ya existía) | Crea la lectura; el cuerpo trae un identificador para evitar duplicados |
| Leer la configuración | GET /api/v1/dispositivos/{id}/configuracion | 200 OK o 304 | Devuelve la configuración con su versión (ETag) |
| Ver comandos pendientes | GET /api/v1/dispositivos/{id}/comandos | 200 OK | Lista los comandos no ejecutados y vigentes |
| Informar un resultado | PUT /api/v1/dispositivos/{id}/comandos/{idc}/resultado | 200 o 204 | Fija el resultado; repetirlo no tiene efecto adicional |
| Registrar el dispositivo | POST /api/v1/dispositivos | 201 Created | Alta de un equipo nuevo (tema 42) |
03 · Acciones y respuestas
Verbos, códigos de estado e idempotencia
Los verbos ya los conocés (tema 19): GET lee, POST crea o procesa, PUT fija un recurso completo, PATCH lo modifica en parte, DELETE lo elimina. En una API coherente, cada verbo se comporta de forma previsible: GET nunca cambia nada, PUT y DELETE son idempotentes, POST no lo es.
Los códigos de estado también: el éxito es 2xx, el error del cliente 4xx, el del servidor 5xx. Una API que los usa con precisión le ahorra trabajo al firmware, porque cada código dice qué hacer (tema 19). Algunos que conviene usar con exactitud en IoT:
| Código | Cuándo usarlo | Qué hace el dispositivo |
|---|---|---|
| 201 Created | Se creó el recurso (puede traer Location con su dirección) | Borra la lectura de su buffer |
| 200 OK con «duplicado» | El recurso ya existía (reintento): es éxito, no error | Borra la lectura del buffer |
| 204 No Content | Hecho, sin nada que devolver | Sigue |
| 400 Bad Request | El mensaje está mal formado | No reintenta; registra el error |
| 401 Unauthorized | No se identificó o la credencial no vale | No reintenta sin parar; renueva el token |
| 403 Forbidden | Se identificó, pero no tiene permiso para esto | No reintenta; reporta |
| 404 Not Found | La ruta o el recurso no existen | No reintenta; revisa la dirección |
| 409 Conflict | El estado del servidor impide la operación (por ejemplo, un comando ya cancelado) | No reintenta igual; consulta el estado actual |
| 422 Unprocessable Content | El mensaje está bien formado pero su contenido no tiene sentido (valor fuera de rango) | No reintenta; reporta el dato |
| 429 Too Many Requests | El dispositivo supera el límite de uso | Espera lo que diga Retry-After |
| 503 Service Unavailable | Mantenimiento o saturación | Reintenta con espera creciente |
Dos distinciones finas que evitan mucha confusión. Una: 401 contra 403. El 401 dice «no sé quién sos»; el 403 dice «sé quién sos y no podés hacer esto». Un token de otro dispositivo es un 403. Otra: 400 contra 422. El 400 es un mensaje que no se puede ni leer; el 422 es uno que se leyó bien pero dice algo imposible. Cuando el cliente sabe cuál es cuál, decide mejor qué hacer.
Y la idempotencia, otra vez: un reintento de POST con el mismo identificador de lectura debe dar el mismo resultado final. Hay dos formas. Un identificador en el cuerpo (el servidor detecta que ya tiene esa lectura y responde 200 con «duplicado») o una cabecera de clave de idempotencia (Idempotency-Key: …) que el servidor recuerda por un tiempo. En ambos casos, la respuesta a un duplicado es un éxito, no un error, de modo que el dispositivo pueda borrar el dato de su buffer sin dudas.
04 · Identidad y permisos
Quién eres y qué puedes hacer
Una API que acepta datos de dispositivos es una puerta abierta a Internet. Dos preguntas la protegen, y conviene no mezclarlas:
- Autenticación: ¿quién es el que llama? El dispositivo presenta una credencial y el servidor la verifica.
- Autorización: ¿qué puede hacer ese que llama? Aunque se sepa quién es, solo accede a lo que le corresponde.
Las prácticas básicas para dispositivos:
| Mecanismo | Cómo funciona | Ventajas | Cuidados |
|---|---|---|---|
Token por dispositivo (Authorization: Bearer …) | Cada equipo tiene una cadena secreta única que presenta en cada petición | Simple; fácil de revocar de a uno | Solo sobre HTTPS; guardarlo en memoria protegida; que venza o se pueda rotar |
| Clave compartida de toda la flota | Todos los equipos usan la misma | Es la más fácil | Evitarla: si se filtra una, se compromete toda la flota y no se puede revocar de a uno |
| Certificado de cliente (TLS mutuo) | Cada equipo se identifica con un certificado en el apretón de manos de TLS | Muy robusto; no hay secreto en cada petición | Más costoso en cálculo, memoria y gestión de certificados |
| Token firmado de corta duración (JWT) | El servidor entrega un token que vence en minutos u horas, firmado | Limita el daño de una filtración | Requiere reloj correcto y un paso de renovación |
La regla de autorización más importante para una API de flota es sencilla: el token de un dispositivo solo vale para sus propias rutas. El token de c08 no debe poder escribir en /dispositivos/c07/lecturas. Parece obvio, y su ausencia es una de las vulnerabilidades más comunes de los sistemas IoT: con un solo equipo comprometido se contaminan o se leen los datos de todos los demás. Se comprueba en el servidor en cada petición, comparando la identidad del token con el identificador de la ruta. El código de este tema lo muestra.
Además: las credenciales no van en la URL (tema 20), los tokens se guardan en memoria no volátil (no en el código ni en el repositorio), se pueden revocar sin tocar al resto de la flota, y el servidor registra los intentos fallidos para detectar abusos. Los detalles de provisión y rotación de credenciales los vemos en el bloque de seguridad.
05 · Cambiar sin romper
Versionado y evolución
Una API de dispositivos tiene una restricción que las demás no conocen: sus clientes no se pueden actualizar a la vez, y algunos no se actualizarán nunca. Hay equipos en campo con el firmware de hace tres años que hablan con la API de hoy. Eso obliga a evolucionarla con cuidado.
- La versión va en la ruta (
/api/v1/…). Cuando hace falta un cambio incompatible, aparece/api/v2/…y la versión 1 sigue funcionando durante el tiempo que haga falta. - Agregar es seguro, cambiar no. Sumar un campo opcional a una respuesta o a un pedido no rompe a nadie, siempre que el firmware ignore lo que no conoce (tema 21). Cambiar el nombre, el tipo o la unidad de un campo existente, sí.
- Los clientes viejos son datos. El servidor sabe qué versión usa cada equipo (por la ruta o el
User-Agent) y puede medir cuántos quedan antes de retirar una versión. - Un plan de retiro: anunciar, medir, dar un plazo y, si quedan equipos, actualizarlos de forma remota (tema 43) antes de apagar la versión.
El código de ejemplo prueba una ruta /api/v2/… inexistente y obtiene un 404, lo mismo que vería un equipo que se adelantara a una versión que el servidor aún no publicó.
06 · Cantidades
Colecciones grandes y límites
Una colección puede crecer sin límite: un dispositivo acumula millones de lecturas con los años. Una petición GET /lecturas que las devuelva todas sería un desastre para el servidor y, peor, para el dispositivo que intente recibirlas. Hay tres mecanismos de control:
- Paginación: la respuesta trae un número máximo de elementos y una forma de pedir los siguientes (
?limite=50&desde=1794061920, o un cursor opaco). Hay que fijar un tope máximo en el servidor aunque el cliente pida más. - Filtros y ventanas de tiempo: pedir solo lo que interesa (
?desde=…&hasta=…), de modo que un dispositivo con poca memoria reciba una porción acotada. - Límites de uso (rate limiting): el servidor cuenta las peticiones de cada cliente y rechaza con
429 Too Many Requestslas que exceden la cuota, indicando conRetry-Aftercuánto esperar. Protege al servicio de un firmware con un error que consulta sin parar, y de un ataque.
Para un dispositivo, además, conviene que las respuestas sean chicas y de tamaño predecible. El tema 21 mostró que una respuesta de 200 lecturas pide más de 10 KB de memoria solo para analizarla; con un tope de 20 elementos por página, el mismo dato llega en porciones que el microcontrolador procesa sin problemas.
07 · Adaptar el estilo
Una API amable con el dispositivo
REST puro propone una ruta para cada recurso y que el cliente combine las que necesite. En una aplicación con buena conexión eso no cuesta casi nada. Pero en un dispositivo, cada petición es una conexión (o una vuelta de ida y vuelta) y cada conexión es energía. Un ciclo típico del dispositivo, con REST de manual, requiere varias peticiones:
GET /hora, para poner el reloj en hora;GET /configuracion, por si cambió;POST /lecturas, con lo medido;GET /comandos, por si hay órdenes pendientes;PUT /comandos/{id}/resultado, para informar lo ejecutado.
Cinco conexiones para un ciclo. La alternativa pragmática, que muchas plataformas IoT adoptan, es una ruta de sincronización (POST /api/v1/dispositivos/{id}/sync): el dispositivo envía en un solo cuerpo sus lecturas y los resultados de los comandos ejecutados, y el servidor responde con la hora, la versión de la configuración (o la configuración nueva) y los comandos pendientes. Una conexión hace el trabajo de cinco.
No hay que ver esto como una traición a REST. Es la aplicación de un principio de diseño más general: la interfaz se ajusta al costo de su cliente. Lo que se pierde es cierta pureza (la ruta ya no es un recurso, sino una operación) y facilidad para el uso de cachés intermedias; lo que se gana es una quinta parte de la energía y del tráfico. Un diseño razonable ofrece ambas: rutas por recurso, para las aplicaciones, personas y diagnósticos, y la ruta de sincronización para los dispositivos.
Dos reglas más para la ruta de sincronización: debe ser idempotente en conjunto (un reintento no duplica lecturas ni reejecuta comandos, gracias a los identificadores), y debe tener respuestas acotadas, con un máximo de comandos por respuesta y, si hay más, una indicación de que debe volver a sincronizar pronto.
08 · Analogía con programación
Simular una API y medir su costo en Python
El programa tiene dos partes. La primera es una API mínima en memoria: una ruta para enviar lecturas de un dispositivo, con tokens por dispositivo. Aplica, en orden, las comprobaciones de un servidor real: la ruta (404), la identidad (401), el permiso sobre ese dispositivo (403), el método (405), el formato (400), el rango del valor (422) y la repetición del mismo envío (200 en lugar de crear otra lectura). Se prueban ocho llamadas. La segunda parte calcula el costo de un ciclo con tres diseños: una ruta para cada cosa (5 peticiones), envío y consulta separados (2) y una sola ruta de sincronización (1), con 96 ciclos por día y una conexión nueva en cada petició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 re
# ---------- Parte 1: una API mínima para dispositivos, simulada en memoria ----------
TOKENS = {"tok-c07": "c07", "tok-c08": "c08"} # cada token pertenece a un solo dispositivo
DISPOSITIVOS = {"c07": {"lecturas": {}}, "c08": {"lecturas": {}}}
RUTA_LECTURAS = re.compile(r"^/api/v1/dispositivos/([\w-]+)/lecturas$")
def api(metodo, ruta, token, cuerpo=None):
"""Devuelve (código de estado, respuesta). Ilustra autenticación, autorización, validación e idempotencia."""
m = RUTA_LECTURAS.match(ruta)
if not m:
return 404, {"error": "ruta inexistente"}
id_dispositivo = m.group(1)
if token not in TOKENS:
return 401, {"error": "credencial inválida"} # no sabemos quién es
if TOKENS[token] != id_dispositivo:
return 403, {"error": "ese token no sirve para este dispositivo"} # sabemos quién es, pero no puede
if id_dispositivo not in DISPOSITIVOS:
return 404, {"error": "dispositivo desconocido"}
if metodo != "POST":
return 405, {"error": "método no permitido"}
if not isinstance(cuerpo, dict) or "t" not in cuerpo or "v" not in cuerpo:
return 400, {"error": "faltan campos t y v"}
if not -400 <= cuerpo["v"] <= 1250:
return 422, {"error": "valor fuera de rango"} # bien formado, pero sin sentido
lecturas = DISPOSITIVOS[id_dispositivo]["lecturas"]
if cuerpo["t"] in lecturas:
return 200, {"estado": "duplicado, ya estaba"} # reenviar no crea otra lectura
lecturas[cuerpo["t"]] = cuerpo["v"]
return 201, {"estado": "creada"}
pruebas = [
("POST", "/api/v1/dispositivos/c07/lecturas", "tok-c07", {"t": 1794061920, "v": 41}),
("POST", "/api/v1/dispositivos/c07/lecturas", "tok-c07", {"t": 1794061920, "v": 41}), # reintento
("POST", "/api/v1/dispositivos/c07/lecturas", "tok-falso", {"t": 1794061980, "v": 44}),
("POST", "/api/v1/dispositivos/c07/lecturas", "tok-c08", {"t": 1794061980, "v": 44}),
("POST", "/api/v1/dispositivos/c07/lecturas", "tok-c07", {"t": 1794061980}),
("POST", "/api/v1/dispositivos/c07/lecturas", "tok-c07", {"t": 1794061980, "v": 9999}),
("GET", "/api/v1/dispositivos/c07/lecturas", "tok-c07", None),
("POST", "/api/v2/dispositivos/c07/lecturas", "tok-c07", {"t": 1794061980, "v": 44}),
]
for metodo, ruta, token, cuerpo in pruebas:
codigo, resp = api(metodo, ruta, token, cuerpo)
print(f"{metodo:<5} {ruta[:36]:<36} {token:<9} -> {codigo} {resp}")
# ---------- Parte 2: cuánto cuesta un ciclo según cómo esté diseñada la API ----------
BATERIA_MAH = 2000
SUENO_MAH_DIA = 0.24
MAH_POR_PETICION = 100 * 0.6 / 3600 # conexión nueva: 0,6 s de radio a 100 mA
BYTES_POR_PETICION = 4700 + 450 # apretón de manos más petición y respuesta
DISENOS = {
"una ruta para cada cosa": 5, # hora, configuración, lecturas, comandos, resultado
"envío y consulta separados": 2, # POST de lecturas y GET de configuración y comandos
"una sola ruta de sincronización": 1,
}
print()
print("diseño peticiones por ciclo mAh/día MB/mes batería")
for ciclos in (96,):
for nombre, peticiones in DISENOS.items():
mah = SUENO_MAH_DIA + ciclos * peticiones * MAH_POR_PETICION
mb = ciclos * peticiones * BYTES_POR_PETICION * 30 / 1e6
dias = BATERIA_MAH / mah
texto = f"{dias / 365:.1f} años" if dias >= 365 else f"{dias:.0f} días"
print(f"{nombre:<31} {peticiones:>10} {mah:>16.2f} {mb:>8.1f} {texto}")
Resultado con los valores iniciales. Las ocho llamadas devuelven: 201 (lectura creada), 200 (el mismo envío repetido, «duplicado, ya estaba»), 401 (token falso), 403 (el token de c08 intentando escribir en c07), 400 (falta el campo v), 422 (valor 9999 fuera de rango), 405 (un GET en una ruta que solo acepta POST) y 404 (la versión v2, que no existe). En cuanto al costo de un ciclo con 96 ciclos por día: una ruta para cada cosa usa 5 peticiones por ciclo, 8,24 mAh por día, 74,2 MB por mes y dura 243 días; envío y consulta separados, 2 peticiones, 3,44 mAh, 29,7 MB y 1,6 años; una sola ruta de sincronización, 1 petición, 1,84 mAh, 14,8 MB y 3,0 años.
Dos hallazgos. Uno: cada código de estado le dice al dispositivo qué hacer, y por eso la API distingue 401 de 403 y 400 de 422. Otro: el diseño de la API pesa más en la batería que muchas decisiones de firmware: con los mismos datos y el mismo equipo, pasar de cinco peticiones por ciclo a una multiplica la duración de la batería por 4,5.
09 · Exploración interactiva
¿Cuántas peticiones por ciclo?, de extremo a extremo
Este laboratorio ejecuta el mismo modelo de costo del código anterior. Elegí el diseño de la API, cuántos ciclos de trabajo hace el dispositivo por día y la autonomía que se pretende. Las barras comparan la duración de la batería de los tres diseños con los mismos ciclos, en escala logarítmica; el elegido va resaltado, y la línea naranja marca el objetivo. Los costos de una petición (0,6 s de radio, 5 KB de tráfico) son valores didácticos.
Costo de un diseño de API
Elegí los parámetros y presioná «Calcular».
Recorrido guiado
- Con los valores iniciales (una sola ruta de sincronización, 96 ciclos por día, objetivo de 2 años), calculá. 96 peticiones por día, 14,8 MB al mes, 1,84 mAh por día y 3,0 años: «Cumple».
- Cambiá a Envío y consulta separados: 192 peticiones por día, 29,7 MB y 3,44 mAh. La batería baja a 1,6 años: «No alcanza». Una sola petición extra por ciclo cuesta casi la mitad de la autonomía.
- Pasá a Una ruta para cada cosa: 480 peticiones por día, 74,2 MB y 8,24 mAh. Solo 243 días: la API «de manual» consume 4,5 veces más energía que la de sincronización.
- Con la ruta de sincronización, subí a 288 ciclos (cada 5 minutos): 5,04 mAh y 1,1 años. Incluso el diseño más eficiente no cumple los 2 años con ese ritmo: hay que bajar los ciclos o aceptar otro objetivo.
- Con 24 ciclos por día (cada hora), hasta el diseño más parlanchino rinde 2,4 años: con pocos ciclos el diseño importa menos.
- Subí el objetivo a 3 años con 96 ciclos: la ruta de sincronización queda en 2,98 años, a un paso de cumplir (el resultado dice «No alcanza»), y los otros diseños ni se acercan. El diseño de la API decide si el producto cumple su requisito; aquí hace falta además bajar un poco los ciclos.
Desafío: el costo en una flota
Con 96 ciclos diarios, el diseño de una ruta por cosa mueve 74,2 MB por mes y por equipo; el de sincronización, 14,8 MB. En una flota de 10.000 equipos son 742 GB contra 148 GB al mes: 594 GB de diferencia. Si cada GB de datos cuesta US$ 0,10 en el servidor (ancho de banda, balanceador y registros), son unos US$ 59 al mes solo en tráfico, que no es mucho; pero las peticiones son otro costo: 4,8 millones por día contra 960.000, lo que cambia la cantidad de servidores y los costos de las plataformas que cobran por petición. A US$ 1 por millón de peticiones, 4,8 millones diarios por 30 días son US$ 144 al mes contra US$ 29. Un diseño más eficiente para el dispositivo también es más económico para el servicio.
10 · Método de trabajo
Cómo diseñar y documentar la API
- Listá los recursos (dispositivo, lectura, configuración, comando) y sus relaciones; armá el árbol de rutas con dos o tres niveles.
- Asigná los verbos y los códigos de éxito y de error de cada operación, distinguiendo 400 de 422 y 401 de 403.
- Hacé idempotente todo lo que el dispositivo pueda reenviar, con identificadores, y responde éxito a los duplicados.
- Autenticá por dispositivo y autorizá cada ruta contra la identidad del token; prevé la revocación y la rotación.
- Versioná desde el primer día (
/v1) y fijá la política de compatibilidad: agregar es libre, cambiar exige una versión nueva. - Paginá y limitá: topes de elementos por página y cuotas de uso con 429 y
Retry-After. - Agregá la ruta de sincronización para el dispositivo y calculá cuánto ahorra por ciclo.
- Documentá con un formato estándar (OpenAPI): rutas, esquemas de los cuerpos, códigos y ejemplos, de modo que el equipo de firmware y el de servidor trabajen sobre el mismo contrato.
- Probá el contrato: un conjunto de pruebas que ejecute cada ruta con casos buenos y malos (como los ocho del código) contra el servidor real.
Un hábito útil: escribí primero los ejemplos de petición y respuesta, antes de programar nada. Si cuesta escribir un buen ejemplo, el diseño tiene un problema.
11 · Comprensión
Confusiones frecuentes
«REST es un protocolo»
Es un estilo de diseño sobre HTTP, con convenciones (recursos, verbos, códigos) pero sin reglas obligatorias. Por eso hay APIs «muy REST» y otras que lo son a medias; lo que importa es que sean coherentes y previsibles.
«Con una clave de API para toda la flota alcanza»
Si la clave se filtra (y una clave grabada en miles de dispositivos termina filtrándose), no hay forma de revocar un equipo sin afectar a todos. Cada dispositivo necesita su propia credencial, y cada credencial vale solo para sus propias rutas.
«401 y 403 son lo mismo: acceso denegado»
El 401 significa que no se pudo identificar al que llama; el 403, que se lo identificó pero no tiene permiso. El dispositivo reacciona distinto: con un 401 renueva su credencial; con un 403 reporta, porque cambiar el token no lo arreglaría.
«Un reintento duplicado debe responder con error»
No: la lectura ya está en el servidor, y de cara al dispositivo la operación se completó. Si responde error, el firmware seguirá reintentando. Debe responder con éxito y, de ser útil, informar que era un duplicado.
«Una API REST pura es siempre lo mejor»
Es excelente para aplicaciones, pero para un dispositivo con batería, cinco peticiones por ciclo cuestan 4,5 veces más energía que una. Una ruta de sincronización, aunque sea menos «pura», es la elección correcta.
«Puedo cambiar el nombre de un campo y actualizar los dispositivos después»
No todos se actualizan, ni a la vez. Un cambio incompatible en la misma versión rompe a los equipos viejos. Se agrega el campo nuevo y se mantiene el viejo hasta retirar la versión.
12 · Práctica guiada
Predecí antes de comprobar
1. Nombrar las rutas
Proponé método y ruta para: (a) enviar una lectura del dispositivo c07; (b) leer la configuración de c07; (c) cancelar un comando 12 del dispositivo c07.
Ver solución
(a) POST /api/v1/dispositivos/c07/lecturas. (b) GET /api/v1/dispositivos/c07/configuracion. (c) DELETE /api/v1/dispositivos/c07/comandos/12 (o PATCH cambiando su estado a cancelado). Sustantivos en la ruta y la acción en el verbo.
2. Elegir el código
Indicá el código que devolvería la API en cada caso: (a) la lectura se creó; (b) el mismo envío por segunda vez; (c) el token pertenece a otro dispositivo; (d) falta la cabecera de autorización; (e) la temperatura es 9999 °C.
Ver solución
(a) 201. (b) 200 (duplicado, es éxito). (c) 403. (d) 401. (e) 422 (bien formado, pero sin sentido).
3. Peticiones por día
Un dispositivo hace 96 ciclos diarios. ¿Cuántas peticiones por día genera con cinco peticiones por ciclo y con una?
Ver solución
Con cinco: 480 peticiones por día. Con una: 96. La diferencia, 384 peticiones por equipo y por día, es de lo que se ahorra la batería: en el modelo, 6,4 mAh por día.
4. Seguridad por ruta
Un atacante extrae el token del dispositivo c08 y lo usa contra /api/v1/dispositivos/c07/lecturas. ¿Qué debe ocurrir y qué falla si el servidor solo verifica que el token sea válido?
Ver solución
El servidor debe comparar la identidad del token (c08) con el identificador de la ruta (c07) y responder 403. Si solo verifica que el token exista, el atacante puede escribir (y leer, si hay GET) los datos de cualquier dispositivo: un equipo comprometido compromete a toda la flota.
5. Paginación
Un dispositivo pide las lecturas históricas de una semana (10.080 lecturas) y la respuesta con todas pesaría unos 250 KB (con cada lectura como objeto). ¿Cómo se diseña la respuesta para que el microcontrolador la pueda procesar?
Ver solución
Con paginación: un tope por página (por ejemplo, 20 lecturas: unos 0,5 KB de texto y cerca de 1 KB de memoria al analizar) y un modo de pedir la siguiente (?limite=20&desde=…). El servidor impone el máximo aunque el cliente pida más. Para un dispositivo, además, casi nunca hace falta el histórico: lo normal es que solo envíe.
6. Retirar una versión
La versión 1 de la API tiene 3000 dispositivos conectados, de los cuales 400 con firmware viejo que no pueden hablar la versión 2. ¿Cuándo se apaga la versión 1?
Ver solución
No hasta que esos 400 se hayan actualizado de forma remota (o reemplazado, o se decida dejarlos sin servicio con aviso). Se mide cuántos quedan (por la ruta y el User-Agent), se comunica un plazo y se actualiza antes del apagado. Mantener la versión 1 operando mientras haya equipos en uso es parte del contrato con la flota.
13 · Síntesis
Ideas para recordar
- Una API es el contrato entre el firmware y el servidor; REST es un estilo sobre HTTP que organiza la interfaz alrededor de recursos y verbos.
- Las rutas son sustantivos jerárquicos y poco profundos (
/dispositivos/{id}/lecturas); la acción la pone el verbo. - Los códigos de estado se usan con precisión: 201 para crear, 200 para un duplicado, 401 contra 403 y 400 contra 422, para que el dispositivo sepa qué hacer.
- Todo lo que el dispositivo pueda reenviar debe ser idempotente, y un duplicado responde con éxito.
- Cada dispositivo tiene su propia credencial, revocable, y esa credencial solo vale para sus propias rutas: autenticación y autorización se verifican en el servidor en cada petición.
- La versión va en la ruta, agregar es seguro y cambiar exige una versión nueva; los clientes viejos se miden y se actualizan antes de retirar una versión.
- Las colecciones se paginan con un tope, y los límites de uso responden 429 con
Retry-After. - Un diseño amable con el dispositivo ofrece una ruta de sincronización que agrupa el envío y la consulta: pasar de cinco peticiones por ciclo a una multiplica la duración de la batería por 4,5.
- La API se documenta con un estándar (OpenAPI) y se prueba con casos buenos y malos contra el servidor real.
¿Listo para el tema 23?
Antes de avanzar, escribí la tabla de rutas de tu sistema (método, ruta, éxito, errores) y, para cada operación, qué hace el dispositivo con cada código posible. Contá las peticiones por ciclo y decidí si una ruta de sincronización las reduce. Si podés probar tu tabla con los casos buenos y malos del ejemplo de este tema, el contrato está listo.
En el próximo tema damos vuelta la perspectiva: el servidor web embebido, donde el propio dispositivo atiende peticiones HTTP, por ejemplo para su configuración local o su panel de estado.