Web Server con esp8266 e esp32: aggiunta di un back-end REST protetto – 5
Ho scritto un lungo tutorial su come creare un server REST, puoi fare riferimento al tutorial “Come creare un server REST su esp8266 o esp32” per avere maggiori informazioni.
Qui andremo a riutilizzare la pagina di accesso, ma variamo il sito per mostrare temperatura e umidità.
Useremo un DHT12 per ottenere questi dati e li mostreremo direttamente su una pagina, creiamo una pagina di dettaglio con un grafico. Puoi trovare informazioni su questo sensore in questo articolo “DHT12: un sensore i2c di umidità e temperatura veramente economico” .
Per la demo useremo lo SPIFFS per memorizzare i dati, ma questa soluzione non è una buona idea, per avere maggiori informazioni a riguardo fare riferimento all’articolo “WeMos D1 mini (esp8266): SPIFFS FileSystem integrato“.
Per rendere il codice più pulito mi piace separare il web server dal server REST, pubblicandolo sulla porta 8080, questo approccio non sarà apprezzato da tutti, chiaramente questo “brucia” risorse, ma la pulizia, in molti progetti, può essere più utile delle risorse.
Questo progetto è molto semplice, quindi utilizzo solo il server di base.
Server REST
Il server REST ha solo 3 end point tutti in GET
void restEndPoint(){
// External rest end point (out of authentication)
httpServer.on("/login", HTTP_POST, handleLogin);
httpServer.on("/logout", HTTP_GET, handleLogout);
httpServer.on("/temperatureHumidity", HTTP_GET, handleTemperatureHumidity);
}
Per la /login
come descritto, prendo i dati da un modulo POST, la /logout
è una semplice GET richiamata direttamente dall’URL, e tutti questi end point tornano uno stato 301 per fare un reindirizzamento a un altro URL.
/**
* Retrieve temperature humidity realtime data
*/
void handleTemperatureHumidity(){
const size_t capacity = 1024;
DynamicJsonDocument doc(capacity);
doc["humidity"] = dht12.readHumidity();
doc["temp"] = dht12.readTemperature();
// If you don't have a DHT12 put only the library
// comment upper line and decomment this line
// doc["humidity"] = random(10,80);
// doc["temp"] = random(1000,3500)/100.;
String buf;
serializeJson(doc, buf);
httpServer.send(200, F("application/json"), buf);
}
In questa funzione recupererò la temperatura e l’umidità dal DHT12 e le aggiungerò in una struttura Json che sarà gestita dal front-end.
var requestTempHum = function(){
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
var res = JSON.parse(xhttp.responseText);
humidityFunction(res.humidity);
termometerTemperature(res.temp);
}
};
xhttp.onerror = function () {
alert("Status code is " + this.status + " click F12 and check what is the problem on console");
};
xhttp.open("GET", "/temperatureHumidity", true);
xhttp.send();
}
Ma con questo codice l’end point REST non è protetto, quindi dobbiamo aggiungere un altro pezzo di codice per completarlo.
Nell’articolo precedente avevamo visto come aggiungere un’autenticazione tramite token e usiamo i cookie come trasporto, perché sono molto semplici da gestire perché tutta la gestione è demandata al browser.
Con questo approccio abbiamo la possibilità di recuperare i dati da tutte le richieste, quindi possiamo utilizzare lo stesso comportamento per l’end point REST.
Gestiremo lo stato HTTP 401 (Unauthorized), creeremo una funzione che funzioni come intercettore del cookie di autenticazione.
Possiamo riutilizzare la funzione is_authenticated()
per controllare le credenziali e poi inviare il messaggio corretto.
//Check if header is present and correct
bool is_authenticated() {
Serial.println("Enter is_authenticated");
if (httpServer.hasHeader("Cookie")) {
Serial.print("Found cookie: ");
String cookie = httpServer.header("Cookie");
Serial.println(cookie);
String token = sha1(String(www_username) + ":" +
String(www_password) + ":" +
httpServer.client().remoteIP().toString());
if (cookie.indexOf("ESPSESSIONID=" + token) != -1) {
Serial.println("Authentication Successful");
return true;
}
}
Serial.println("Authentication Failed");
return false;
}
void manageSecurity(){
if (!is_authenticated()) {
httpServer.send(401, F("application/json"), "{\"msg\": \"You must authenticate!\"}");
return;
}
}
È necessario aggiungere questa funzione in tutti gli endpoint REST e quindi se si tenta di ottenere dati senza autenticazione, verrà visualizzato uno stato http 401 (non autorizzato).
/**
* Retrieve temperature humidity realtime data
*/
void handleTemperatureHumidity(){
Serial.println("handleTemperatureHumidity");
manageSecurity();
Serial.println("handleTemperatureHumidity security pass!");
const size_t capacity = 1024;
DynamicJsonDocument doc(capacity);
doc["humidity"] = dht12.readHumidity();
doc["temp"] = dht12.readTemperature();
// If you don't have a DHT12 put only the library
// comment upper line and decomment this line
// doc["humidity"] = random(10,80);
// doc["temp"] = random(1000,3500)/100.;
String buf;
serializeJson(doc, buf);
httpServer.send(200, F("application/json"), buf);
}
AsyncWebServer
Ora possiamo fare la stessa cosa con l’AsyncWebServer .
Fai attenzione ora sul WiFiClient di ESP32 c’è un grosso bug, puoi controllare qui , quindi se provi ad avviare questo server avrai qualche problema in presenza di molte richieste simultanee. Perciò ho scritto un porting di questo sketch usando la libreria ESPAsyncWebServer che usa l’AsyncTCP dove non si presenta questo problema. Ma ora spiego la soluzione attuale per l’esp8266 e alla fine dell’articolo la soluzione equivalente per ESP32.
Risolto sulla versione 1.0.5 dell’esp32 core.
Il codice è abbastanza simile, qui la funzione per recuperare i dati dal DHT12:
/**
* Retrieve temperature humidity realtime data
*/
void handleTemperatureHumidity(AsyncWebServerRequest *request){
Serial.println("handleTemperatureHumidity");
manageSecurity(request);
Serial.println("handleTemperatureHumidity security pass!");
const size_t capacity = 1024;
DynamicJsonDocument doc(capacity);
// doc["humidity"] = dht12.readHumidity();
// doc["temp"] = dht12.readTemperature();
// If you don't have a DHT12 put only the library
// comment upper line and decomment this line
doc["humidity"] = random(10,80);
doc["temp"] = random(1000,3500)/100.;
String buf;
serializeJson(doc, buf);
request->send(200, F("application/json"), buf);
}
Di seguito la gestione della sicurezza per l’endpoint REST:
//Check if header is present and correct
bool is_authenticated(AsyncWebServerRequest *request) {
Serial.println("Enter is_authenticated");
if (request->hasHeader("Cookie")) {
Serial.print("Found cookie: ");
String cookie = request->header("Cookie");
Serial.println(cookie);
String token = sha1(String(www_username) + ":" +
String(www_password) + ":" +
request->client()->remoteIP().toString());
if (cookie.indexOf("ESPSESSIONID=" + token) != -1) {
Serial.println("Authentication Successful");
return true;
}
}
Serial.println("Authentication Failed");
return false;
}
void manageSecurity(AsyncWebServerRequest *request){
if (!is_authenticated(request)) {
request->send(401, F("application/json"), "{\"msg\": \"You must authenticate!\"}");
return;
}
}
Grazie
- Web Server su esp8266 e esp32: servire pagine e gestire LEDs
- Web Server su esp8266 e esp32: servire pagine compresse come byte array e SPIFFS
- Web Server su esp8266 e esp32: web server generico multiuso
- Web Server su esp8266 e esp32: gestione sicurezza ed autenticazione
- Web Server su esp8266 e esp32: aggiunta di un back-end REST protetto
- Web Server su esp8266 e esp32: Interfaccia Web sicura per temperatura ed umidità di un DHT
Codice ed esempio su repository GitHub