Desarrollo de aplicaciones IoT en la nube para Arduino y ESP8266

Tekst
0
Recenzje
Przeczytaj fragment
Oznacz jako przeczytane
Czcionka:Mniejsze АаWiększe Aa

2.2.5.2 Servidor web

En el apartado anterior han creado un cliente web que se conectaba a un servidor. Ahora se situarán en el lado opuesto de la comunicación y crearán un servidor web al que se pueda llegar desde cualquier cliente. El servidor web que desplegarán dará servicio dentro de su red WiFi en la dirección IP 192.168.1.99. Cuando accedan a él tecleando dicha IP en la barra de direcciones de su navegador, aparecerá la siguiente página web:


La dirección IP utilizada solo tiene sentido dentro de la red WiFi en la que está conectado el ESP-01. Por eso el navegador utilizado deberá ejecutarse en un ordenador o teléfono móvil previamente conectado a dicha red.

Si acceden al código HTML de dicha página, verán lo siguiente:

<!DOCTYPE html> <head> <meta charset=“UTF-8”> <title>Servidor Web ESP8266</title> </head> <html> <body> <h1>Bienvenido al servidor Web de mi ESP8266</h1> </body> </html>

Todos los navegadores ofrecen la posibilidad de ver el código HTML de una página web. Si, por ejemplo, utilizan Chrome, pulsen con el botón derecho del ratón sobre dicha página y seleccionen “Ver código fuente de la página”.

Dicho código deberá haber sido generado por el propio ESP-01. Para entenderlo, es necesario tener unas nociones básicas de HTML que trataré de dar a continuación. Como se dijo en la introducción de este capítulo, HTML es un lenguaje hipertexto de marcas. Las marcas son etiquetas o tags limitadas entre los caracteres ‘<’ y ‘>’. Se utilizan para estructurar el contenido de una página web. Generalmente cada etiqueta tiene su correspondiente de cierre (la misma, pero situada entre los caracteres ‘</’ y ‘>’), dentro de las cuales se enmarca el contenido al que afectan. Por ejemplo, la siguiente sentencia HTML utiliza la etiqueta h1 para indicarle al navegador que muestre el texto “Hola Mundo” como la cabecera más importante del documento (generalmente el título de la página).

<h1>Bienvenido al servidor Web de mi ESP8266</h1>

Si quisieran que dicho texto se mostrara como subtítulo de la página, se utilizaría la etiqueta h2.

<h2>Bienvenido al servidor Web de mi ESP8266</h2>

El texto sigue siendo el mismo, pero se visualiza de forma diferente dependiendo de las etiquetas dentro de las que se encuentre, que indicarán al navegador cómo mostrarlo.

La primera etiqueta que se encuentra en un documento HTML5 es la siguiente.

<!DOCTYPE html>

A continuación aparecen las que contienen la cabecera (head) y el cuerpo del documento (html), junto con las correspondientes de cierre.

<head> … </head> <html> … </html>

La cabecera contiene elementos generales de la página, en el caso que nos ocupa, la codificación de los caracteres (etiqueta meta que indica una codificación UTF-8) o el título de la pestaña donde se mostraría en el navegador (etiqueta title).

<meta charset=”UTF-8”> <title>Servidor Web ESP8266</title>

El cuerpo contiene todos los elementos visibles de la página, en este caso, el mensaje de bienvenida.

<h1>Bienvenido al servidor Web de mi ESP8266</h1>

HTML es un estándar y, por lo tanto, cualquier página expresada en este lenguaje podrá visualizarse en un navegador web. De todas formas, dependiendo de la versión HTML implementada o del tipo de navegador, la visualización de una misma página puede llegar a tener un aspecto diferente.

¿Cómo ha conseguido el ESP-01 atender la petición de un navegador y responder con una página HTML? Vean el código que lo ha permitido.

