01 · Punto de partida
Darle la vuelta a la conversación
En los temas 19 a 22 el dispositivo habló con un servidor de Internet. Ahora invertimos los papeles: el dispositivo es el servidor y quien lo consulta (el celular, una computadora) es el cliente. Todo lo que aprendiste sobre HTTP vale igual, solo que desde el otro lado: recibir el pedido, interpretarlo, decidir qué responder y devolverlo.
Esto resuelve un problema práctico que dejamos pendiente desde el tema 14: ¿cómo se configura un equipo que no tiene pantalla ni teclado? Con un servidor web. El usuario se conecta desde el celular a una red temporal que crea el propio dispositivo, abre una página en el navegador, escribe la red de su casa y su clave, y listo. No hay que instalar ninguna aplicación ni escribir ningún comando.
Los fragmentos de C++ siguen el estilo Arduino para ESP32. Los números del laboratorio son un modelo con valores didácticos, no mediciones de un chip concreto.
02 · Decisión
Cuándo tiene sentido y cuándo no
| Uso | Cómo funciona | Condición |
|---|---|---|
| Configuración inicial | El dispositivo crea una red Wi-Fi temporal; el usuario carga la red de su casa y otros parámetros desde el navegador | Activo solo hasta terminar la configuración, con un tiempo límite |
| Panel de estado local | El usuario abre una página del dispositivo desde la red de la casa y ve temperatura, señal, batería, versión | Dispositivo con alimentación eléctrica, en la red local |
| API local | Otro equipo de la red local (un concentrador domótico, un script) consulta GET /estado o envía comandos | Red local de confianza, con autenticación |
| Diagnóstico y servicio técnico | Se activa con un botón para ver registros y contadores, o para actualizar el firmware | Temporal, con credencial |
| Actualización del firmware desde el navegador | Una página permite subir un archivo de firmware | Solo con autenticación y verificación de firma (tema 43) |
Y hay dos situaciones en las que un servidor embebido no es la respuesta:
- Acceso desde Internet. Como vimos en el tema 17, un dispositivo detrás de un NAT no recibe conexiones del exterior. La solución nunca es abrir un puerto en el router del cliente (inseguro y fuera del alcance de casi todos los usuarios), sino que el dispositivo abra una conexión saliente con un servidor (MQTT, WebSocket) por la que lleguen las órdenes. El servidor embebido sirve para la red local.
- Dispositivos con batería. Atender pedidos exige tener la radio encendida y atenta todo el tiempo: del orden de 20 mA continuos (tema 14), unos 480 mAh por día. Con pila, el servidor es temporal (se activa con un botón durante unos minutos) o no existe.
03 · Mecánica
Cómo funciona un servidor embebido
El ciclo de un servidor es siempre el mismo: esperar una conexión, leer el pedido, buscar la ruta, ejecutar la función asociada y escribir la respuesta. Las bibliotecas de los microcontroladores esconden la mecánica de sockets y presentan una interfaz de rutas: «cuando llegue un GET a /estado, ejecutá esta función».
#include <WiFi.h>
#include <WebServer.h>
WebServer servidor(80); // escucha en el puerto 80
void paginaInicio() {
servidor.send(200, "text/html", "<h1>Sensor c07</h1><p><a href='/estado'>Ver estado</a></p>");
}
void apiEstado() { // GET /estado → JSON chico
char json[96];
snprintf(json, sizeof(json), "{\"id\":\"c07\",\"t\":%d,\"b\":%d,\"rssi\":%d}",
(int)(temperatura * 10), bateriaPct, WiFi.RSSI());
servidor.send(200, "application/json", json);
}
void configurar() { // POST /configurar (formulario)
if (!servidor.hasArg("periodo_s")) { servidor.send(400, "text/plain", "Falta periodo_s"); return; }
long periodo = servidor.arg("periodo_s").toInt();
if (periodo < 60 || periodo > 86400) { // validar antes de aplicar
servidor.send(422, "text/plain", "periodo_s fuera de rango");
return;
}
guardarPeriodo(periodo); // recién acá se cambia la configuración
servidor.send(200, "text/plain", "Guardado");
}
void setup() {
// ... conectar o crear la red Wi-Fi ...
servidor.on("/", HTTP_GET, paginaInicio);
servidor.on("/estado", HTTP_GET, apiEstado);
servidor.on("/configurar", HTTP_POST, configurar);
servidor.onNotFound([]() { servidor.send(404, "text/plain", "No encontrado"); });
servidor.begin();
}
void loop() {
servidor.handleClient(); // atiende los pedidos pendientes
// ... medir, dormir un poco, otras tareas (sin esperas largas) ...
}
Observá la estructura: se registran las rutas una sola vez en setup y en loop se llama a handleClient para atender lo que llegue. Esa llamada es síncrona: si la función de una ruta tarda (porque hace una medición lenta o espera una respuesta), el servidor y el resto del programa quedan detenidos, y los demás clientes esperan. Por eso las funciones de las rutas deben ser breves: devolver el último valor medido, no medirlo ahí. Existen servidores asíncronos, que manejan las conexiones con eventos y no bloquean el programa; consumen algo más de memoria y son más complejos, pero son la opción cuando el dispositivo hace otras cosas a la vez.
04 · Contenido
Páginas livianas
Una página web pensada para una computadora pesa cientos de kilobytes entre imágenes, tipografías y bibliotecas de JavaScript. Un microcontrolador tiene unos cientos de kilobytes de RAM y entrega unos 100 a 200 KB por segundo. La conclusión es que las páginas del dispositivo se diseñan para ser mínimas.
- HTML y CSS simples, unos pocos kilobytes, con formularios comunes. Un formulario HTML funciona sin una línea de JavaScript.
- Todo local, sin recursos externos. En modo de punto de acceso no hay Internet: una página que carga una tipografía o una biblioteca desde un CDN se ve rota. Todo lo que necesita está dentro del dispositivo.
- Guardadas en la flash, no armadas en la RAM: como constantes del programa (
PROGMEM) o como archivos en un sistema de archivos como LittleFS. Se envían desde la flash en trozos pequeños, sin cargarlas enteras. - Comprimidas con gzip de antemano. Los archivos de texto se reducen a un tercio o menos; el servidor los envía tal cual con la cabecera
Content-Encoding: gzipy el navegador los descomprime. Ahorra flash y tiempo de transferencia. - JavaScript mínimo, solo si hace falta, para refrescar un valor con
fetch('/estado')cada pocos segundos. Evitar los frameworks: una aplicación con biblioteca puede pesar 350 KB, que para el dispositivo son segundos de transferencia. - Imágenes evitadas o reducidas a íconos pequeños en formato vectorial (SVG), que se comprimen bien.
Una arquitectura que funciona bien es separar la página, que casi no cambia, de los datos, que cambian siempre: la página estática se sirve desde la flash una vez, y su JavaScript pide cada tanto el pequeño JSON de /estado. Así el tráfico repetido es de unas decenas de bytes y no de la página entera.
05 · Recursos
Memoria, clientes y energía
Cada cliente conectado consume memoria: buffers de red y estructuras de la conexión que, en un microcontrolador, rondan los diez a veinte kilobytes por conexión (el modelo de este tema usa 12 KB). Y la pila de red tiene además un número máximo de conexiones simultáneas, del orden de unas pocas decenas en el mejor caso y muchas menos en la práctica. Un navegador moderno, por si fuera poco, abre varias conexiones en paralelo para cargar una sola página. Tres reglas para no quedarse sin memoria:
- No armar la respuesta completa en memoria. Concatenar una página grande en un
Stringpara enviarla fragmenta el montón y puede agotar la RAM; se envía en trozos desde la flash. - Limitar los clientes simultáneos (por ejemplo, a 2 o 4) y rechazar con
503a los que sobren. Un dispositivo de configuración atiende a una persona por vez. - Limitar el tamaño del cuerpo que se acepta en los POST (unos cientos de bytes para un formulario de configuración). Sin límite, un cuerpo enorme agota la memoria.
En energía, el costo es el que conocés: un servidor siempre atento mantiene la radio encendida (decenas de mA continuos). Si el dispositivo tiene batería, el servidor se enciende por una acción deliberada (un botón, el primer arranque) y se apaga solo después de unos minutos sin actividad. Y como el nombre de una dirección IP asignada por DHCP no se recuerda, conviene anunciar el dispositivo en la red local con mDNS, de modo que se pueda abrir http://sensor-c07.local en lugar de buscar su número en el router.
06 · Defensa
Seguridad
Un servidor en un dispositivo es una superficie de ataque que antes no existía. Y como el firmware es pequeño y no se suele actualizar, las vulnerabilidades quedan para siempre. Las defensas esenciales:
- Autenticación para todo lo que cambie algo o muestre datos privados. Una contraseña única por dispositivo (impresa en la etiqueta o generada en fábrica), nunca la misma para todos ni una por defecto como «admin». En redes de confianza limitada, ninguna acción de configuración sin credencial.
- El cifrado tiene límites. HTTP sobre una red local deja las credenciales a la vista de quien escuche esa red. HTTPS en un microcontrolador es posible, pero costoso (memoria, tiempo de cálculo) y, con un certificado autofirmado, el navegador muestra advertencias. Por eso estos servidores se usan en redes de confianza (la red temporal de configuración protegida con clave, la red doméstica), y nunca expuestos a Internet.
- Validar todo lo que entra. Tipos, rangos y largos de cada campo de un formulario, y rechazar con 400 o 422 lo que no cumple (como en el ejemplo). Los cambios se aplican completos o no se aplican.
- Escapar todo lo que sale a una página. Si la página muestra el nombre de una red Wi-Fi que escribió un tercero, y ese nombre es
<script>…</script>, el navegador lo ejecutaría (ataque de XSS). Los caracteres< > & " 'se reemplazan por sus entidades HTML antes de mostrar un texto externo. - Proteger las acciones contra pedidos falsos. Una página maliciosa abierta en el mismo navegador podría hacer que el celular envíe un POST al dispositivo sin que el usuario lo note (CSRF). Se mitiga exigiendo la credencial en cada acción y, si el servidor lo permite, un valor secreto en el formulario.
- No mostrar secretos. Ni la clave del Wi-Fi guardada, ni los tokens, ni la clave del servidor aparecen en las páginas de estado. Se informan como «configurado».
- Desactivar lo que no se usa: si el servidor ya cumplió su tarea (la configuración terminó), se apaga. Una puerta cerrada no se ataca.
- Actualización por la web solo con firma verificada: una página que permite subir firmware sin comprobarlo es la forma más directa de tomar el control del equipo.
07 · Analogía con programación
Simular el servidor y medir su capacidad en Python
El programa tiene dos partes. La primera es la función atender, que reproduce las rutas del servidor del ejemplo: la página de inicio, el JSON de estado y el formulario de configuración. Decodifica el cuerpo del formulario (application/x-www-form-urlencoded), limita su tamaño (413), valida el nombre de la red, la clave y el período, y solo cuando todo es correcto modifica la configuración. Se prueban nueve pedidos. La segunda parte calcula, para tres páginas (mínima, con imágenes y con biblioteca JavaScript), cuántos kilobytes se envían, cuánta memoria se necesita y cuánto tarda la carga con 4 clientes a la vez, con y sin gzip y sirviendo desde la RAM o desde la flash.
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
from urllib.parse import parse_qs
# ---------- Parte 1: las rutas de un servidor web embebido, simuladas ----------
ESTADO = {"id": "c07", "temperatura_c": 4.3, "bateria_pct": 87, "rssi_dbm": -61}
CONFIG = {"ssid": "", "periodo_s": 900}
def atender(metodo, ruta, cuerpo=""):
"""Devuelve (código, tipo de contenido, respuesta)."""
if metodo == "GET" and ruta == "/":
return 200, "text/html", "<h1>Sensor c07</h1><p>Temperatura: 4.3 °C</p>"
if metodo == "GET" and ruta == "/estado":
return 200, "application/json", json.dumps(ESTADO, separators=(",", ":"))
if ruta == "/configurar":
if metodo != "POST":
return 405, "text/plain", "Usar POST"
if len(cuerpo) > 256:
return 413, "text/plain", "Cuerpo demasiado grande" # límite para no agotar la memoria
campos = {k: v[0] for k, v in parse_qs(cuerpo, keep_blank_values=True).items()}
ssid, clave = campos.get("ssid", ""), campos.get("clave", "")
try:
periodo = int(campos.get("periodo_s", ""))
except ValueError:
return 400, "text/plain", "periodo_s debe ser un número entero"
if not 1 <= len(ssid) <= 32:
return 422, "text/plain", "El nombre de la red debe tener entre 1 y 32 caracteres"
if clave and not 8 <= len(clave) <= 63:
return 422, "text/plain", "La clave debe tener entre 8 y 63 caracteres"
if not 60 <= periodo <= 86400:
return 422, "text/plain", "periodo_s debe estar entre 60 y 86400"
CONFIG.update({"ssid": ssid, "periodo_s": periodo}) # recién acá se aplican los cambios
return 200, "text/plain", "Guardado. Reiniciando para aplicar..."
return 404, "text/plain", "No encontrado"
pruebas = [
("GET", "/", ""),
("GET", "/estado", ""),
("POST", "/configurar", "ssid=Casa+P%C3%A9rez&clave=claveSegura1&periodo_s=900"),
("POST", "/configurar", "ssid=Casa&clave=123&periodo_s=900"),
("POST", "/configurar", "ssid=Casa&clave=claveSegura1&periodo_s=5"),
("POST", "/configurar", "ssid=Casa&clave=claveSegura1&periodo_s=abc"),
("POST", "/configurar", "ssid=" + "x" * 300),
("GET", "/configurar", ""),
("GET", "/admin", ""),
]
for metodo, ruta, cuerpo in pruebas:
codigo, tipo, resp = atender(metodo, ruta, cuerpo)
print(f"{metodo:<5} {ruta:<12} -> {codigo} {resp[:60]}")
print("Configuración final:", CONFIG)
# ---------- Parte 2: cuántos clientes aguanta, según la memoria y el tamaño de la página ----------
KB_POR_CONEXION = 12 # buffers de red y estructuras de cada conexión abierta
KB_POR_TROZO = 2 # buffer para enviar el archivo desde la flash en trozos
KB_POR_SEGUNDO = 150 # velocidad total de servicio (se reparte entre los clientes)
PAGINAS = {"mínima (HTML y CSS)": (6, 0.35), "con imágenes": (120, 0.9), "aplicación con biblioteca JS": (350, 0.3)}
def capacidad(pagina, gzip, desde_ram, clientes):
kb, compresion = PAGINAS[pagina]
enviado = kb * (compresion if gzip else 1)
memoria = clientes * (KB_POR_CONEXION + (kb if desde_ram else KB_POR_TROZO))
segundos = enviado * clientes / KB_POR_SEGUNDO
return enviado, memoria, segundos
print()
print("página gzip origen clientes enviado memoria tiempo de carga")
for pagina in PAGINAS:
for gzip in (False, True):
for desde_ram in (True, False):
enviado, mem, seg = capacidad(pagina, gzip, desde_ram, 4)
print(f"{pagina:<29} {'sí' if gzip else 'no':<4} {'RAM' if desde_ram else 'flash':<6} {4:>8} {enviado:>6.0f} KB {mem:>7.0f} KB {seg:>8.1f} s")
Resultado con los valores iniciales. De los nueve pedidos: la página de inicio y el estado responden 200; el formulario válido (ssid=Casa+P%C3%A9rez…, con la «é» codificada) responde 200 y cambia la configuración a «Casa Pérez» y 900 s; una clave de 3 caracteres devuelve 422; un período de 5 s, 422; un período «abc», 400; un cuerpo de 300 caracteres, 413; un GET a /configurar, 405; y /admin, 404. Ninguno de los rechazos cambió la configuración. En capacidad, con 4 clientes: la página mínima necesita 56 KB de RAM si se sirve desde la flash (72 KB si se carga en RAM) y carga en 0,2 s; la página con imágenes carga en 3,2 s (2,9 con gzip) y desde RAM pediría 528 KB; la aplicación con biblioteca JavaScript tarda 9,3 s sin comprimir y 2,8 s con gzip, y desde RAM exigiría 1448 KB.
Tres lecciones. Una: la validación va antes del cambio: ninguno de los cinco pedidos inválidos modificó nada. Otra: la memoria depende más de cómo se sirve que de qué se sirve: la misma aplicación necesita 1448 KB desde la RAM y 56 KB desde la flash en trozos. Y la tercera: gzip es la mejora más barata: la aplicación tarda 3,3 veces menos y ocupa 3,3 veces menos flash.
08 · Exploración interactiva
¿Aguanta el servidor?, de extremo a extremo
Este laboratorio ejecuta el mismo modelo de capacidad del código anterior. Elegí qué página se sirve, si está comprimida, si se carga en RAM o se envía desde la flash, cuántos clientes piden a la vez y cuánta RAM libre hay. Las barras muestran la memoria necesaria para las tres páginas, en escala logarítmica, con el tiempo de carga al lado; son rojas si superan la RAM libre, que marca la línea naranja. Todos los valores son del modelo.
Capacidad de un servidor web embebido
Elegí los parámetros y presioná «Calcular».
Recorrido guiado
- Con los valores iniciales (aplicación de 350 KB, sin gzip, desde la flash, 4 clientes, 120 KB de RAM), calculá. 56 KB de memoria, pero 9,3 s de carga: «Lento». La memoria alcanza, el ancho de banda no.
- Activá gzip: se envían 105 KB por cliente y la carga baja a 2,8 s: «Ágil». Es la mejora más barata.
- Cambiá a Cargada entera en RAM: la memoria sube a 1448 KB, doce veces la RAM libre: «Se queda sin memoria». Cargar la página en RAM para cada cliente no escala.
- Elegí la página mínima (6 KB): 0,2 s y 56 KB sin gzip. Con una página chica, hasta cargada en RAM cabe con 4 clientes (72 KB, sí en 120 KB).
- Elegí la página con imágenes sin gzip: 3,2 s, «Lento»; con gzip, 2,9 s, casi igual, porque las imágenes ya están comprimidas. La compresión de texto no ayuda con las imágenes: hay que quitarlas o achicarlas.
- Con la aplicación de 350 KB y gzip desde la flash, bajá la RAM a 60 KB y subí a 8 clientes: 112 KB de memoria y 5,6 s, «Se queda sin memoria». Cada conexión cuesta 12 KB aunque la página no esté en RAM: limitar clientes es obligatorio.
Desafío: el costo de tener el servidor encendido
El dispositivo funciona con una batería de 2000 mAh y quiere un servidor web para consultar su estado desde el celular. Si lo deja siempre encendido, la radio atenta consume unos 20 mA continuos: 480 mAh por día, apenas 4 días de batería. Si lo enciende solo cuando alguien presiona un botón y lo apaga tras 5 minutos de inactividad, y esto ocurre una vez por día, el costo es 100 mA × 300 s / 3600 ≈ 8,3 mAh por día (con la radio plenamente activa), 58 veces menos que los 480 mAh de tenerlo siempre encendido. La batería pasa de 4 días a unos 8 meses (2000 / 8,5 ≈ 234 días), y aun así ese servidor sería la parte más cara del consumo de un dispositivo que duerme casi todo el día. La regla: con batería, el servidor es una función de servicio, no una función permanente. Si el estado se necesita seguido, el dispositivo debe enviarlo a la nube (temas anteriores) y que sea el celular quien lo consulte allí.
09 · Método de trabajo
Lista de verificación del servidor embebido
- Justificá el servidor: ¿configuración, estado local, API local o diagnóstico? Si el acceso debe ser desde Internet, es otro diseño.
- Definí el ciclo de vida: cuándo se enciende, cuándo se apaga solo, y cómo se vuelve a activar.
- Páginas mínimas, locales, comprimidas y guardadas en la flash; JavaScript solo si es imprescindible.
- Rutas breves: las funciones devuelven datos ya medidos y no bloquean el programa.
- Límites: clientes simultáneos, tamaño de cuerpo, tiempo máximo por pedido.
- Autenticación con una credencial única por dispositivo, y sin secretos visibles.
- Validación de entrada y escape de salida en cada campo; cambios completos o ninguno.
- Nada hacia Internet: sin puertos abiertos en el router; para acceso remoto, conexión saliente.
- mDNS para encontrar el dispositivo por nombre en la red local.
- Pruebas: con varios navegadores y celulares, con clientes lentos, con cuerpos enormes, con campos inválidos y con textos hostiles (
<script>, comillas, caracteres largos).
Un ensayo revelador: abrí la página del dispositivo en tres pestañas a la vez y recargá varias veces seguidas. Si el equipo se reinicia o se vuelve lento, sus límites de memoria y de clientes todavía no están bien puestos.
10 · Comprensión
Confusiones frecuentes
«Con un servidor embebido puedo controlar el dispositivo desde cualquier lugar»
Solo desde la red local. Desde Internet no se llega a un dispositivo detrás de un NAT, y abrir puertos en el router es inseguro. El acceso remoto se resuelve con una conexión saliente hacia un servicio en la nube.
«Una página con una biblioteca JavaScript popular es más cómoda de escribir»
Lo es, pero pesa cientos de kilobytes: en el dispositivo son varios segundos de carga y mucha flash. Un formulario HTML y unas líneas de JavaScript hacen lo mismo con una centésima parte.
«Si la página muestra solo datos, no hace falta validar ni escapar»
Los datos pueden venir de afuera (el nombre de una red Wi-Fi, un texto de configuración) y contener código. Sin escapar, el navegador lo ejecuta (XSS). Toda entrada se valida y toda salida se escapa.
«Un servidor siempre encendido no gasta tanto»
Mantener la radio atenta cuesta decenas de miliamperes continuos, cientos de mAh por día. Con batería, es inviable; solo con alimentación eléctrica.
«Con una contraseña de fábrica igual para todos, el usuario ya la cambiará»
Casi nadie la cambia, y esa clave termina en listas públicas. La causa de grandes incidentes de IoT fueron miles de dispositivos con credenciales por defecto. Cada equipo sale con la suya.
«HTTPS en el dispositivo resuelve la seguridad»
Cifra el tráfico, pero no valida la entrada, no impide los pedidos falsos ni corrige una contraseña débil. Además tiene costos de memoria y los certificados autofirmados generan advertencias. La seguridad es un conjunto de medidas, no una sola.
11 · Práctica guiada
Predecí antes de comprobar
1. ¿Servidor o no?
Indicá si conviene un servidor web embebido: (a) configurar la red Wi-Fi de un sensor nuevo; (b) ver desde la oficina el estado de un sensor instalado en otra ciudad; (c) mostrar en la red de la casa el estado de un termostato enchufado; (d) un sensor a pila que debe mostrar su estado en cualquier momento.
Ver solución
(a) Sí: modo punto de acceso temporal. (b) No: acceso por Internet; el dispositivo envía su estado a un servicio que se consulta desde la oficina. (c) Sí: red local y alimentación eléctrica. (d) No, o solo temporal (botón): tenerlo siempre encendido agota la pila en días.
2. Memoria y clientes
Se carga en RAM una página de 120 KB para cada uno de 4 clientes, con 12 KB por conexión. ¿Cuánta memoria necesita? ¿Y enviándola desde la flash en trozos de 2 KB?
Ver solución
Desde RAM: 4 × (12 + 120) = 528 KB. Desde la flash: 4 × (12 + 2) = 56 KB. Casi diez veces menos memoria con la misma página.
3. Compresión
La aplicación de 350 KB se comprime a 105 KB con gzip. ¿Cuánto tarda en cargar para un cliente a 150 KB/s, con y sin compresión?
Ver solución
Sin compresión: 350 / 150 ≈ 2,3 s. Con gzip: 105 / 150 = 0,7 s. Con 4 clientes a la vez, el ancho de banda se reparte: 9,3 s y 2,8 s.
4. XSS
La página muestra el nombre de la red Wi-Fi guardado. Un atacante configuró el nombre <script>alert(1)</script>. ¿Qué pasa y cómo se evita?
Ver solución
Si el nombre se inserta tal cual en el HTML, el navegador lo ejecuta como código (XSS) cada vez que alguien abre la página. Se evita escapando los caracteres especiales (< como <, > como >, & como &, comillas) antes de mostrar un texto externo, y validando el largo y los caracteres del nombre al guardarlo.
5. Validar antes de aplicar
El formulario envía periodo_s=5, pero el firmware exige entre 60 y 86400. ¿Qué código devuelve y qué ocurre con la configuración?
Ver solución
Devuelve 422 (bien formado, pero fuera de rango) con un mensaje claro, y no modifica nada: la configuración anterior sigue vigente. Si el valor no fuera un número (abc), sería 400.
6. Por qué no abrir un puerto
Un cliente quiere ver su sensor desde el trabajo y le piden que abra el puerto 80 en su router hacia el dispositivo. Dá tres razones para no hacerlo y la alternativa.
Ver solución
(1) El firmware queda expuesto a escaneos y ataques permanentes de todo Internet, sin actualizaciones de seguridad frecuentes. (2) HTTP sin cifrar deja a la vista las credenciales. (3) Casi nadie sabe configurar un router, y una IP pública cambia. La alternativa: el dispositivo envía su estado a un servicio en la nube mediante una conexión saliente autenticada, y el cliente lo consulta desde allí.
12 · Síntesis
Ideas para recordar
- El dispositivo también puede ser servidor HTTP: recibe el pedido, ejecuta la función de la ruta y responde.
- Sirve para configurar un equipo sin pantalla (modo punto de acceso temporal), para un panel de estado y una API local, y para diagnóstico; no sirve para acceso desde Internet.
- Las funciones de las rutas son breves y no bloquean el programa; los servidores asíncronos son la opción cuando el dispositivo hace otras tareas.
- Las páginas del dispositivo son mínimas, locales, guardadas en la flash y comprimidas con gzip; los datos cambiantes se piden aparte en un JSON chico.
- Cada conexión cuesta unos 12 KB de RAM: se limitan los clientes simultáneos y se sirve desde la flash en trozos, no cargando la página en memoria.
- Un servidor siempre encendido mantiene la radio atenta (cientos de mAh por día): con batería es una función temporal, activada por una acción deliberada.
- La seguridad exige credencial única por dispositivo, validación de toda entrada, escape de toda salida, protección contra pedidos falsos, ningún secreto visible y servidor apagado cuando ya no se usa.
- Nunca se abre un puerto del router hacia el dispositivo; el acceso remoto se resuelve con conexión saliente a un servicio.
- La validación va antes de aplicar cambios: completos o ninguno.
¿Listo para el tema 24?
Antes de avanzar, diseñá la página de configuración de tu dispositivo: qué campos tiene, cuáles son obligatorios, qué rango admite cada uno y qué mensaje de error muestra. Calculá su tamaño comprimido y la memoria que necesitarían tres clientes a la vez. Después escribí tres entradas hostiles (un valor fuera de rango, un texto con <script> y un cuerpo enorme) y comprobá que tu servidor las rechaza sin cambiar nada.
En el próximo tema cerramos el bloque de HTTP con los WebSockets y la comunicación en tiempo real: una conexión persistente por la que el servidor puede empujar datos al dispositivo, sin que éste tenga que preguntar.