Saltar al contenido principal

Módulo Http

El módulo Http es un cliente HTTP: hace peticiones GET y POST a una dirección y te devuelve lo que contesta el servidor. Sirve para hablar con APIs REST, servicios web o aparatos de tu red. Las cabeceras que pongas valen para todas las peticiones siguientes, y status() y error() cuentan cómo fue la última.

use Http

Captura del módulo HTTP de JARU para realizar peticiones web desde proyectos ESP32 conectados

Plataformas​

WindowsESP32 / ESP32-S3ESP32-P4ESP32-S31
La redLa del PCLa WiFi del perfil de la placaIgualIgual
HTTPSSí, y Windows comprueba el certificado del servidorSí, pero sin comprobar el certificado del servidorSí, y comprueba el certificado del servidorIgual
Redirecciones (301, 302…)Las sigue y devuelve la página finalNo las sigue: devuelve la respuesta de la redirecciónIgualIgual
Http.status() si no hay respuesta0, o un número negativo de -1 a -5Un número negativo, de -1 a -11 (0 si la dirección no se entiende)0, o -1 si no llega la respuesta0, o -1 si no llega la respuesta
Con Content-Type en setHeader, un post mandaEl de setHeaderEl de postEl de setHeaderEl de setHeader

No hay un módulo WiFi: la placa se conecta sola al arrancar, con la red que tenga su perfil en ESP32 Tools. Lo cuenta Pantalla, sonido, SD y red.

Funciones​

FunciónQué haceDevuelve
Http.get(url)Hace una petición GETEl cuerpo de la respuesta, o nil si no hay respuesta
Http.post(url, cuerpo) / Http.post(url, cuerpo, tipo)Hace una petición POSTEl cuerpo de la respuesta, o nil si no hay respuesta
Http.setHeader(nombre, valor)Pone una cabecera para las peticiones siguientestrue
Http.clearHeaders()Quita todas las cabecerastrue
Http.status()El código de estado de la última peticiónUn entero
Http.error()El error de la última peticiónUna cadena; vacía si no hubo error
Http.timeout() / Http.timeout(ms)Lee o cambia el tiempo de esperaLos milisegundos, o true al cambiarlo

Mientras espera la respuesta, el programa se para: no se pinta la pantalla ni corren los callbacks de Timer, MQTT o GPIO.

Si les pasas argumentos que no son los que esperan, ninguna lanza una excepción: devuelven un valor de error, que try no captura (ver Errores y excepciones).

get(url)​

Pide la dirección url, que empieza por http:// o https://, y devuelve el cuerpo de la respuesta como cadena.

  • Si el servidor contesta, devuelve lo que manda, también si contesta con un error como 404 o 500. Mira Http.status() para saber si fue bien.
  • Si no hay respuesta (no hay red, no encuentra el servidor, se acaba el tiempo…), devuelve nil, y Http.error() dice qué pasó.
use Http

var respuesta = Http.get("https://api.ejemplo.com/datos")

if (respuesta == nil) then
println("Sin respuesta: ", Http.error())
elsif (Http.status() == 200) then
println("Respuesta: ", respuesta)
else
println("El servidor contesta ", Http.status())
end

post(url, cuerpo, tipo)​

Manda cuerpo, una cadena, a la dirección url con una petición POST. tipo es opcional: el tipo del contenido, que se manda en la cabecera Content-Type; si no lo pones, "application/json". Devuelve lo mismo que get.

use Http

var datos = '{"temperatura": 25.5, "humedad": 60}'
var respuesta = Http.post("https://api.ejemplo.com/sensores", datos)

if (respuesta != nil) then
println("El servidor contesta ", Http.status(), ": ", respuesta)
else
println("Error en el POST: ", Http.error())
end

Con otro tipo de contenido, por ejemplo un formulario:

use Http

var formulario = "usuario=admin&clave=secreto"
var respuesta = Http.post("https://api.ejemplo.com/login", formulario, "application/x-www-form-urlencoded")

Si has puesto una cabecera Content-Type con setHeader, en Windows, el P4 y el S31 se manda esa, y en ESP32 y ESP32-S3, la de post.

setHeader(nombre, valor)​

Pone la cabecera nombre con el valor valor en todas las peticiones siguientes, hasta que llames a clearHeaders(). Si ya había una con ese nombre, cambia su valor. Devuelve true.

use Http

Http.setHeader("Authorization", "Bearer mi_token")
Http.setHeader("X-API-Key", "clave_secreta")

// Las dos cabeceras van en esta petición y en las siguientes
var respuesta = Http.get("https://api.ejemplo.com/privado")

clearHeaders()​

Quita todas las cabeceras que hayas puesto con setHeader y devuelve true. Úsalo al pasar de una API a otra.

use Http

Http.setHeader("Authorization", "Bearer token_de_la_api_1")
Http.get("https://api1.ejemplo.com/datos")

Http.clearHeaders()
Http.setHeader("X-Custom-Header", "otro_valor")
Http.get("https://api2.ejemplo.com/otros-datos")

status()​

Devuelve el código de estado HTTP de la última petición: el número con el que contesta el servidor.

CódigoSignificado
200Bien
201Creado
301, 302La página se ha movido (en las placas, que no siguen las redirecciones)
400La petición está mal hecha
401Falta autenticarse
403No tienes permiso
404No existe
500Error del servidor

Si no hubo respuesta, vale 0 o un número negativo, según la plataforma (ver Plataformas). Antes de la primera petición, vale 0.

