Web Server with ESP32: byte array, gzipped pages and SPIFFS

Web Server with esp8266 and esp32: byte array, gzipped pages and SPIFFS - 2
Here a little tutorial to learn how to manage a complete web server via esp8266 esp32 or other arduino like device.When we speak about web server we know that the basic is serving a web page, in a micro-controller we have some solution to do that.
GitHub - xreef/WebServer-Esp8266-ESP32-Tutorial: Some examples from my little tutorial to learn how to manage a complete web server via esp8266 esp32 or other arduino like device.
Some examples from my little tutorial to learn how to manage a complete web server via esp8266 esp32 or other arduino like device. - GitHub - xreef/WebServer-Esp8266-ESP32-Tutorial: Some examples f...
Online convert archive, create new archive GZIP
Free online convert any archive or create new archive in GZIP format
Online converter: File to (cpp) gzip byte array
Online converter: File to (cpp) gzip byte array.Utility usefully to generate bytearray to use with Arduino, esp8266 or esp32

Write html code directly on the sketch is tedious, and you cannot create complex page without a lot of problem. To make it easier, people convert pages to byte arrays, this is a better solution, and you can you use gzipped page to save space. Of course to create a gzipped array you have to use a converter.

Stream a file from SPIFFS

But start with simple example, an index.html page to handle as default root. So here a simple index.html page

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo index page</title>
</head>
<body style="background-color: azure">
    <h1 style="text-align: center">Here the demo page</h1>
    <div style="text-align: justify">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh.
    </div>
</body>
</html>

We are going to create a data folder on project create an index.html file and put it inside the SPIFFS. The code is for ESP32

#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>
 
const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";
 
WebServer server(80);
 
void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
 
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
 
  server.send(404, "text/plain", message);
}
 
bool loadFromSPIFFS(String path) {
  String dataType = "text/html";
 
  Serial.print("Requested page -> ");
  Serial.println(path);
  if (SPIFFS.exists(path)){
      File dataFile = SPIFFS.open(path, "r");
      if (!dataFile) {
          handleNotFound();
          return false;
      }
 
      if (server.streamFile(dataFile, dataType) != dataFile.size()) {
        Serial.println("Sent less data than expected!");
      }else{
          Serial.println("Page served!");
      }
 
      dataFile.close();
  }else{
      handleNotFound();
      return false;
  }
  return true;
}
 
 
void handleRoot() {
    loadFromSPIFFS("/index.html");
}
 
void setup(void) {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  Serial.print(F("Inizializing FS..."));
  if (SPIFFS.begin()){
    Serial.println(F("done."));
  }else{
    Serial.println(F("fail."));
  }
 
  server.on("/", handleRoot);
 
  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });
 
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
}
 
void loop(void) {
  server.handleClient();
}

As you can see the code is very simple, and most of the work is made by server.streamFile command that It want for first parameter the file and for the second the mime type. The core is this code

bool loadFromSPIFFS(String path) {
  String dataType = "text/html";
 
  Serial.print("Requested page -> ");
  Serial.println(path);
  if (SPIFFS.exists(path)){
      File dataFile = SPIFFS.open(path, "r");
      if (!dataFile) {
          handleNotFound();
          return false;
      }
 
      if (server.streamFile(dataFile, dataType) != dataFile.size()) {
        Serial.println("Sent less data than expected!");
      }else{
          Serial.println("Page served!");
      }
 
      dataFile.close();
  }else{
      handleNotFound();
      return false;
  }
  return true;
}

This function get the file from SPIFFS and stream the content with the function server.streamFile(dataFile, dataType), where dataFile is the content and dataType is the mime type.

AsyncWebServer

Now the same sketch with the very popular library AsyncWebServer, exist a version for esp8266 and esp32, you must only change the base library. For ESP8266 it requires ESPAsyncTCP To use this library you might need to have the latest git versions of ESP8266 Arduino Core. For ESP32 it requires AsyncTCP to work To use this library you might need to have the latest git versions of ESP32 Arduino Core

This is fully asynchronous server and as such does not run on the loop thread. Here the previous example for esp32 with this library.

/*
 *  ESP32
 *  Simple web server
 *  an html page stored on SPIFFS
 *  stream on browser
 *
 *  by Mischianti Renzo <https://www.mischianti.org>
 *
 *  https://www.mischianti.org/
 *
 */
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
 
const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";
 
AsyncWebServer server(80);
 