#include <ESP8266WiFi.h> // SSID de la red WIFI a la que desea conectarse const char* ssid = “*********”; //contraseña de dicha red const char* password = “ *********”; //dirección IP elegida dentro de la red IPAddress ip(192, 168, 1, 99); //dirección IP del gateway IPAddress gateway(192, 168, 1, 1); //mascara de red IPAddress subnet(255, 255, 255, 0); WiFiServer servidorWeb(80); WiFiClient clienteWeb; void setup() { Serial.begin(115200); //Se inicia la conexión WiFI Serial.print(“Conectando a “ + String(ssid) + “ “); WiFi.config(ip, gateway, subnet); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED){ delay(500); Serial.print(“.”); } Serial.println(“ Conectado”); //Se arranca el servidor <link¢Web servidorWeb.begin(); Serial.println(“Servidor</link> web arrancado”); } void loop() { clienteWeb = servidorWeb.available(); //Se espera que llegue un cliente if (clienteWeb) { boolean lineaEnBlanco = true; //El cliente está conectado while (clienteWeb.connected()) { //el cliente ha transmitido datos if (clienteWeb.available()) { char c = clienteWeb.read(); if (c == ‘\n’ && lineaEnBlanco){ clienteWeb.println(“HTTP/1.1 200 OK”); clienteWeb.println(“Content-Type: text/html”); clienteWeb.println(“Connection: close”); clienteWeb.println(); clienteWeb.println(“<!DOCTYPE HTML>”); clienteWeb.println(“<head>”); clienteWeb.println(“<meta charset=‘UTF-8’>”); clienteWeb.println(“<title>Servidor Web ESP8266</title>”); clienteWeb.println(“</head>”); clienteWeb.println(“<html>”); clienteWeb.println(“<body>”); clienteWeb.println(“<h1>Bienvenido al servidor Web de mi ESP8266</h1>”); clienteWeb.println(“</body>”); clienteWeb.println(“</html>”); break; } if (c == ‘\n’) lineaEnBlanco = true; else if (c != ‘\r’) lineaEnBlanco = false; } } //Se cierra la conexión para asegurar que los datos se envían delay(10); clienteWeb.stop(); } }

Como en el caso anterior, lo primero que se hace es importar la librería de comunicación web del ESP8266.

#include <ESP8266WiFi.h>

A continuación declararán las variables que serán utilizadas en el resto del programa. Además de la que contienen el SSID (ssid) y la contraseña (password) de la red WiFi, se observan otras tres nuevas.

La primera (ip) contendrá la dirección IP por la que el servidor web atenderá las peticiones, en concreto, la 192.168.1.99. De no hacerlo así, su router le asignaría una de acuerdo al protocolo DHCP (Dynamic Host Configuration Protocol).

Tengan cuidado de no elegir una IP ya utilizada en la WiFi de su casa, es decir, que haya sido asignada previamente por su router al ordenador, móvil, TV o cualquier otro dispositivo con capacidades WiFi que tenga conectado a la red.

Tampoco use la dirección 192.168.1.1 porque es la empleada generalmente para la administración del propio router.

La segunda variable (gateway) contendrá la dirección IP del gateway (puerta de enlace). Un gateway es un dispositivo que permite interconectar redes diferentes. Su propósito es hacer de traductor entre los lenguajes empleados por los protocolos de ambas redes.

En sus casas, el router es el responsable de realizar dicha función, haciendo de pasarela entre la red externa del proveedor de comunicaciones que haya contratado y su red local, a la que se conectan todos sus dispositivos. Generalmente, dicha dirección IP es la 192.168.1.1. Si no funcionara con esa, tendrían que consultar la información de su router o al proveedor.

Finalmente, se encontrarán con la tercera variable (subnet). Representa la máscara de subred y, sin entrar en detalles, solo les diré que sirve para conocer qué números de una dirección IP identifican la red local y cuáles el dispositivo conectado a ella. Una máscara de subred 255.255.255.0, como la de la variable de su programa, indica que en la dirección 192.168.1.99, la red está formada por los dígitos 192.168.1 y el servidor web es el dispositivo número 99 dentro de dicha red.

IPAddress ip(192, 168, 1, 99); IPAddress gateway(192, 168, 1, 1); IPAddress subnet(255, 255, 255, 0);

La última variable representa el servidor web que han creado perteneciente a la clase WiFiServer. Éste atenderá las peticiones de los clientes por el puerto 80.

WiFiServer servidorWeb(80);

En el bloque setup() realizarán las mismas operaciones de conexión a la red WiFi que en la práctica del cliente pero, además, arrancarán el servidor web.

servidorWeb.begin();