use Http

var respuesta = Http.get("https://api.ejemplo.com/usuario/123")
var codigo = Http.status()

switch (codigo)
case 200:
println("Usuario: ", respuesta)
case 404:
println("Ese usuario no existe")
case 401:
println("No autorizado: mira el token")
default:
println("Código de respuesta: ", codigo)
end

error()​

Devuelve el error de la última petición, o una cadena vacía si no lo hubo. Una respuesta con un código de error (404, 500…) no es un error para error(): el servidor ha contestado.

MensajeDóndeCuándo
Invalid URLWindowsLa dirección no se entiende.
Connection errorWindowsNo se puede conectar con el servidor.
Network errorWindowsNo llega la respuesta a tiempo, u otro fallo de la red.
Protocol error, Read error, Write error, Generic errorWindowsFalla la conversación con el servidor.
begin() failedESP32 y ESP32-S3La dirección no se entiende.
GET failed, POST failedTodas las placasLa petición no llega a hacerse: no hay red, no encuentra el servidor, se acaba el tiempo… En ESP32 y ESP32-S3, Http.status() dice el motivo con su número negativo.
init() failed, write() shortESP32-P4 y ESP32-S31No se puede preparar la petición, o no se puede mandar el cuerpo entero.
El nombre del error del chip (ESP_ERR_…)ESP32-P4 y ESP32-S31No se puede abrir la conexión.
use Http

var respuesta = Http.get("https://servidor-que-no-existe.ejemplo/api")
if (respuesta == nil) then
println("La petición falló: ", Http.error())
end

timeout(ms)​

Sin argumentos, devuelve el tiempo máximo que espera cada petición, en milisegundos: de serie, 10000 (10 segundos). Con ms, un entero, lo cambia y devuelve true; un valor negativo cuenta como 0.

En Windows, la petición se abandona cuando pasa ese tiempo y un segundo más de margen.

use Http

println("Espera: ", Http.timeout(), " ms") // Espera: 10000 ms
Http.timeout(30000) // 30 segundos, para una API lenta

Ejemplo completo​

Un sensor que manda su lectura a una API cada 30 segundos:

use Http, GPIO

Http.timeout(15000)
Http.setHeader("X-Device-ID", "esp32-sensor-001")

var pinSensor = 34
GPIO.pinmode(pinSensor, GPIO.INPUT)

while (true)
var lectura = GPIO.aread(pinSensor)
var temperatura = lectura * 0.1 // la conversión de tu sensor

var cuerpo = '{"aparato": "esp32-001", "temp": ' + temperatura.toString() + '}'
var respuesta = Http.post("https://api.miservidor.com/telemetria", cuerpo)

if (respuesta == nil) then
println("Sin respuesta: ", Http.error())
elsif (Http.status() == 200 or Http.status() == 201) then
println("Enviado")
else
println("El servidor contesta ", Http.status())
end

pause(30000)
end

Con autenticación​

Primero pide un token, y después lo manda en la cabecera Authorization. Lee la respuesta con JSON:

use Http, JSON

var acceso = '{"email": "usuario@ejemplo.com", "clave": "clave123"}'
var respuesta = Http.post("https://api.ejemplo.com/auth/login", acceso)

if (respuesta == nil or Http.status() != 200) then
println("No he podido entrar: ", Http.status(), " ", Http.error())
exit(1)
end

var sesion = JSON.parse(respuesta)
Http.setHeader("Authorization", "Bearer " + sesion["token"])

var perfil = Http.get("https://api.ejemplo.com/usuarios/perfil")
if (perfil != nil and Http.status() == 200) then
var datos = JSON.parse(perfil)
println("Hola, ", datos["nombre"])
end

Con reintentos​

Reintenta si no hay respuesta o si falla el servidor (códigos 500 y siguientes), pero no si el error es de la petición:

use Http

func pedir(url)
for (var intento = 1; intento <= 3; intento++)
var respuesta = Http.get(url)
if (respuesta == nil) then
println("Sin respuesta (", Http.error(), "), intento ", intento)
elsif (Http.status() >= 500) then
println("Error del servidor (", Http.status(), "), intento ", intento)
elsif (Http.status() >= 400) then
println("Error de la petición: ", Http.status())
return nil
else
return respuesta
end
pause(2000)
end
return nil
end

var datos = pedir("https://api.ejemplo.com/datos")
if (datos != nil) then
println("Datos: ", datos)
end

Trampas y problemas conocidos​

  • Un 404 no da nil. get y post devuelven lo que contesta el servidor, sea cual sea el código: mira siempre Http.status().
  • Los errores de los argumentos no se capturan. Un argumento de otro tipo, como Http.get(5), no lanza una excepción: devuelve un valor de error.
  • El programa espera. Una petición para el programa hasta que llega la respuesta o se acaba el tiempo: en un juego, la pantalla se congela ese rato.
  • Las cabeceras se quedan. Lo que pones con setHeader va en todas las peticiones siguientes, también a otros servidores. Usa clearHeaders() al cambiar de API.
  • HTTPS en ESP32 y ESP32-S3 no comprueba el certificado. La conexión va cifrada, pero la placa no comprueba que el servidor sea quien dice ser. No mandes así datos que tengan que estar protegidos.
  • Las redirecciones en las placas. Si la dirección redirige a otra (por ejemplo, de http:// a https://), en la placa recibes el código 301 o 302: pon en el programa la dirección final.

Todos los problemas conocidos de JARU, juntos, están en Problemas conocidos.