Web Server with esp8266 and esp32: byte array, gzipped pages and SPIFFS – 2

Spread the love

Writing HTML code directly on the sketch is tedious, and you cannot create a complex page without a lot of problems.

To make it easier, people convert pages to byte arrays; this is a better solution, and you can use gzipped page to save space.

WebServer Esp8266 ESP32 byte array gzipped pages and SPIFFS
WebServer Esp8266 ESP32 byte array gzipped pages and SPIFFS

Of course, to create a gzipped array, you have to use a converter.

Stream a file from SPIFFS

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

First, I advise you to read the article “WeMos D1 mini (esp8266), integrated SPIFFS Filesystem” for esp8266 or “ESP32: integrated SPIFFS FileSystem” for ESP32; here, you will be explained how to use the SPIFFS filesystem.

So here is 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>
    <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>

We are going to create a data folder on the project, create an index.html file and put It inside the SPIFFS (read the article posted before on how to put the file to SPIFFS).

For esp32, you must only change these lines

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
[...]
ESP8266WebServer server(80);

to

#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>
[...]
WebServer server(80);

Now we can specify to the device to read that page.

/*
 *  WeMos D1 mini (esp8266)
 *  Simple web server an html page stored on SPIFFS
 *  stream on browser
 *  
 *  by Mischianti Renzo <https://mischianti.org>
 *
 *  https://mischianti.org/
 *
 */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>

const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";

ESP8266WebServer 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 the server.streamFile is the command that It wants for the 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 gets the file from SPIFFS and streams the content with the function server.streamFile(dataFile, dataType), where dataFile is the content and dataType is the mime type.

Here the demo page

www.mischianti.org

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.

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, and 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 a fully asynchronous server and, as such, does not run on the loop thread.

Here is 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://mischianti.org>
 *
 *  https://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 a file, and I add that little program used in esp32-cam, for example,

filetoarray "index.html" > web_index.h

to a repository (I add the exe file that in the original repository isn’t present), and with this program, the file becomes.

// 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

/*
 *  WeMos D1 mini (esp8266)
 *  Simple web server an html page in array format
 *  stream on browser
 *  
 *  by Mischianti Renzo <https://mischianti.org>
 *
 *  https://mischianti.org/
 *
 */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "web_index.h"

const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";

ESP8266WebServer 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 such a pretty solution, and It’s better to write an 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 huge. 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 a 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 behavior does not change, but instead of transferring a file of 1,15Kb, you transfer only 615byte, about half size, but this difference grows when you have a bigger size file. In my ABB Aurora inverter centraline (WIC), js’ size from 2,15Mb becomes 610kb, a big difference.

You can use this gzipped solution for all types of files, and you must only set the correct mime type.

Gzipped to array

In the same manner, you can transform the gzipped index page into an array

filetoarray "index.html.gz" > web_index.h

Or you can use my file directly to the byte array converter that gives 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(200, dataType, (const char*)index_html_gz, index_html_gz_len);
}

for the esp32, you must change the send method in send_P.

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 the 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 is 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>

And here is the image to put in the same folder as given

Here the sketch

/*
 *  WeMos D1 mini (esp8266)
 *  Simple web server that read from SPIFFS and
 *  stream on browser (page and image)
 *  
 *  by Mischianti Renzo <https://mischianti.org>
 *
 *  https://mischianti.org/
 *
 */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>

const char* ssid = "<your-ssid>";
const char* password = "<your-passwd>";

ESP8266WebServer 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 means that when the page is rendered from the browser, It tries to get the image with a relative URL, so It tries 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");
}

Here the result

Here the demo page

www.mischianti.org

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.

Thanks

This is fun, but as you can see, you don’t have any interaction with the page; it’s just static content that’s pretty much useless. You can start seeing how to get dynamic with RESTFull services by looking at this article “How to create a REST server on esp8266 or esp32“.

However, in the following articles, we will create a complete Back-end Web Server.

  1. Web Server with esp8266 and esp32: serve pages and manage LEDs
  2. Web Server with esp8266 and esp32: byte array gzipped pages and SPIFFS
  3. Web Server with esp8266 and esp32: multi purpose generic web server
  4. Web Server with esp8266 and esp32: manage security and authentication
  5. Web Server with esp8266 and esp32: add secure REST back-end
  6. Web Server with esp8266 and esp32: DHT temperature humidity on protected Web Interface

Code and examples on this repository GitHub


Spread the love

8 Responses

  1. oleg says:

    Very interesting, thank you

  2. Francis R says:

    very interesting and clear, I already follow several of your tutos with great interest
    I need to replace some variable in the stored HTML page, I think it become impossible as long the page is now Gzipped..right ? is there any way to fix this ? Imean Gzip but change some variable on the fly

  3. john young says:

    Thank you very much for your clear explanation. I wonder how a real server handle this problem like appache?(it is not possible use code to handle different page).
    So how about write the website first (html, image, and include css and javascript in html file), then convert it to arrays, and then gzip it , then automatically generate the whole project? (user can choose which server used),then user can edit this automatically generated project to add their logic function.

  4. Hi John,
    Apache, Nginx, or others don’t need that you gzip the code because they manage this operation directly with this mod

    LoadModule deflate_module modules/mod_deflate.so

    but a microcontroller like esp32 or esp8266 has very low power to do this operation in real time, so we must compress them.

    I also wrote an example that processes all pages stored in the microcontroller (like a real server), you can find It here

    Web server with esp8266 and esp32: multi purpose generic web server – 3

    But for a complex application, I use node that automatizes the generation of the site.
    Here is an application example

    EByte LoRa E32 Web Manager: description, configure and demo (esp8266, esp32) – 2

    Bye Renzo

  5. Yuri says:

    Please keep in mind, when you create additional handlers, each one consumes very valuable RAM.
    What if your page contains multiple images, css, javascript(s)?
    It would be a bad approach to create a handle for everyone. For this you can use serveStatic method which provides at least asyncServer.
    And there is no need to convert html code to array to place it into PROGMEM. You can just use some literal declaration like this:
    static PGM_P PROGMEM _DONE_HTML = R”raw(Wi-Fi Settings>Done. ESP will restart in 3 seconds.)raw”;
    or save it to FS with gzip compression.

    • Hi Yuri,
      when you use PROGMEM you don’t use SRAM but Flash memory, and the use of byte array help you to store in the variable not the verbose html but gzipped html with a big save of space.

      In generally, as you say, a better solution is to store It in the Flash FS in gz format and stream the data in realtime to reduce the temporary use of the memory (only in the request phase), but, for me, to solve simple problem It’s a tedious solution respect to put gzipped byterray to a Flash variable.

      Bye Renzo

Leave a Reply

Your email address will not be published. Required fields are marked *