void handleNotFound(AsyncWebServerRequest *request) {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += request->url();
  message += "\nMethod: ";
  message += (request->method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += request->args();
  message += "\n";
 
  for (uint8_t i = 0; i < request->args(); i++) {
    message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
  }
 
  request->send(404, "text/plain", message);
}
 
bool loadFromSPIFFS(AsyncWebServerRequest *request, String path) {
  String dataType = "text/html";
 
  Serial.print("Requested page -> ");
  Serial.println(path);
  if (SPIFFS.exists(path)){
      File dataFile = SPIFFS.open(path, "r");
      if (!dataFile) {
          handleNotFound(request);
          return false;
      }
 
        AsyncWebServerResponse *response = request->beginResponse(SPIFFS, path, dataType);
        Serial.print("Real file path: ");
        Serial.println(path);
 
        request->send(response);
 
 
      dataFile.close();
  }else{
      handleNotFound(request);
      return false;
  }
  return true;
}
 
 
void handleRoot(AsyncWebServerRequest *request) {
    loadFromSPIFFS(request, "/index.html");
}
 
void setup(void) {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  Serial.print(F("Inizializing FS..."));
  if (SPIFFS.begin()){
    Serial.println(F("done."));
  }else{
    Serial.println(F("fail."));
  }
 
  server.on("/", handleRoot);
 
  server.on("/inline", [](AsyncWebServerRequest *request) {
      request->send(200, "text/plain", "this works as well");
  });
 
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
}
 
void loop(void) {
 

Using a file to array converter

If you don’t want to use the SPIFFS file system, you can use a simple utility to create a compact array from file. I add that little program, used in esp32-cam for example.

// Filename web_index.h
// File stored is index.html, Size: 1187
#define index_html_len 1187
const uint8_t index_html[] PROGMEM = {
 0x3C, 0x21, 0x44, 0x4F, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x68, 0x74, 0x6D, 0x6C, 0x3E, 0x0D,
 0x0A, 0x3C, 0x68, 0x74, 0x6D, 0x6C, 0x20, 0x6C, 0x61, 0x6E, 0x67, 0x3D, 0x22, 0x65, 0x6E, 0x22,
 0x3E, 0x0D, 0x0A, 0x3C, 0x68, 0x65, 0x61, 0x64, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C,
 0x6D, 0x65, 0x74, 0x61, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3D, 0x22, 0x55, 0x54,
 0x46, 0x2D, 0x38, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x74, 0x69, 0x74, 0x6C,
 0x65, 0x3E, 0x44, 0x65, 0x6D, 0x6F, 0x20, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x20, 0x70, 0x61, 0x67,
 0x65, 0x3C, 0x2F, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x3E, 0x0D, 0x0A, 0x3C, 0x2F, 0x68, 0x65, 0x61,
 0x64, 0x3E, 0x0D, 0x0A, 0x3C, 0x62, 0x6F, 0x64, 0x79, 0x20, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x3D,
 0x22, 0x62, 0x61, 0x63, 0x6B, 0x67, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x2D, 0x63, 0x6F, 0x6C, 0x6F,
 0x72, 0x3A, 0x20, 0x61, 0x7A, 0x75, 0x72, 0x65, 0x22, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20,
 0x3C, 0x68, 0x31, 0x20, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x3D, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2D,
 0x61, 0x6C, 0x69, 0x67, 0x6E, 0x3A, 0x20, 0x63, 0x65, 0x6E, 0x74, 0x65, 0x72, 0x22, 0x3E, 0x48,
 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x6D, 0x6F, 0x20, 0x70, 0x61, 0x67,
 0x65, 0x3C, 0x2F, 0x68, 0x31, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x68, 0x32, 0x20,
 0x73, 0x74, 0x79, 0x6C, 0x65, 0x3D, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2D, 0x61, 0x6C, 0x69, 0x67,
 0x6E, 0x3A, 0x20, 0x63, 0x65, 0x6E, 0x74, 0x65, 0x72, 0x22, 0x3E, 0x77, 0x77, 0x77, 0x2E, 0x6D,
 0x69, 0x73, 0x63, 0x68, 0x69, 0x61, 0x6E, 0x74, 0x69, 0x2E, 0x6F, 0x72, 0x67, 0x3C, 0x2F, 0x68,
 0x32, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x64, 0x69, 0x76, 0x20, 0x73, 0x74, 0x79,
 0x6C, 0x65, 0x3D, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2D, 0x61, 0x6C, 0x69, 0x67, 0x6E, 0x3A, 0x20,
 0x6A, 0x75, 0x73, 0x74, 0x69, 0x66, 0x79, 0x22, 0x3E, 0x0D, 0x0A, 0x0D, 0x0A, 0x20, 0x20, 0x20,
 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76, 0x3E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x4C, 0x6F, 0x72,
 0x65, 0x6D, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6D, 0x20, 0x64, 0x6F, 0x6C, 0x6F, 0x72, 0x20, 0x73,
 0x69, 0x74, 0x20, 0x61, 0x6D, 0x65, 0x74, 0x2C, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x63, 0x74,
 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6E, 0x67, 0x20,
 0x65, 0x6C, 0x69, 0x74, 0x2E, 0x20, 0x49, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x20, 0x6E, 0x65,
 0x63, 0x20, 0x6F, 0x64, 0x69, 0x6F, 0x2E, 0x20, 0x50, 0x72, 0x61, 0x65, 0x73, 0x65, 0x6E, 0x74,
 0x20, 0x6C, 0x69, 0x62, 0x65, 0x72, 0x6F, 0x2E, 0x20, 0x53, 0x65, 0x64, 0x20, 0x63, 0x75, 0x72,
 0x73, 0x75, 0x73, 0x20, 0x61, 0x6E, 0x74, 0x65, 0x20, 0x64, 0x61, 0x70, 0x69, 0x62, 0x75, 0x73,
 0x20, 0x64, 0x69, 0x61, 0x6D, 0x2E, 0x20, 0x53, 0x65, 0x64, 0x20, 0x6E, 0x69, 0x73, 0x69, 0x2E,
 0x20, 0x4E, 0x75, 0x6C, 0x6C, 0x61, 0x20, 0x71, 0x75, 0x69, 0x73, 0x20, 0x73, 0x65, 0x6D, 0x20,
 0x61, 0x74, 0x20, 0x6E, 0x69, 0x62, 0x68, 0x20, 0x65, 0x6C, 0x65, 0x6D, 0x65, 0x6E, 0x74, 0x75,
 0x6D, 0x20, 0x69, 0x6D, 0x70, 0x65, 0x72, 0x64, 0x69, 0x65, 0x74, 0x2E, 0x20, 0x44, 0x75, 0x69,
 0x73, 0x20, 0x73, 0x61, 0x67, 0x69, 0x74, 0x74, 0x69, 0x73, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6D,
 0x2E, 0x20, 0x50, 0x72, 0x61, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x20, 0x6D, 0x61, 0x75, 0x72, 0x69,
 0x73, 0x2E, 0x20, 0x46, 0x75, 0x73, 0x63, 0x65, 0x20, 0x6E, 0x65, 0x63, 0x20, 0x74, 0x65, 0x6C,
 0x6C, 0x75, 0x73, 0x20, 0x73, 0x65, 0x64, 0x20, 0x61, 0x75, 0x67, 0x75, 0x65, 0x20, 0x73, 0x65,
 0x6D, 0x70, 0x65, 0x72, 0x20, 0x70, 0x6F, 0x72, 0x74, 0x61, 0x2E, 0x20, 0x4D, 0x61, 0x75, 0x72,
 0x69, 0x73, 0x20, 0x6D, 0x61, 0x73, 0x73, 0x61, 0x2E, 0x20, 0x56, 0x65, 0x73, 0x74, 0x69, 0x62,
 0x75, 0x6C, 0x75, 0x6D, 0x20, 0x6C, 0x61, 0x63, 0x69, 0x6E, 0x69, 0x61, 0x20, 0x61, 0x72, 0x63,
 0x75, 0x20, 0x65, 0x67, 0x65, 0x74, 0x20, 0x6E, 0x75, 0x6C, 0x6C, 0x61, 0x2E, 0x20, 0x43, 0x6C,
 0x61, 0x73, 0x73, 0x20, 0x61, 0x70, 0x74, 0x65, 0x6E, 0x74, 0x20, 0x74, 0x61, 0x63, 0x69, 0x74,
 0x69, 0x20, 0x73, 0x6F, 0x63, 0x69, 0x6F, 0x73, 0x71, 0x75, 0x20, 0x61, 0x64, 0x20, 0x6C, 0x69,
 0x74, 0x6F, 0x72, 0x61, 0x20, 0x74, 0x6F, 0x72, 0x71, 0x75, 0x65, 0x6E, 0x74, 0x20, 0x70, 0x65,
 0x72, 0x20, 0x63, 0x6F, 0x6E, 0x75, 0x62, 0x69, 0x61, 0x20, 0x6E, 0x6F, 0x73, 0x74, 0x72, 0x61,
 0x2C, 0x20, 0x70, 0x65, 0x72, 0x20, 0x69, 0x6E, 0x63, 0x65, 0x70, 0x74, 0x6F, 0x73, 0x20, 0x68,
 0x69, 0x6D, 0x65, 0x6E, 0x61, 0x65, 0x6F, 0x73, 0x2E, 0x20, 0x43, 0x75, 0x72, 0x61, 0x62, 0x69,
 0x74, 0x75, 0x72, 0x20, 0x73, 0x6F, 0x64, 0x61, 0x6C, 0x65, 0x73, 0x20, 0x6C, 0x69, 0x67, 0x75,
 0x6C, 0x61, 0x20, 0x69, 0x6E, 0x20, 0x6C, 0x69, 0x62, 0x65, 0x72, 0x6F, 0x2E, 0x20, 0x53, 0x65,
 0x64, 0x20, 0x64, 0x69, 0x67, 0x6E, 0x69, 0x73, 0x73, 0x69, 0x6D, 0x20, 0x6C, 0x61, 0x63, 0x69,
 0x6E, 0x69, 0x61, 0x20, 0x6E, 0x75, 0x6E, 0x63, 0x2E, 0x20, 0x43, 0x75, 0x72, 0x61, 0x62, 0x69,
 0x74, 0x75, 0x72, 0x20, 0x74, 0x6F, 0x72, 0x74, 0x6F, 0x72, 0x2E, 0x20, 0x50, 0x65, 0x6C, 0x6C,
 0x65, 0x6E, 0x74, 0x65, 0x73, 0x71, 0x75, 0x65, 0x20, 0x6E, 0x69, 0x62, 0x68, 0x2E, 0x20, 0x41,
 0x65, 0x6E, 0x65, 0x61, 0x6E, 0x20, 0x71, 0x75, 0x61, 0x6D, 0x2E, 0x20, 0x49, 0x6E, 0x20, 0x73,
 0x63, 0x65, 0x6C, 0x65, 0x72, 0x69, 0x73, 0x71, 0x75, 0x65, 0x20, 0x73, 0x65, 0x6D, 0x20, 0x61,
 0x74, 0x20, 0x64, 0x6F, 0x6C, 0x6F, 0x72, 0x2E, 0x20, 0x4D, 0x61, 0x65, 0x63, 0x65, 0x6E, 0x61,
 0x73, 0x20, 0x6D, 0x61, 0x74, 0x74, 0x69, 0x73, 0x2E, 0x20, 0x53, 0x65, 0x64, 0x20, 0x63, 0x6F,
 0x6E, 0x76, 0x61, 0x6C, 0x6C, 0x69, 0x73, 0x20, 0x74, 0x72, 0x69, 0x73, 0x74, 0x69, 0x71, 0x75,
 0x65, 0x20, 0x73, 0x65, 0x6D, 0x2E, 0x20, 0x50, 0x72, 0x6F, 0x69, 0x6E, 0x20, 0x75, 0x74, 0x20,
 0x6C, 0x69, 0x67, 0x75, 0x6C, 0x61, 0x20, 0x76, 0x65, 0x6C, 0x20, 0x6E, 0x75, 0x6E, 0x63, 0x20,
 0x65, 0x67, 0x65, 0x73, 0x74, 0x61, 0x73, 0x20, 0x70, 0x6F, 0x72, 0x74, 0x74, 0x69, 0x74, 0x6F,
 0x72, 0x2E, 0x20, 0x4D, 0x6F, 0x72, 0x62, 0x69, 0x20, 0x6C, 0x65, 0x63, 0x74, 0x75, 0x73, 0x20,
 0x72, 0x69, 0x73, 0x75, 0x73, 0x2C, 0x20, 0x69, 0x61, 0x63, 0x75, 0x6C, 0x69, 0x73, 0x20, 0x76,
 0x65, 0x6C, 0x2C, 0x20, 0x73, 0x75, 0x73, 0x63, 0x69, 0x70, 0x69, 0x74, 0x20, 0x71, 0x75, 0x69,
 0x73, 0x2C, 0x20, 0x6C, 0x75, 0x63, 0x74, 0x75, 0x73, 0x20, 0x6E, 0x6F, 0x6E, 0x2C, 0x20, 0x6D,
 0x61, 0x73, 0x73, 0x61, 0x2E, 0x20, 0x46, 0x75, 0x73, 0x63, 0x65, 0x20, 0x61, 0x63, 0x20, 0x74,
 0x75, 0x72, 0x70, 0x69, 0x73, 0x20, 0x71, 0x75, 0x69, 0x73, 0x20, 0x6C, 0x69, 0x67, 0x75, 0x6C,
 0x61, 0x20, 0x6C, 0x61, 0x63, 0x69, 0x6E, 0x69, 0x61, 0x20, 0x61, 0x6C, 0x69, 0x71, 0x75, 0x65,
 0x74, 0x2E, 0x20, 0x4D, 0x61, 0x75, 0x72, 0x69, 0x73, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6D, 0x2E,
 0x20, 0x4E, 0x75, 0x6C, 0x6C, 0x61, 0x20, 0x6D, 0x65, 0x74, 0x75, 0x73, 0x20, 0x6D, 0x65, 0x74,
 0x75, 0x73, 0x2C, 0x20, 0x75, 0x6C, 0x6C, 0x61, 0x6D, 0x63, 0x6F, 0x72, 0x70, 0x65, 0x72, 0x20,
 0x76, 0x65, 0x6C, 0x2C, 0x20, 0x74, 0x69, 0x6E, 0x63, 0x69, 0x64, 0x75, 0x6E, 0x74, 0x20, 0x73,
 0x65, 0x64, 0x2C, 0x20, 0x65, 0x75, 0x69, 0x73, 0x6D, 0x6F, 0x64, 0x20, 0x69, 0x6E, 0x2C, 0x20,
 0x6E, 0x69, 0x62, 0x68, 0x2E, 0x0D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x2F, 0x64, 0x69, 0x76,
 0x3E, 0x0D, 0x0A, 0x3C, 0x2F, 0x62, 0x6F, 0x64, 0x79, 0x3E, 0x0D, 0x0A, 0x3C, 0x2F, 0x68, 0x74,
 0x6D, 0x6C, 0x3E
};

I wrote a simple html script to simplify the process, upload and copy the result inside the web_index.h file.

Filearray converter

Select file



Generated filearray

And now you can stream the file like so.

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>

const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";
 
AsyncWebServer server(80);
 
void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
 
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
 
  server.send(404, "text/plain", message);
}
 
void handleRoot() {
    String dataType = "text/html";
 
    Serial.println("Stream the array!");
    server.send(200, dataType, (const char*)index_html);
}
 
void setup(void) {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  server.on("/", handleRoot);
 
  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });
 
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
}
 
