Home / References / ESP32 Library / WebServer Library
Description
The enableETag()
method is used to enable or disable ETag (Entity Tag) support on the ESP32 WebServer with an optional custom ETag generation function. When ETag support is enabled, the server automatically generates and sends ETag headers for served files, allowing clients (browsers) to cache resources efficiently and reduce bandwidth usage. This method is particularly valuable for serving static files like HTML, CSS, JavaScript, and images, as it enables smart caching mechanisms that only re-download files when they have actually changed. The method supports both built-in MD5-based ETag generation and custom ETag generation functions, providing flexibility for different caching strategies based on file modification time, content hash, or other criteria.
For more ESP32 development resources and tutorials, visit the All About ESP32 Resources Hub on AvantMaker.com
Understanding ETag Support and Caching
ETag (Entity Tag) is a crucial web caching mechanism that helps optimize web performance by reducing unnecessary data transfers. For beginners, it’s important to understand how web caching works and why ETags are so valuable for IoT projects.
How Web Caching Works Without ETags
Without ETags, every time a browser requests a file from your ESP32 server (like an image, CSS file, or JavaScript file), the server must send the entire file, even if it hasn’t changed. For example, if your ESP32 serves a 50KB image file and a user refreshes the page 10 times, the server sends 500KB of data unnecessarily.
How ETags Improve Performance
ETags work by creating a unique “fingerprint” for each file. Here’s the process:
- First Request: Client requests
/logo.png
from your ESP32 - Server Response: ESP32 sends the file with an ETag header like
ETag: "abc123xyz"
- Client Caching: Browser stores the file and remembers the ETag value
- Subsequent Requests: Browser sends
If-None-Match: "abc123xyz"
header - Smart Response: If file unchanged, ESP32 sends
304 Not Modified
(only ~200 bytes instead of the full file)
ETag Generation Methods
The ESP32 WebServer supports two ETag generation approaches:
- Default MD5 Method:
server.enableETag(true)
– Creates ETags based on file content using MD5 hash. More accurate but requires reading the entire file. - Custom Function Method:
server.enableETag(true, customFunction)
– Uses your custom function, typically based on file modification time. Faster but requires filesystem timestamp support.
Practical Benefits for ESP32 Projects
- Reduced Bandwidth: Saves precious WiFi bandwidth on repeated requests
- Faster Loading: Web interfaces load faster after the first visit
- Lower CPU Usage: Less file processing and transmission on the ESP32
- Better User Experience: Responsive web interfaces even on slower connections
Syntax and Usage
The enableETag()
method can be used in two different ways:
- Enable with default MD5 ETags:
server.enableETag(true)
– Enables ETag support using built-in MD5 hash calculation of file contents. - Enable with custom ETag function:
server.enableETag(true, customFunction)
– Enables ETag support with a custom function for generating ETags. - Disable ETag support:
server.enableETag(false)
– Disables ETag support (default behavior).
For practical applications and examples of this method, please consult the “Example Code” section on this page. This section provides comprehensive guidance to help you better understand and apply the method effectively.
Arguments
- enable (bool) – A boolean value that determines whether to enable or disable ETag support.
true
enables ETag headers,false
disables them (default). - fn (ETagFunction, optional) – An optional custom function with signature
String function(FS &fs, const String &fileName)
that generates custom ETag values. If not provided, the server uses built-in MD5-based ETag generation.
Return Value
The enableETag()
method does not return any value (void return type). The method configures the WebServer internally to include or exclude ETag headers when serving static files.
Example Code
File Server with Smart ETag Caching
This example demonstrates how to create an ESP32 file server with intelligent ETag caching that uses both default MD5-based ETags and custom timestamp-based ETags. The server includes a web interface to monitor caching performance and test different ETag strategies.
How to use this example: Upload this code to your ESP32 and replace the WiFi credentials. Upload some test files to the ESP32’s filesystem (HTML, CSS, images). After connecting, access the web interface at http://ESP32_IP/
to see the caching dashboard. Test caching by refreshing pages multiple times and observing the cache statistics. Use browser developer tools to inspect ETag headers and 304 responses. Try both ETag modes to compare MD5 vs timestamp-based caching performance.
/*
* Author: Avant Maker
* Date: June 18, 2025
* Version: 1.0
* License: MIT
*
* Description:
* This example demonstrates how to create an ESP32 file server with
* intelligent ETag caching that uses both default MD5-based ETags and
* custom timestamp-based ETags. The server includes a web interface to
* monitor caching performance and test different ETag strategies.
*
* How to use this example:
* Upload this code to your ESP32 and replace the WiFi credentials.
* Upload some test files to the ESP32's filesystem (HTML, CSS, images).
* After connecting, access the web interface at http://ESP32_IP/ to see
* the caching dashboard. Test caching by refreshing pages multiple times
* and observing the cache statistics. Use browser developer tools to
* inspect ETag headers and 304 responses. Try both ETag modes to compare
* MD5 vs timestamp-based caching performance.
*
* Code Source:
* This example code is sourced from the Comprehensive Guide
* to the ESP32 Arduino Core Library, accessible on AvantMaker.com.
* For additional code examples and in-depth documentation related to
* the ESP32 Arduino Core Library, please visit:
*
* https://avantmaker.com/home/all-about-esp32-arduino-core-library/
*
* AvantMaker.com, your premier destination for all things
* DIY, AI, IoT, Smart Home, and STEM projects. We are dedicated
* to empowering makers, learners, and enthusiasts with
* the resources they need to bring their nnovative ideas to life.
*/
#include <WiFi.h>
#include <WebServer.h>
#include <LittleFS.h>
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
WebServer server(80);
// ETag and caching statistics
struct CacheStats {
unsigned long totalRequests = 0;
unsigned long cacheHits = 0;
unsigned long cacheMisses = 0;
unsigned long bytesSaved = 0;
bool etagEnabled = false;
bool useCustomETag = false;
} stats;
// Custom ETag function based on file modification time
String customETagFunction(FS &fs, const String &path) {
File file = fs.open(path, "r");
if (!file) {
return "";
}
// Create ETag based on file modification time (faster than MD5)
time_t lastWrite = file.getLastWrite();
size_t fileSize = file.size();
file.close();
// Format: "timestamp-size" for uniqueness
String etag = "\"" + String(lastWrite, 16) + "-" + String(fileSize, 16) + "\"";
return etag;
}
void handleRoot() {
stats.totalRequests++;
String html = "<!DOCTYPE html><html><head><title>ESP32 ETag Caching Server</title>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<style>";
html += "body{font-family:Arial,sans-serif;margin:20px;background:#f5f5f5;}";
html += ".container{max-width:900px;margin:0 auto;background:white;padding:30px;border-radius:10px;box-shadow:0 4px 15px rgba(0,0,0,0.1);}";
html += ".stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px;margin:20px 0;}";
html += ".stat-card{background:#e8f4fd;padding:20px;border-radius:8px;text-align:center;border-left:4px solid #007bff;}";
html += ".stat-number{font-size:2em;font-weight:bold;color:#007bff;margin-bottom:5px;}";
html += ".stat-label{color:#666;font-size:0.9em;}";
html += ".status{padding:15px;margin:15px 0;border-radius:8px;font-weight:bold;}";
html += ".enabled{background:#d4edda;color:#155724;border:1px solid #c3e6cb;}";
html += ".disabled{background:#f8d7da;color:#721c24;border:1px solid #f5c6cb;}";
html += ".button{background:#007bff;color:white;padding:12px 24px;border:none;border-radius:6px;cursor:pointer;margin:8px;text-decoration:none;display:inline-block;}";
html += ".button:hover{background:#0056b3;}";
html += ".button.secondary{background:#6c757d;} .button.secondary:hover{background:#545b62;}";
html += ".test-files{background:#f8f9fa;padding:20px;border-radius:8px;margin:20px 0;}";
html += "</style></head><body>";
html += "<div class='container'>";
html += "<h1>ESP32 ETag Caching Dashboard</h1>";
// ETag Status
html += "<div class='status " + String(stats.etagEnabled ? "enabled" : "disabled") + "'>";
html += "ETag Status: " + String(stats.etagEnabled ? "ENABLED" : "DISABLED");
if (stats.etagEnabled) {
html += " (" + String(stats.useCustomETag ? "Custom Timestamp" : "MD5 Hash") + ")";
}
html += "</div>";
// Statistics
html += "<div class='stats'>";
html += "<div class='stat-card'><div class='stat-number'>" + String(stats.totalRequests) + "</div><div class='stat-label'>Total Requests</div></div>";
html += "<div class='stat-card'><div class='stat-number'>" + String(stats.cacheHits) + "</div><div class='stat-label'>Cache Hits (304)</div></div>";
html += "<div class='stat-card'><div class='stat-number'>" + String(stats.cacheMisses) + "</div><div class='stat-label'>Cache Misses</div></div>";
html += "<div class='stat-card'><div class='stat-number'>" + String(stats.bytesSaved/1024) + " KB</div><div class='stat-label'>Bandwidth Saved</div></div>";
float hitRate = stats.totalRequests > 0 ? (stats.cacheHits * 100.0 / stats.totalRequests) : 0;
html += "<div class='stat-card'><div class='stat-number'>" + String(hitRate, 1) + "%</div><div class='stat-label'>Cache Hit Rate</div></div>";
html += "</div>";
// Controls
html += "<h2>ETag Configuration</h2>";
html += "<a href='/etag/disable' class='button secondary'>Disable ETag</a>";
html += "<a href='/etag/enable-md5' class='button'>Enable MD5 ETag</a>";
html += "<a href='/etag/enable-custom' class='button'>Enable Custom ETag</a>";
html += "<a href='/stats/reset' class='button secondary'>Reset Statistics</a>";
// Test Files Section
html += "<div class='test-files'>";
html += "<h3>Test Files for Caching</h3>";
html += "<p>Try accessing these files multiple times to test ETag caching:</p>";
html += "<ul>";
html += "<li><a href='/test/sample.html' target='_blank'>Sample HTML File</a></li>";
html += "<li><a href='/test/style.css' target='_blank'>Sample CSS File</a></li>";
html += "<li><a href='/test/data.json' target='_blank'>Sample JSON Data</a></li>";
html += "<li><a href='/test/image.png' target='_blank'>Sample Image (if available)</a></li>";
html += "</ul>";
html += "<p><strong>Testing Tip:</strong> Use browser developer tools (F12) → Network tab to see ETag headers and 304 responses.</p>";
html += "</div>";
// System Info
html += "<h3>System Information</h3>";
html += "<p>Free Heap: " + String(ESP.getFreeHeap()) + " bytes</p>";
html += "<p>Uptime: " + String(millis()/1000) + " seconds</p>";
html += "<p>Filesystem: " + String(LittleFS.totalBytes()) + " bytes total, " + String(LittleFS.usedBytes()) + " bytes used</p>";
html += "</div>";
html += "<script>setTimeout(function(){location.reload();},30000);</script>"; // Auto-refresh every 30 seconds
html += "</body></html>";
server.send(200, "text/html", html);
}
void handleDisableETag() {
stats.etagEnabled = false;
stats.useCustomETag = false;
server.enableETag(false);
server.send(200, "text/plain", "ETag disabled\nGo back to: http://" + WiFi.localIP().toString());
Serial.println("ETag support disabled");
}
void handleEnableMD5ETag() {
stats.etagEnabled = true;
stats.useCustomETag = false;
server.enableETag(true); // Use default MD5-based ETag
server.send(200, "text/plain", "MD5 ETag enabled\nGo back to: http://" + WiFi.localIP().toString());
Serial.println("MD5 ETag support enabled");
}
void handleEnableCustomETag() {
stats.etagEnabled = true;
stats.useCustomETag = true;
server.enableETag(true, customETagFunction); // Use custom timestamp-based ETag
server.send(200, "text/plain", "Custom ETag enabled\nGo back to: http://" + WiFi.localIP().toString());
Serial.println("Custom ETag support enabled");
}
void handleResetStats() {
stats.totalRequests = 1; // Keep this request in count
stats.cacheHits = 0;
stats.cacheMisses = 0;
stats.bytesSaved = 0;
server.send(200, "text/plain", "Statistics reset\nGo back to: http://" + WiFi.localIP().toString());
Serial.println("Cache statistics reset");
}
void createTestFiles() {
// Create sample files for testing ETag functionality
File file;
// Sample HTML file
file = LittleFS.open("/test/sample.html", "w");
if (file) {
file.println("<!DOCTYPE html><html><head><title>Sample Page</title></head>");
file.println("<body><h1>Sample HTML File</h1><p>This file is used to test ETag caching.</p>");
file.println("<p>File size: ~200 bytes</p><p>Last modified: " + String(millis()) + "</p></body></html>");
file.close();
}
// Sample CSS file
file = LittleFS.open("/test/style.css", "w");
if (file) {
file.println("/* Sample CSS file for ETag testing */");
file.println("body { font-family: Arial, sans-serif; margin: 20px; background: #f0f0f0; }");
file.println("h1 { color: #333; border-bottom: 2px solid #007bff; }");
file.println("p { line-height: 1.6; color: #666; }");
file.close();
}
// Sample JSON file
file = LittleFS.open("/test/data.json", "w");
if (file) {
file.println("{");
file.println(" \"message\": \"Sample JSON data for ETag testing\",");
file.println(" \"timestamp\": " + String(millis()) + ",");
file.println(" \"data\": [1, 2, 3, 4, 5],");
file.println(" \"config\": { \"caching\": true, \"etag\": \"enabled\" }");
file.println("}");
file.close();
}
}
// Custom request handler that tracks cache statistics
class CacheTrackingHandler : public RequestHandler {
public:
bool canHandle(HTTPMethod requestMethod, const String &requestUri) override {
return requestMethod == HTTP_GET && requestUri.startsWith("/test/");
}
bool handle(WebServer &server, HTTPMethod requestMethod, const String &requestUri) override {
stats.totalRequests++;
String path = requestUri;
if (!LittleFS.exists(path)) {
stats.cacheMisses++;
return false;
}
File file = LittleFS.open(path, "r");
if (!file) {
stats.cacheMisses++;
return false;
}
size_t fileSize = file.size();
// Check if this is a cache hit (304 response)
String ifNoneMatch = server.header("If-None-Match");
if (ifNoneMatch.length() > 0 && stats.etagEnabled) {
// This will be handled by the static file handler
// but we can predict it's likely a cache hit
stats.cacheHits++;
stats.bytesSaved += fileSize;
} else {
stats.cacheMisses++;
}
file.close();
return false; // Let the static handler actually serve the file
}
};
void setup() {
Serial.begin(115200);
// Initialize filesystem
if (!LittleFS.begin()) {
Serial.println("LittleFS initialization failed!");
return;
}
// Create test directory and files
LittleFS.mkdir("/test");
createTestFiles();
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("WiFi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Enable custom ETag by default
stats.etagEnabled = true;
stats.useCustomETag = true;
server.enableETag(true, customETagFunction);
// Add cache tracking handler
server.addHandler(new CacheTrackingHandler);
// Set up route handlers
server.on("/", handleRoot);
server.on("/etag/disable", handleDisableETag);
server.on("/etag/enable-md5", handleEnableMD5ETag);
server.on("/etag/enable-custom", handleEnableCustomETag);
server.on("/stats/reset", handleResetStats);
// Serve static files with ETag support
server.serveStatic("/test/", LittleFS, "/test/");
// Start server
server.begin();
Serial.println("HTTP server started with ETag support enabled");
Serial.println("Access the dashboard at: http://" + WiFi.localIP().toString());
Serial.println("Test files created in /test/ directory");
}
void loop() {
server.handleClient();
// Optional: Print periodic status
static unsigned long lastPrint = 0;
if (millis() - lastPrint > 60000) { // Every 60 seconds
lastPrint = millis();
float hitRate = stats.totalRequests > 0 ? (stats.cacheHits * 100.0 / stats.totalRequests) : 0;
Serial.println("Cache Stats - Requests: " + String(stats.totalRequests) +
", Hits: " + String(stats.cacheHits) +
", Hit Rate: " + String(hitRate, 1) + "%" +
", Bytes Saved: " + String(stats.bytesSaved/1024) + " KB");
}
}
This page is part of the Comprehensive Guide to the ESP32 Arduino Core Library, accessible on AvantMaker.com.
ESP32 Library Index
- ESP32 WiFi Library
- ESP32 WiFiClient Library
- ESP32 HTTPClient Library
- ESP32 WiFiClientSecure Library
- ESP32 AsyncUDP Librarry
- ESP32 WebServer Library
- Server Operation
- Client Hnadling
- Routing and Handlers
- Authentication
- Request Information
- Request Header Management
- Response Information
- Server Configuration
- Which ESP32 Boards are Recommended for Learners
- How to Copy Codes from AvantMaker.com
- What is SPIFFS and how to upload files to it?
- What is LIttleFS and how to upload files to it?
Ready to experiment and explore more about ESP32? Visit our website’s All About ESP32 Resources Hub, packed with tutorials, guides, and tools to inspire your maker journey. Experiment, explore, and elevate your skills with everything you need to master this powerful microcontroller platform!