En el bucle loop() lo primero que harán es ver si el servidor web tiene algún cliente conectado. Para ello utilizarán el método available(), que, de haberlo, devolverá dicho cliente como un objeto de la clase WiFiClient (clienteWeb). En ese caso, la condición de la sentencia if se evaluaría como true y esperaría a recibir la petición que quisiera hacerle. Si no hubiera ningún cliente, el método available() devolvería null y la condición no se cumpliría.

WiFiClient clienteWeb = servidorWeb.available(); if (clienteWeb){ … }

El bloque de control while asegura, utilizando el método connected() del cliente, que este siga conectado mientras se lee la información transmitida. Sabrán que hay datos pendientes de ser leídos mientras el método available() del cliente (utilizado en la condición if) devuelva true. La lectura de la petición realizada por el cliente se hará carácter a carácter con el método read().

 

while (clienteWeb.connected()) { if (clienteWeb.available()) { char c = clienteWeb.read();

Dicha lectura finalizará cuando se encuentre una línea en blanco (lineaEnBlanco), es decir, cuando se cumpla la condición del siguiente if.

if (c == ‘\n’ && lineaEnBlanco){ … }

El valor de la variable lineaEnBlanco vendrá determinado por las siguientes sentencias (la estructura de los mensajes de solicitud se estudia en detalle más adelante).

if (c == ‘\n’) lineaEnBlanco = true; else if (c != ‘\r’) lineaEnBlanco = false;

Finalizada la lectura del mensaje de solicitud, solo queda enviar, línea a línea, la respuesta al cliente con el método println(). No se preocupen por el contenido enviado como mensaje de respuesta porque su estructura, al igual que la de las peticiones, tendrán ocasión de conocerla más adelante. Por ahora solo es necesario que identifiquen una primera parte con información de control.

clienteWeb.println(“HTTP/1.1 200 OK”); clienteWeb.println(“Content-Type: text/html”); clienteWeb.println(“”);

Y una segunda parte con el contenido de la página HTML que se ha de mostrar.

clienteWeb.println(“<!DOCTYPE HTML>”); clienteWeb.println(“<head>”); clienteWeb.println(“<meta charset=‘UTF-8’>”); clienteWeb.println(“<title>Servidor Web ESP8266</title>”); clienteWeb.println(“</head>”); clienteWeb.println(“<html>”); clienteWeb.println(“<h1>Bienvenido al servidor Web de mi ESP8266</h1>”);

Si en esta segunda parte, en vez de clienteWeb.println(), hubieran utilizado Serial.println(), habrían obtenido el código HTML de la página web por el monitor serie de Arduino.


Una vez enviada la respuesta al cliente, la sentencia break les permite salir del bucle while en el que han permanecido mientras el cliente estaba conectado, forzando la finalización la conexión.

clienteWeb.stop();

Cada vez que un cliente accede al servidor web del ESP-01, se enciende un diminuto led que avisa de este hecho. Si el navegador indicara que está esperando respuesta (en Chrome en la parte inferior izquierda), refresquen la página para comprobar si se enciende dicho piloto un instante. Si no lo hiciera, reseteen el ESP-01 o apáguenlo y vuelvan a encenderlo.

2.3 PROTOCOLO HTTP

Como se ha explicado anteriormente, el protocolo HTTP es el que permite la comunicación entre clientes y servidores web a nivel de aplicación. El proceso es el siguiente:

1. El cliente realiza una petición HTTP a un servidor web solicitando información.

2. El servidor recibe dicha petición.

3. El servidor genera la información solicitada y devuelve una respuesta HTTP al cliente con la información generada.

4. El cliente recibe dicha respuesta.

En la siguiente imagen pueden ver este proceso gráficamente, utilizando el modelo de capas estudiado anteriormente.


Como ya saben, dicho proceso de comunicación se enmarca dentro de lo que se conoce como arquitectura cliente-servidor, es decir, aquella en la que la comunicación se establece entre proveedores de recursos o servicios (servidores) y quienes se los solicitan (clientes). Cada una de las peticiones de un cliente se realiza de forma independiente, sin ningún conocimiento de las anteriores, por lo que HTTP se considera un protocolo sin estado.

Con HTTP la información viaja en claro y, por lo tanto, es susceptible de ser vista por cualquiera con capacidad para escuchar el tráfico que va por la red. En caso de que quieran transmitir información confidencial, esta deberá ser cifrada para ocultarla a miradas indiscretas. En ese caso deberán emplear HTTPS, que utiliza TLS (Transport Layer Security - Seguridad de la capa de transporte), aunque anteriormente se conocía como SSL (Secure Sockets Layer - Capa de sockets seguros), para encriptar la comunicación. A diferencia de HTTP, cuyo puerto de escucha estándar es el 80, HTTPS utiliza el 443.

Hay dos tipos de mensajes: el utilizado por los clientes para realizar las peticiones al servidor web y el de respuesta con la información solicitada. El protocolo HTTP establece el formato de ambos tipos de mensaje (válido también para HTTPS). Veamos en detalle cada uno de ellos.

2.3.1 Peticiones HTTP

Las peticiones HTTP tienen la siguiente estructura:

• Línea de solicitud

• Cabeceras

• Línea en blanco

• Cuerpo de mensaje

Todas las líneas deben terminar con un retorno de carro y un avance de línea, que es lo único que debe ir en la línea en blanco.

La secuencia de avance de línea (line feed – LN) es ‘\n’. Sería como mover el cursor hacia la siguiente línea sin volver al comienzo de esta. La secuencia correspondiente al retorno de carro (carriage return - CR) es ‘\r’. Movería el cursor al comienzo de la línea sin avanzar a la siguiente.


La línea de solicitud indica el tipo de petición que se quiere hacer (los más comunes son GET y POST), la ruta del servidor a la que se realiza (habitualmente sería la de una página HTML) y el protocolo utilizado (generalmente HTTP/1.1).

Las cabeceras contienen información sobre el contenido que quiere obtenerse o sobre el cliente. Está formado por una serie de líneas, cada una de las cuales consta de una clave y un valor separados por el carácter ‘:’. Una de las más comunes es Host, cuyo valor corresponde con la dirección IP o el nombre del servidor web al que se realiza la petición.

La sección formada por la línea de solicitud y las cabeceras (si existieran) se termina con una línea en blanco que siempre deberá estar presente.

El cuerpo del mensaje es opcional y contendrá cualquier tipo de información asociada a la petición. Puesto que el contenido del cuerpo de un mensaje puede ser de cualquier tipo y tamaño, para ayudar a interpretar la información que contiene se utilizan las siguientes cabeceras:

• Content-Type: especifica el formato del contenido del cuerpo de la petición. Si dicho contenido fuera una página HTML, su valor sería text/html. Otro formato que verán frecuentemente es application/ json, utilizado para el intercambio de datos entre aplicaciones. Se explicará más adelante porque será el empleado para la comunicación con los servicios en la nube usados durante el desarrollo de las aplicaciones IoT.

• Content-Length: es el tamaño, en bytes, del cuerpo de la petición.

La cabecera Content-Type toma como valor un tipo MIME (Multipurpose Internet Mail Extensions), que es una forma estandarizada de indicar el formato de un documento. La estructura de un tipo MIME está formada por un tipo y un subtipo separados por el carácter ‘/’. El tipo representa la categoría (por ejemplo, text, image, video, application, etc.). El subtipo es específico para cada tipo (por ejemplo, plain o html para text, jpeg o png para image, etc.).

Irán viendo más cabeceras a largo del libro cuyo significado, si fuera de interés, se explicará según vayan apareciendo.

2.3.1.1 Tipos de peticiones HTTP

Como se acaba de indicar, la primera línea de una petición HTTP incluye el método utilizado para realizarla. Aunque hay diversos métodos (GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE y PATCH), solo se explicarán los más comunes: PUT y GET.

El método GET se utiliza fundamentalmente para la recogida de datos del servidor. Dicha consulta vendrá especificada por parejas clave-valor que formarán parte del propio URL. Las parejas clave-valor se separan del URL por el carácter ‘?’ y, entre ellas, por el carácter ‘&’. Cada clave se asigna a un valor mediante el carácter ‘=’. Por lo tanto, su formato es el siguiente:

<URL>?clave1=valor1&clave2=valor2 ...

Por ejemplo, prueben a escribir lo siguiente en la barra de direcciones de un navegador web:

https://www.google.com/search?q=marcombo

Efectivamente, se obtienen los mismos resultados que introduciendo la palabra marcombo en la página de Google https://www.google.com. Eso es porque este buscador utiliza el método GET para obtener la información solicitada en sus servidores, una de cuyas claves es q (query - consulta), cuyo valor en este caso es marcombo.

Como podrán observar, el hecho de utilizar este método supone que los datos enviados al servidor sean visibles en la barra de direcciones del navegador. Por ese motivo, nunca se debe utilizar para realizar consultas en las que se transmita información confidencial. Además, si el navegador se ejecutara en un ordenador compartido, el problema sería aún mayor, ya que hay que tener en cuenta que las solicitudes GET se almacenan en caché y permanecen en el historial del navegador. Si no se borraran, podrían ser utilizadas por terceros con acceso al mismo ordenador.

El método POST sirve para el envío de datos al servidor. Dichos datos estarán contenidos en el cuerpo del mensaje de solicitud, por lo que no tiene los problemas de seguridad del método GET, ya que dicha información no se almacena en caché ni tampoco en el historial del navegador.

Como ventaja adicional, el cuerpo de un mensaje POST no tiene limitación de tamaño, por lo que se pueden enviar grandes volúmenes de datos. En cambio, la longitud de un URL es limitada (aproximadamente 3000 caracteres), por lo que en las peticiones GET puede viajar poca información.

Por lo general, las peticiones GET no tienen cuerpo, aunque técnicamente sería posible. Por su parte, las peticiones POST no suelen llevar parámetros en el URL, aunque podría hacerse en casos excepcionales que veremos más adelante en el uso de ciertos servicios web.

2.3.2 Respuestas HTTP

Las respuestas HTTP tienen una estructura similar a la de las peticiones:

• Línea de estado • Cabeceras • Línea en blanco

• Cuerpo de mensaje


La línea de estado indica qué protocolo está hablando el servidor (generalmente HTTP/1.1), que debe coincidir con el utilizado por el cliente (presente en la línea de solicitud de la petición). Luego contiene un código de estado numérico y un mensaje corto descriptivo que indica lo que ha sucedido durante la comunicación. Por ejemplo, la siguiente línea de estado indica que la solicitud realizada por un cliente se ha atendido correctamente.

HTTP / 1.1 200 OK

Cuando un cliente realiza una petición a un servidor, se espera que este haga lo solicitado. Pero desgraciadamente no siempre es así. Tanto si la petición se ha realizado con éxito como si no, el código de estado de la respuesta HTTP indicará lo sucedido. Dicho código de estado (no de error) está formado por tres dígitos cuyo significado pueden encontrar en https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml. Solo voy a destacar los dos tipos más frecuentes con los que se van a encontrar:

• 2xx: son los códigos que el servidor envía en la respuesta cuando la petición ha podido ser recibida y procesada correctamente. El más conocido y esperado por todos es el 200.

 

• 4xx: son los errores que devuelve el servidor cuando el cliente no ha construido bien la petición o esta no ha podido ser procesada. Durante el desarrollo de clientes lo verán si su programación no ha sido la correcta.

Las cabeceras (una por línea) permiten al servidor enviar información adicional sobre la respuesta o el propio servidor. Por ejemplo, si se requiere autenticación, el servidor lo utilizará para indicar el tipo de autenticación que precisa. De forma similar a lo que sucedía en las peticiones, la cabecera más común es Content-Type, que, en este caso, será utilizada por el cliente para interpretar la información contenida en el cuerpo de la respuesta. Además, muchas respuestas contienen una cabecera Content-Length, la cual especifica la longitud, en bytes, del cuerpo. Sin embargo, esta línea rara vez está presente en páginas HTML generadas dinámicamente.

La sección formada por la línea de estado y las cabeceras (si existieran) siempre debe terminar con una línea en blanco.

El cuerpo de la respuesta viene a continuación de dicha línea en blanco y puede contener cualquier tipo de información. Como se acaba de indicar, para interpretarla se utilizan frecuentemente las cabeceras Content-Type y Content-Length descritas antes. En el caso de una respuesta web típica, este contendrá el documento HTML que se mostrará en el navegador, siendo su Content-Type de tipo text/html.