void loop(void) {
  server.handleClient();
}

It’s work, and you can think that It isn’t so pretty solution, and It’s better to write html page directly like a string, but next we understand why this solution It’s a better solution.

Gzipped page

But a file like an html page or a JavaScript library can be very large, and in modern web servers or application servers it is possible to enable gzip compression transparently, but the esp8266 and esp32 are not powerful enough to perform the compression in real time. But we can gzip the file and specify the correct MIME type for the browser, and you’ll get better performance and lower throughput.

For test you can use a simple online compressor to obtain and index.html.gz. Now we upload the file on SPIFFS. And you must change only the handle root.

void handleRoot() {
    loadFromSPIFFS("/index.html.gz");
}

The behaivor not change, but instead of transfer a file of 1,15Kb you transfer only 615byte about half size, but this difference grow when you have a bigger size file, in my ABB Aurora inverter centraline (WIC) the size of js from 2,15Mb become 610kb, a big difference. You can use this gzipped solution for al type of file, you must only set correct mime type.

Gzipped to array

At same manner you can transform the gzipped index page in an array. Or you can use directly my file to byte array converter that give you the standard version and the gzipped version "Online converter: File to (cpp) gzip byte array". Replace the contents of the web_index.h file and in the sketch above change the name of the array.

void handleRoot() {
    const char* dataType = "text/html";
 
    Serial.println("Stream the array!");
     
    server.sendHeader(F("Content-Encoding"), F("gzip"));
    server.send_P(200, dataType, (const char*)index_html_gz, index_html_gz_len);
}

It works great and the size of file is reduced.

//File: index.html.gz, Size: 674
#define index_html_gz_len 674
const uint8_t index_html_gz[] PROGMEM = {
 0x1F, 0x8B, 0x08, 0x08, 0xEB, 0xAF, 0x30, 0x5E, 0x00, 0x03, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E,
 0x68, 0x74, 0x6D, 0x6C, 0x00, 0x7D, 0x54, 0x5D, 0x6F, 0xD3, 0x40, 0x10, 0x7C, 0x47, 0xE2, 0x3F,
 0x2C, 0x79, 0x76, 0x1D, 0xD1, 0x27, 0x84, 0xD2, 0x4A, 0xA8, 0x05, 0x51, 0x89, 0x8F, 0x4A, 0x14,
 0x24, 0x1E, 0xD7, 0x77, 0x8B, 0xB3, 0x70, 0xBE, 0x4B, 0xEF, 0xF6, 0xFA, 0xC1, 0xAF, 0x67, 0xCE,
 0x4E, 0x4A, 0x2B, 0x21, 0xA2, 0xC8, 0x8E, 0xD7, 0xBB, 0xB3, 0xB3, 0x33, 0x9B, 0xDB, 0xBC, 0x38,
 0xFF, 0x7C, 0x76, 0xF5, 0xFD, 0xF2, 0x2D, 0x6D, 0x6D, 0x0A, 0xA7, 0xCF, 0x9F, 0x6D, 0xDA, 0x9D,
 0x02, 0xC7, 0xF1, 0x64, 0x25, 0x71, 0x35, 0x47, 0x84, 0x3D, 0xEE, 0x84, 0xCF, 0x66, 0x12, 0x63,
 0x72, 0x5B, 0xCE, 0x45, 0xEC, 0x64, 0xF5, 0xF5, 0xEA, 0xDD, 0xD1, 0xAB, 0xD5, 0xE1, 0x9D, 0xA9,
 0x05, 0x39, 0x3D, 0x97, 0x29, 0x91, 0x46, 0x2F, 0x77, 0xB4, 0xE3, 0x51, 0x36, 0xEB, 0x25, 0x0C,
 0x9C, 0xF5, 0x1E, 0x68, 0x33, 0x24, 0x7F, 0x4F, 0xC5, 0xEE, 0x83, 0x9C, 0xAC, 0x06, 0x76, 0xBF,
 0xC6, 0x9C, 0x6A, 0xF4, 0x47, 0x2E, 0x85, 0x94, 0x5F, 0x13, 0xFF, 0xAE, 0x59, 0x1E, 0x40, 0xB7,
 0x2F, 0x0F, 0x99, 0x26, 0x77, 0x76, 0xC4, 0x41, 0xC7, 0xF8, 0x9A, 0x9C, 0x44, 0x93, 0xBC, 0x3A,
 0x7D, 0x2F, 0x59, 0xC8, 0xB6, 0x42, 0xBE, 0x75, 0x5D, 0xFA, 0x6D, 0x5F, 0x3E, 0xD4, 0x1E, 0xFF,
 0xAF, 0xF6, 0xF6, 0xF6, 0xB6, 0x9F, 0xB4, 0xB8, 0xAD, 0x72, 0x34, 0xED, 0x53, 0x1E, 0x51, 0x7B,
 0x7C, 0xA8, 0xF5, 0x7A, 0xF3, 0xAF, 0xE2, 0x9F, 0xB5, 0x98, 0xFE, 0xB8, 0x6F, 0xF4, 0xF6, 0x89,
 0x6B, 0x64, 0xEE, 0x8B, 0x3E, 0xA4, 0x2C, 0x13, 0xE9, 0xAE, 0xD4, 0x89, 0x7C, 0x1B, 0x86, 0x8A,
 0x1A, 0x31, 0x34, 0xEB, 0xC8, 0xA5, 0x58, 0xC4, 0x99, 0x58, 0xCD, 0xC4, 0x5E, 0x77, 0x68, 0xAC,
 0x71, 0x24, 0x09, 0x6A, 0x3D, 0x5D, 0x80, 0xD1, 0x28, 0x99, 0xA2, 0x38, 0x4A, 0x5E, 0x53, 0x4F,
 0x97, 0x99, 0xA5, 0x80, 0x28, 0x05, 0x1D, 0x24, 0x23, 0xF0, 0x45, 0x3C, 0xB9, 0x9A, 0x4B, 0x2D,
 0x04, 0xBA, 0x18, 0x98, 0x77, 0x3A, 0xE0, 0xC1, 0x2B, 0x4F, 0xCB, 0xDB, 0xA8, 0x45, 0x7B, 0xFA,
 0x54, 0x43, 0x60, 0xBA, 0xAE, 0x5A, 0xA8, 0x80, 0x0C, 0x1B, 0xE2, 0xC3, 0x16, 0x7D, 0x64, 0x02,
 0x1C, 0x88, 0xE9, 0xB4, 0x93, 0xEC, 0x55, 0xD0, 0xF6, 0x7C, 0xCE, 0xE2, 0x51, 0xCD, 0xF0, 0x63,
 0xE6, 0xFD, 0xA8, 0xF3, 0xC4, 0x35, 0x6B, 0xE9, 0xE9, 0x5D, 0x2D, 0x4E, 0x66, 0x6A, 0x26, 0x21,
 0xD4, 0x86, 0xEB, 0x89, 0xEB, 0x58, 0xA5, 0x75, 0x00, 0x18, 0xED, 0x52, 0x36, 0xEE, 0xE9, 0xE3,
 0x5C, 0x80, 0xBA, 0x52, 0xF0, 0xF4, 0x4D, 0xA0, 0xD4, 0x50, 0x03, 0x5A, 0x06, 0xC6, 0xAC, 0xCA,
 0xC4, 0xD9, 0x55, 0xC2, 0xA0, 0xE0, 0xD4, 0x58, 0xF6, 0x74, 0x16, 0x90, 0x4B, 0xBC, 0xB3, 0xD6,
 0xCF, 0x90, 0x65, 0x4A, 0x25, 0x39, 0x4D, 0xE5, 0xBA, 0x42, 0x25, 0x0C, 0x6F, 0x29, 0x33, 0xE1,
 0x72, 0x5D, 0x5B, 0x4A, 0x6B, 0x06, 0x21, 0xEB, 0x00, 0xB0, 0x98, 0x8A, 0x65, 0xEE, 0xE6, 0x98,
 0x46, 0x27, 0x3B, 0x4B, 0x85, 0xB6, 0x8A, 0x29, 0x59, 0x12, 0x68, 0x9F, 0xD5, 0xCC, 0x83, 0x36,
 0xB9, 0x4B, 0xF2, 0x1C, 0xA4, 0x00, 0x6D, 0xAC, 0xD0, 0x46, 0xE3, 0x13, 0x51, 0x3D, 0x7C, 0xD5,
 0x52, 0xF4, 0x2F, 0xCD, 0x58, 0xA3, 0x7B, 0x5C, 0x8F, 0xF6, 0xF8, 0x42, 0x19, 0x8C, 0xDF, 0x76,
 0x07, 0xE4, 0x64, 0x56, 0xB5, 0xA7, 0x37, 0x12, 0x85, 0x23, 0xE4, 0x6E, 0x26, 0x5C, 0x44, 0x82,
 0x52, 0x41, 0x20, 0xC2, 0xF5, 0x22, 0x4E, 0x93, 0x7F, 0x5E, 0x84, 0x26, 0x8E, 0x60, 0xF1, 0xB8,
 0xC9, 0xD3, 0xD4, 0xDE, 0x1B, 0x9A, 0xE2, 0x0D, 0x87, 0x00, 0xD1, 0x0C, 0x45, 0xA6, 0xFB, 0xB2,
 0x66, 0x42, 0x02, 0xCD, 0x6A, 0x07, 0xCE, 0x37, 0x12, 0x66, 0x5A, 0x4D, 0xBD, 0x62, 0x40, 0x69,
 0x92, 0x9B, 0xCE, 0xB4, 0x3E, 0xA6, 0x3C, 0x28, 0x05, 0x2C, 0x17, 0xBC, 0x01, 0x4C, 0x2D, 0x1D,
 0x29, 0xBB, 0xDA, 0x60, 0x51, 0xD7, 0x11, 0x22, 0x0E, 0x0B, 0x67, 0xF3, 0x56, 0x74, 0x14, 0xEA,
 0x9C, 0x19, 0x53, 0xEC, 0x0E, 0x5E, 0x2D, 0x16, 0x33, 0x1C, 0xAE, 0x19, 0x9B, 0xB9, 0xAC, 0xCF,
 0xBE, 0xF5, 0x83, 0x79, 0xA1, 0xD1, 0xB3, 0x07, 0x9B, 0xF7, 0xEB, 0xB2, 0xEC, 0x1B, 0x56, 0x1C,
 0x90, 0xF3, 0xB5, 0xA3, 0x16, 0x99, 0x5C, 0xCA, 0xCD, 0x99, 0x99, 0x80, 0xC1, 0x1F, 0xF5, 0x15,
 0x06, 0x62, 0x75, 0x3A, 0x12, 0xA0, 0x4F, 0xC9, 0xC3, 0x88, 0x6E, 0x91, 0xF1, 0xE9, 0x3F, 0x69,
 0xB3, 0x6E, 0xE7, 0xC3, 0x72, 0x60, 0xB4, 0x33, 0xE9, 0x0F, 0xCC, 0x5A, 0xC7, 0xBB, 0xA3, 0x04,
 0x00, 0x00
};

As you can understand the array solution can be used in many cases, I discovered it and used it to modify the “WebCameraServer” example to add the flash functionality on the esp32-cam.

AsyncWebServer

Here the AsyncWebServer implementation with gzip compression.

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "web_index.h"
 
const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";
 
AsyncWebServer server(80);
 
void handleNotFound(AsyncWebServerRequest *request) {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += request->url();
  message += "\nMethod: ";
  message += (request->method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += request->args();
  message += "\n";
 
  for (uint8_t i = 0; i < request->args(); i++) {
    message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
  }
 
  request->send(404, "text/plain", message);
}
 
void handleRoot(AsyncWebServerRequest *request) {
    const char* dataType = "text/html";
 
    Serial.println("Stream the array!");
 
    AsyncWebServerResponse *response = request->beginResponse_P(200, dataType,index_html_gz, index_html_gz_len);
    response->addHeader("Content-Encoding", "gzip");
    request->send(response);
}
 
void setup(void) {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  server.on("/", handleRoot);
 
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
}
 
void loop(void) {
 
}

GET an image inside a page

Inside a page you can have images, but images are also simple URLs served with the specific MIME type, see examples

Here the page index.html to put in data folder.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo index page</title>
</head>
<body style="background-color: azure;text-align: center">
    <h1 style="text-align: center">Here the demo page</h1>
    <img style="text-align: center" src="logo256.jpg" width="128"/>
    <h2 style="text-align: center">www.mischianti.org</h2>
    <div style="text-align: justify">
 
    </div>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh.
    </div>
</body>
</html>

Here the sketch

/*
 *  Simple web server that read from SPIFFS and
 *  stream on browser (page and image)
 *  
 */
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
 
const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";
 
ESPAsyncWebServer server(80);
 
void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
 
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
 
  server.send(404, "text/plain", message);
}
 
bool loadFromSPIFFS(String path, String dataType) {
  Serial.print("Requested page -> ");
  Serial.println(path);
  if (SPIFFS.exists(path)){
      File dataFile = SPIFFS.open(path, "r");
      if (!dataFile) {
          handleNotFound();
          return false;
      }
 
      if (server.streamFile(dataFile, dataType) != dataFile.size()) {
        Serial.println("Sent less data than expected!");
      }else{
          Serial.println("Page served!");
      }
 
      dataFile.close();
  }else{
      handleNotFound();
      return false;
  }
  return true;
}
 
 
void handleRoot() {
    loadFromSPIFFS("/index.html", "text/html");
}
void handleLogo() {
    loadFromSPIFFS("/logo256.jpg", "image/jpeg");
}
 
void setup(void) {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  Serial.print(F("Inizializing FS..."));
  if (SPIFFS.begin()){
    Serial.println(F("done."));
  }else{
    Serial.println(F("fail."));
  }
 
  server.on("/", handleRoot);
  server.on("/logo256.jpg", handleLogo);
 
  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });
 
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
}
 
void loop(void) {
  server.handleClient();

You entered the link to the image in the html page with this tag.

<img style="text-align: center" src="logo256.jpg" width="128"/>

This mean that when the page are rendered from the browser It try to get the image with relative url, so It try to get http://192.168.1.146/logo256.jpg. You will need to instruct the microcontroller on how to handle this request.

server.on("/logo256.jpg", handleLogo);
[...]
void handleLogo() {
    loadFromSPIFFS("/logo256.jpg", "image/jpeg");
}