Chapter 112: HTTP/HTTPS Server Implementation
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the role and architecture of an HTTP/HTTPS server running on an ESP32.
- Utilize the ESP-IDF
esp_http_server
component to create and manage a web server. - Implement URI handlers to process incoming GET and POST requests.
- Serve static web pages and files directly from the ESP32.
- Generate dynamic content in server responses based on device status or sensor data.
- Set up a basic HTTPS server using self-signed certificates for secure local communication.
- Recognize considerations for different ESP32 variants when implementing web server functionalities.
- Troubleshoot common issues encountered during ESP32 web server development.
Introduction
In the previous chapter, we explored how an ESP32 can act as an HTTP/HTTPS client, consuming data and services from remote servers. Now, we turn the tables and investigate how the ESP32 itself can become a server. By implementing an HTTP/HTTPS server, your ESP32 can host web pages, offer a RESTful API for control, or provide a configuration interface accessible from any web browser on the local network.
This capability is incredibly powerful for IoT applications. Imagine an ESP32-based device that allows you to configure its Wi-Fi settings, view real-time sensor data, or control its actuators directly through a web page served by the ESP32 itself—no external cloud service or mobile app required for local interaction. This chapter will equip you with the knowledge to build such embedded web servers, opening up a new dimension of interactivity for your ESP32 projects.
Prerequisite Note: This chapter assumes you have a working Wi-Fi connection on your ESP32, either in Station (STA) mode connected to an existing network or in Access Point (AP) mode where the ESP32 creates its own network. Please refer to Volume 2: “Connectivity Fundamentals – WiFi” (Chapters 26-50) for establishing Wi-Fi connectivity. Basic understanding of HTML is also beneficial.
Theory
HTTP Server Fundamentals
An HTTP Server is a software application that listens for incoming network requests (typically over TCP/IP on port 80 for HTTP or port 443 for HTTPS) from clients (like web browsers). When a request arrives, the server processes it and sends back an HTTP response.
Key concepts for an embedded web server:
- Listening Socket: The server creates a socket bound to a specific IP address and port on the ESP32, waiting for client connections.
- Client Connection: When a client attempts to connect, the server accepts the connection, creating a new socket for communication with that specific client.
- Request Parsing: The server reads the HTTP request message from the client. This message includes:
- Request Line: Method (e.g.,
GET
,POST
), URI (e.g.,/index.html
,/api/led
), and HTTP version. - Headers: Key-value pairs providing metadata (e.g.,
Host
,User-Agent
,Content-Type
). - Body (optional): Data payload, typically used with POST or PUT requests.
- Request Line: Method (e.g.,
- URI Routing/Dispatching: The server examines the requested URI and HTTP method to determine which internal function (handler) should process the request.
- Request Handling: The designated handler function executes, performing actions like reading sensor data, controlling GPIOs, or preparing content to be sent back.
- Response Generation: The handler constructs an HTTP response message, which includes:
- Status Line: HTTP version, status code (e.g.,
200 OK
,404 Not Found
), and reason phrase. - Headers: Key-value pairs (e.g.,
Content-Type
,Content-Length
,Connection
). - Body (optional): The actual content being sent (e.g., HTML, JSON, image data).
- Status Line: HTTP version, status code (e.g.,
- Sending Response: The server transmits the HTTP response back to the client over the established connection.
- Connection Handling: The connection might be closed after the response or kept alive for further requests, depending on HTTP headers (
Connection: keep-alive
orConnection: close
).
ESP32 HTTP Server Request-Response Flow.
sequenceDiagram participant Client as Web Browser/Client participant ESP32Server as ESP32 HTTP Server Client->>+ESP32Server: 1. TCP Connection Request (e.g., to IP_ADDRESS:PORT) ESP32Server-->>-Client: 2. TCP Connection Established Client->>+ESP32Server: 3. HTTP Request<br/>(e.g., GET /index.html HTTP/1.1<br/>Host: esp32_ip<br/>User-Agent: BrowserName) Note over ESP32Server: 4. Server Parses Request:<br/>- Method (GET)<br/>- URI (/index.html)<br/>- Headers Note over ESP32Server: 5. URI Routing: Find handler for "/" or "/index.html" Note over ESP32Server: 6. Handler Invocation: Executes registered C function Note over ESP32Server: 7. Handler Logic:<br/>- Reads sensor data<br/>- Accesses GPIOs<br/>- Prepares HTML/JSON content Note over ESP32Server: 8. Response Generation:<br/>- Status Line (HTTP/1.1 200 OK)<br/>- Headers (Content-Type: text/html)<br/>- Body (HTML content) ESP32Server-->>-Client: 9. HTTP Response<br/>(HTTP/1.1 200 OK<br/>Content-Type: text/html<br/><br/>html... /html) Client->>+ESP32Server: 10. (Optional) Further Requests or Close Note over ESP32Server: Handles further requests or closes <br>connection based on "Connection" header. ESP32Server-->>-Client: (Connection Close or Kept Alive)
ESP-IDF esp_http_server
Component
The ESP-IDF provides the esp_http_server
component, a lightweight HTTP server designed for embedded applications. It supports features like multiple URI handlers, GET/POST methods, request parsing, and integration with the ESP-IDF event loop and VFS (Virtual File System) for serving files.
Core Components and Workflow:
- Server Configuration (httpd_config_t):This structure initializes the server. Key fields include:
task_priority
: Priority of the server task.stack_size
: Stack size for the server task.server_port
/ctrl_port
: Ports for data and control (control port used for internal signaling).max_open_sockets
: Maximum number of concurrent client connections.max_uri_handlers
: Maximum number of URI handlers that can be registered.lru_purge_enable
: Enables Least Recently Used (LRU) purging of old connections ifmax_open_sockets
is reached.global_user_ctx
/global_user_ctx_free_fn
: Global context accessible by all handlers.For HTTPS,server_port
is typically 443, and specific SSL configuration is needed.
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
// Modify config fields as needed, e.g.:
// config.server_port = 80;
// config.max_open_sockets = 7; // Default is 7
// config.stack_size = 4096;
Key fields in httpd_config_t structure:
Field Name | Type | Default (HTTPD_DEFAULT_CONFIG) | Description |
---|---|---|---|
task_priority |
UBaseType_t |
4 | Priority of the HTTP server task. |
stack_size |
size_t |
4096 bytes | Stack size for the HTTP server task. May need adjustment for complex handlers or HTTPS. |
server_port |
uint16_t |
80 | Port number for the server to listen on (HTTP). For HTTPS, this is part of httpd_ssl_config_t . |
ctrl_port |
uint16_t |
32768 | Control port used for internal signaling to the server task (e.g., for shutdown). |
max_open_sockets |
uint8_t |
7 | Maximum number of concurrent client connections allowed. For HTTPS, default is lower (e.g., 4 in HTTPD_SSL_CONFIG_DEFAULT ). |
max_uri_handlers |
uint8_t |
8 | Maximum number of URI handlers that can be registered. |
max_resp_headers |
uint8_t |
8 | Maximum number of response headers that can be set. |
backlog_conn |
uint8_t |
5 | Maximum number of pending connections in the listen queue. |
lru_purge_enable |
bool |
false | Enables/disables Least Recently Used (LRU) purging of idle connections if max_open_sockets is reached. |
recv_wait_timeout |
uint16_t |
5 seconds | Timeout for receiving data from a client socket. |
send_wait_timeout |
uint16_t |
5 seconds | Timeout for sending data to a client socket. |
global_user_ctx |
void* |
NULL | Global user context pointer accessible by all handlers. |
global_user_ctx_free_fn |
void (*)(void*) |
NULL | Function to free the global user context when the server stops. |
global_transport_ctx |
void* |
NULL | Global transport context (e.g., for SSL context in HTTPS). Managed internally for SSL. |
global_transport_ctx_free_fn |
void (*)(void*) |
NULL | Function to free the global transport context. |
open_fn |
httpd_open_func_t |
NULL (uses default) | Custom function to open a new session/socket. If NULL, default lwIP sockets are used. |
close_fn |
httpd_close_func_t |
NULL (uses default) | Custom function to close a session/socket. Invoked when a session is LRU purged or server is stopped. |
uri_match_fn |
httpd_uri_match_func_t |
NULL (uses default wildcard matching) | Custom URI matching function. If NULL, default matching (supporting ‘*’ wildcard) is used. |
- Starting the Server (httpd_start):Once configured, the server is started, returning a handle.
httpd_handle_t server = NULL;
esp_err_t ret = httpd_start(&server, &config);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);
}
- URI Handlers (httpd_uri_t):These structures define how the server responds to requests for specific URIs and HTTP methods.
uri
: The URI string (e.g.,/hello
,/data
,/*
for wildcard).method
: The HTTP method (e.g.,HTTP_GET
,HTTP_POST
,HTTP_PUT
,HTTP_DELETE
).handler
: A pointer to the C function that will handle requests matching the URI and method.user_ctx
: A void pointer to user-defined context data that will be passed to the handler function. This is useful for passing state or configuration specific to that handler.
esp_err_t my_handler_function(httpd_req_t *req);
- Registering URI Handlers (httpd_register_uri_handler):After starting the server, you register your URI handlers.
httpd_uri_t my_uri_handler_config = {
.uri = "/hello",
.method = HTTP_GET,
.handler = hello_get_handler,
.user_ctx = "Hello User Context" // Optional context
};
httpd_register_uri_handler(server, &my_uri_handler_config);
- Request Object (httpd_req_t *req):The handler function receives a pointer to an httpd_req_t structure, which contains all information about the incoming request. Key functions to interact with it:
req->method
: The HTTP method of the request.req->uri
: The URI string of the request.httpd_req_get_hdr_value_len()
andhttpd_req_get_hdr_value_str()
: To get request header values.httpd_req_get_url_query_len()
andhttpd_req_get_url_query_str()
: To get the query string part of the URI (e.g.,param1=value1¶m2=value2
).httpd_query_key_value()
: Helper to parse key-value pairs from a query string.httpd_req_get_content_len()
: Gets the length of the request body (e.g., for POST).httpd_req_recv()
: Reads data from the request body. This is crucial for handling POST data.
Member/Function | Description | Example Usage |
---|---|---|
req->method |
(Member) The HTTP method of the current request (e.g., HTTP_GET , HTTP_POST ). Type: http_method_t . |
if (req->method == HTTP_POST) { /* ... */ } |
req->uri |
(Member) A null-terminated string containing the URI of the request. | ESP_LOGI(TAG, "URI: %s", req->uri); |
req->user_ctx |
(Member) The user_ctx pointer provided when the handler was registered with httpd_register_uri_handler() . |
my_config_t *cfg = (my_config_t *)req->user_ctx; |
httpd_req_get_hdr_value_len(req, field) |
Gets the length of a specific request header field’s value. Returns 0 if header not found. | size_t len = httpd_req_get_hdr_value_len(req, "Content-Type"); |
httpd_req_get_hdr_value_str(req, field, buf, buf_len) |
Copies the value of a specific request header field into buf . Ensure buf_len is sufficient. |
char type_buf[32]; httpd_req_get_hdr_value_str(req, "Content-Type", type_buf, sizeof(type_buf)); |
httpd_req_get_url_query_len(req) |
Gets the length of the URL query string (the part after ‘?’). | size_t query_len = httpd_req_get_url_query_len(req); |
httpd_req_get_url_query_str(req, buf, buf_len) |
Copies the URL query string into buf . |
char query_buf[64]; if(query_len > 0) httpd_req_get_url_query_str(req, query_buf, sizeof(query_buf)); |
httpd_query_key_value(query_str, key, val_buf, val_buf_len) |
Parses a query string (query_str ) to find the value associated with a key . Stores value in val_buf . |
char param_val[16]; httpd_query_key_value(query_buf, "param1", param_val, sizeof(param_val)); |
httpd_req_get_content_len(req) |
Gets the length of the request body, typically from the Content-Length header. Essential for POST/PUT. |
size_t body_len = httpd_req_get_content_len(req); |
httpd_req_recv(req, buf, buf_len) |
Receives data from the request body into buf . Returns bytes read, 0 on EOF, or negative on error. Crucial for POST/PUT data. |
char post_data[128]; int received = httpd_req_recv(req, post_data, sizeof(post_data) - 1); if (received > 0) post_data[received] = '\\0'; |
- Sending Responses from Handlers:Handlers must send a response back to the client.
httpd_resp_set_status()
: Sets the HTTP status code (e.g., “200 OK”, “404 Not Found”).httpd_resp_set_status(req, "200 OK");
httpd_resp_set_type()
: Sets theContent-Type
header (e.g., “text/html”, “application/json”, “text/plain”).httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr()
: Sets any custom response header.httpd_resp_set_hdr(req, "X-Powered-By", "ESP32");
httpd_resp_send()
: Sends the response body. If the body is NULL or length is 0, only headers are sent.const char *resp_str = "<h1>Hello from ESP32!</h1>";
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
// HTTPD_RESP_USE_STRLEN automatically calculates length for null-terminated strings
// Alternatively, provide explicit length: httpd_resp_send(req, resp_str, strlen(resp_str));
httpd_resp_send_chunk()
: For sending response body in chunks (Transfer-Encoding: chunked). Useful for large or dynamically generated content where total length is unknown beforehand.- Convenience functions:
httpd_resp_send_404()
,httpd_resp_send_500()
,httpd_resp_send_err()
.
- Stopping the Server (httpd_stop):When the server is no longer needed:
httpd_stop(server);
Complete Workflow:
%%{init: {"flowchart": {"htmlLabels": true}} }%% graph TD A[Start: Initialize System] --> B(Initialize NVS & Wi-Fi); B -- Wi-Fi Connected --> C{"Configure HTTP Server <br> (httpd_config_t config = HTTPD_DEFAULT_CONFIG())"}; C -- Modify config if needed --> D["Start HTTP Server <br> httpd_handle_t server; <br> httpd_start(&server, &config)"]; D -- Success (ESP_OK) --> E{"Define URI Handlers <br> (httpd_uri_t my_uri = {...})"}; E -- For each handler --> F["Register URI Handler <br> httpd_register_uri_handler(server, &my_uri)"]; F --> G[Server Running & Listening for Requests]; subgraph "Incoming Request Processing" direction LR H[Client Sends HTTP Request] --> I{Server Receives Request}; I --> J{"Match URI & Method <br> (against registered handlers)"}; J -- Match Found --> K["Invoke Handler Function <br> (esp_err_t handler(httpd_req_t *req))"]; K --> L{"Handler Logic: <br> - Read req->uri, req->method <br> - Get headers (httpd_req_get_hdr_value_str) <br> - Get query (httpd_req_get_url_query_str) <br> - Receive body (httpd_req_recv for POST)"}; L --> M{"Prepare Response: <br> - Set status (httpd_resp_set_status) <br> - Set type (httpd_resp_set_type) <br> - Set headers (httpd_resp_set_hdr)"}; M --> N["Send Response Body <br> httpd_resp_send(req, body, len) <br> or httpd_resp_send_chunk()"]; N --> O[Request Handled]; J -- No Match --> P["Send 404 Not Found <br> (or custom error handler)"]; P --> O; end G -.-> H; O -.-> G; Q[Application Logic Decides to Stop Server] --> R["Stop HTTP Server <br> httpd_stop(server)"]; R --> S[End]; D -- Error --> T[Handle Server Start Error]; T --> S; classDef startEnd fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef io fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46; classDef error fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; classDef subgraphStyle fill:#F3F4F6,stroke:#6B7280; class A,S startEnd; class B,C,D,E,F,G,R,T process; class H,I,K,L,M,N,O,P process; class J decision; class Q decision; class subgraphStyle Processing; class Incoming Processing; class Request Processing;
Serving Static Files
Often, an ESP32 web server needs to serve static files like HTML pages, CSS stylesheets, JavaScript files, or images. These files can be:
Method | Description | Pros | Cons | Best Suited For |
---|---|---|---|---|
Embedding as C Strings/Arrays | File content is converted into C character arrays or byte arrays and compiled directly into the firmware. Served from RAM/Flash. | Simple to implement for small files. No filesystem dependency. Fast access. | Increases firmware size significantly for larger files. Difficult to update files without recompiling. Tedious to manage many files. Limited by available RAM/Flash for string literals. | Very small HTML snippets, tiny CSS, or very small icons. Single-page interfaces. |
Embedding via COMPONENT_EMBED_FILES |
Files are embedded as binary blobs by the build system. Accessible via _binary_filename_ext_start and _binary_filename_ext_end symbols. Served from Flash. |
No filesystem dependency. Relatively easy to include files. Good for read-only assets. | Increases firmware size. Files cannot be modified without recompiling and reflashing. Less flexible than a true filesystem. | Small to medium-sized web assets (HTML, CSS, JS, small images) that don’t change often. Certificates, keys. |
SPIFFS/LittleFS Filesystem | Files are stored on a dedicated partition in the ESP32’s internal flash memory using a wear-leveling filesystem. Files can be uploaded/managed at runtime (e.g., via UART, OTA, or HTTP upload handler). | Allows dynamic updates to web content without recompiling firmware. Good for larger websites with multiple files. Standard file I/O operations. | Consumes flash storage space. Slightly slower access than direct embedding. Filesystem overhead. Requires filesystem initialization and mounting. | Websites with multiple HTML, CSS, JS files, images. Applications where web content needs to be updated post-deployment. Data logging. |
FAT Filesystem on SD Card | Files are stored on an external SD card connected to the ESP32 via SPI. | Vast storage capacity for very large websites and media files. Easily updatable by removing SD card. Standard FAT filesystem. | Requires SD card hardware and SPI interface. Slower access compared to internal flash. Higher power consumption. Physical peripheral. | Applications requiring large amounts of web content, media files, or extensive data logging where internal flash is insufficient. |
When serving files, it’s crucial to set the correct Content-Type
header so the browser interprets the file correctly (e.g., text/html
, text/css
, application/javascript
, image/jpeg
).
Generating Dynamic Content
Dynamic content is generated by the ESP32 at the time of the request. For example:
- Displaying real-time sensor readings.
- Showing the current state of GPIO pins.
- Generating JSON responses for an API.
This involves constructing a string (HTML, JSON, etc.) within the URI handler function and sending it as the response body.
HTTPS Server Setup
To create an HTTPS server, the esp_http_server
needs to be configured for SSL/TLS. This involves:
1. Server Certificate and Private Key:
An HTTPS server needs an SSL certificate and its corresponding private key to establish secure connections. For development and local networks, a self-signed certificate is often used.
You can generate these using tools like OpenSSL. Example commands
# Generate a private key (e.g., 2048-bit RSA)
openssl genrsa -out server.key 2048
# Generate a Certificate Signing Request (CSR)
openssl req -new -key server.key -out server.csr
# (Fill in the prompts, Common Name (CN) is important, e.g., your ESP32's IP or a hostname)
# Generate a self-signed certificate valid for 365 days
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
The server.crt
(certificate) and server.key
(private key) files then need to be embedded into your ESP32 firmware, typically as C string arrays.
2. Configuration (httpd_ssl_config_t):
This structure is similar to httpd_config_t but is used for httpd_ssl_start.
httpd_ssl_config_t ssl_config = HTTPD_SSL_CONFIG_DEFAULT();
// Embed certificate and key data
extern const unsigned char servercert_pem_start[] asm("_binary_server_crt_start");
extern const unsigned char servercert_pem_end[] asm("_binary_server_crt_end");
ssl_config.servercert = servercert_pem_start;
ssl_config.servercert_len = servercert_pem_end - servercert_pem_start;
extern const unsigned char prvtkey_pem_start[] asm("_binary_server_key_start");
extern const unsigned char prvtkey_pem_end[] asm("_binary_server_key_end");
ssl_config.prvtkey_pem = prvtkey_pem_start;
ssl_config.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;
// Other httpd_config_t fields are part of ssl_config
ssl_config.httpd.server_port = 443; // Standard HTTPS port
ssl_config.httpd.max_open_sockets = 4; // HTTPS is more resource intensive
// ... other config fields ...
Tip: To embed binary files like certificates (.crt, .key) as symbols, you can list them in COMPONENT_EMBED_FILES in your CMakeLists.txt (or COMPONENT_EMBED_TXTFILES if they are text-based PEM files).idf_component_register(… EMBED_FILES “server.crt” “server.key”)This makes them accessible via _binary_filename_ext_start and _binary_filename_ext_end symbols.
3. Starting the HTTPS Server (httpd_ssl_start
):
httpd_handle_t server_https = NULL;
esp_err_t ret_https = httpd_ssl_start(&server_https, &ssl_config);
if (ret_https == ESP_OK) {
ESP_LOGI(TAG, "HTTPS server started on port %d", ssl_config.httpd.server_port);
// Register URI handlers as with HTTP server
// httpd_register_uri_handler(server_https, &my_uri_handler_config);
}
4. Stopping the HTTPS Server (httpd_ssl_stop):
httpd_ssl_stop(server_https);
Warning: When using self-signed certificates, web browsers will display security warnings because the certificate is not issued by a trusted Certificate Authority (CA). Users will need to manually accept the risk to proceed. This is generally acceptable for local development or private networks but not for public-facing services.
Typical Workflow:
%%{init: {"flowchart": {"htmlLabels": true}} }%% graph TD A[Start: Secure Server Needed] --> B{1- Generate SSL Certificate & Private Key}; B -- Using OpenSSL or similar tool --> Ba(server.key: Private Key); B -- Using OpenSSL or similar tool --> Bb(server.crt: Self-Signed Certificate); Ba --> C{2- Embed Keys into Firmware}; Bb --> C; C -- Add to CMakeLists.txt <br> EMBED_FILES <i>server.crt server.key</i> --> D["Build System Embeds Files <br> (Accessible via _binary_..._start/end symbols)"]; D --> E{"3- Configure HTTPS Server <br> (httpd_ssl_config_t ssl_config = HTTPD_SSL_CONFIG_DEFAULT())"}; E --> F[Set Certificate Pointers & Lengths <br> ssl_config.servercert = server_crt_start; <br> ssl_config.servercert_len = ...;]; F --> G[Set Private Key Pointers & Lengths <br> ssl_config.prvtkey_pem = server_key_start; <br> ssl_config.prvtkey_len = ...;]; G --> H["Configure other httpd settings in ssl_config.httpd <br> (e.g., .server_port = 443, .max_open_sockets)"]; H --> I["4- Start HTTPS Server <br> httpd_handle_t https_server; <br> httpd_ssl_start(&https_server, &ssl_config)"]; I -- Success (ESP_OK) --> J["5- Register URI Handlers <br> (httpd_register_uri_handler for HTTPS server handle)"]; J --> K[HTTPS Server Running Securely]; K --> L["Client Connects (Browser shows warning for self-signed cert)"]; L --> K; I -- Error --> M[Handle HTTPS Server Start Error]; M --> N[End Setup]; K --> Q["Application Stops Server <br> httpd_ssl_stop(https_server)"]; Q --> N; classDef startEnd fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef io fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46; classDef warning fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; class A,N startEnd; class B,C,E,H,I,J,K,Q decision; class Ba,Bb,D,F,G,M io; class L warning;
Practical Examples
Before running these examples, ensure:
- ESP-IDF v5.x is set up with VS Code.
- Your ESP32 board is connected.
- Wi-Fi is configured (the examples will use STA mode; adapt
wifi_init_sta()
from Chapter 111 or your own Wi-Fi connection logic). - You have a way to find your ESP32’s IP address on the network (e.g., from serial monitor logs).
Common main.c Structure (includes and Wi-Fi):
(This setup is similar to Chapter 111, ensure NVS and Wi-Fi initialization are present in your app_main.)
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "lwip/err.h"
#include "lwip/sys.h"
// For HTTP Server
#include "esp_http_server.h"
// For HTTPS Server (Example 4)
// #include "esp_https_server.h" // Note: esp_https_server.h is for a simplified https server.
// We will use esp_http_server with SSL config.
static const char *TAG = "HTTP_SERVER_EXAMPLE";
// (Include your Wi-Fi connection logic here from Chapter 111 or similar)
// Example:
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
#define WIFI_MAXIMUM_RETRY 5
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static esp_ip4_addr_t ip_addr; // To store the IP address
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < WIFI_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
ip_addr = event->ip_info.ip; // Store the IP address
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK }};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "connected to ap SSID:%s", WIFI_SSID); }
else if (bits & WIFI_FAIL_BIT) { ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID); }
else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); }
}
// End of Wi-Fi setup
Example 1: Basic HTTP Server (Root GET Handler)
main.c
(relevant part):
// ... (includes and Wi-Fi setup from above) ...
/* An HTTP GET handler */
static esp_err_t hello_get_handler(httpd_req_t *req)
{
char* buf;
size_t buf_len;
/* Get header value string length and allocate memory for it (+1 for null terminator) */
buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
if (buf_len > 1) {
buf = malloc(buf_len);
/* Copy null terminated value string into buffer */
if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
ESP_LOGI(TAG, "Found header => Host: %s", buf);
}
free(buf);
}
/* Send response with custom headers and body */
const char* resp_str = "Hello from your ESP32 Web Server!";
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_hdr(req, "X-ESP32-Info", "Basic Server Example");
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
/* After sending the response, update NVS or other system state if needed */
return ESP_OK;
}
static const httpd_uri_t hello_uri = {
.uri = "/hello",
.method = HTTP_GET,
.handler = hello_get_handler,
.user_ctx = "Hello World Context Data" // Example user context
};
/* Root URI handler */
static esp_err_t root_get_handler(httpd_req_t *req)
{
const char* resp_str = "Welcome to the ESP32 Root Page! Try /hello or /led";
httpd_resp_set_type(req, "text/plain");
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static const httpd_uri_t root_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler,
.user_ctx = NULL
};
static httpd_handle_t start_webserver(void)
{
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.lru_purge_enable = true; // Enable LRU purge for old sockets
// Start the httpd server
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
// Set URI handlers
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &root_uri);
httpd_register_uri_handler(server, &hello_uri);
return server;
}
ESP_LOGI(TAG, "Error starting server!");
return NULL;
}
static void stop_webserver(httpd_handle_t server)
{
// Stop the httpd server
if (server) {
httpd_stop(server);
}
}
void app_main(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
wifi_init_sta(); // Connect to Wi-Fi
// Wait for Wi-Fi connection
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi Connected. Starting HTTP server...");
start_webserver();
ESP_LOGI(TAG, "HTTP server started. Access at http://" IPSTR "/", IP2STR(&ip_addr));
} else {
ESP_LOGE(TAG, "WiFi connection failed. Cannot start HTTP server.");
}
// The server runs in its own task(s). app_main can exit or do other things.
}
Build Instructions:
- Save as
main/main.c
. UpdateWIFI_SSID
andWIFI_PASS
. - Ensure
main/CMakeLists.txt
is:idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")
- Build:
idf.py build
Run/Flash/Observe Steps:
- Flash:
idf.py -p /dev/ttyUSB0 flash
(replace port if needed). - Monitor:
idf.py -p /dev/ttyUSB0 monitor
. Note the IP address logged by the ESP32. - Open a web browser and navigate to
http://<ESP32_IP_ADDRESS>/
andhttp://<ESP32_IP_ADDRESS>/hello
.
Example 2: Serving a Simple HTML Page & Handling POST (LED Control)
This example serves an HTML page with buttons to control an LED and handles POST requests from that page.
(Assume an LED is connected to GPIO 2, or use CONFIG_BLINK_GPIO if set up).
// ... (includes, Wi-Fi setup, start_webserver, stop_webserver from Example 1) ...
#include "driver/gpio.h"
#define LED_GPIO GPIO_NUM_2 // Change if your LED is on a different GPIO
static void configure_led(void)
{
gpio_reset_pin(LED_GPIO);
gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT);
}
/* Handler for serving the HTML page */
static esp_err_t led_control_html_get_handler(httpd_req_t *req)
{
// Determine current LED state to reflect in HTML
// This is a simplified example; in a real app, you'd read the actual GPIO state
// or a variable tracking the state.
// For this example, let's assume we have a global or static variable for state.
static uint8_t led_state = 0; // 0 for OFF, 1 for ON
char html_response[512];
snprintf(html_response, sizeof(html_response),
"<!DOCTYPE html><html><head><title>ESP32 LED Control</title>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"<style> body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; } "
".button { padding: 15px 25px; font-size: 20px; margin: 10px; border-radius: 8px; cursor: pointer; } "
".on { background-color: #4CAF50; color: white; } "
".off { background-color: #f44336; color: white; } "
"p { font-size: 18px; } </style></head>"
"<body><h1>ESP32 LED Control</h1>"
"<p>LED is currently: %s</p>"
"<form action=\"/led\" method=\"post\">"
"<button class=\"button on\" name=\"led_action\" value=\"on\">Turn ON</button>"
"<button class=\"button off\" name=\"led_action\" value=\"off\">Turn OFF</button>"
"</form></body></html>",
led_state ? "ON" : "OFF");
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, html_response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
/* Handler for POST requests to /led */
static esp_err_t led_action_post_handler(httpd_req_t *req)
{
char buf[100];
int ret, remaining = req->content_len;
// Read data posted
if (remaining >= sizeof(buf)) {
ESP_LOGE(TAG, "Request body too large for buffer");
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Request body too large");
return ESP_FAIL;
}
ret = httpd_req_recv(req, buf, remaining);
if (ret <= 0) { // Error or connection closed
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req); // Request Timeout
}
return ESP_FAIL;
}
buf[ret] = '\0'; // Null-terminate the received data
ESP_LOGI(TAG, "Received POST data: %s", buf);
// Parse the received data (e.g., "led_action=on" or "led_action=off")
// For simplicity, we check for substrings. Robust parsing is recommended for production.
if (strstr(buf, "led_action=on")) {
ESP_LOGI(TAG, "Turning LED ON");
gpio_set_level(LED_GPIO, 1);
// Update our static led_state for the GET handler (simplified)
// In a real app, this state might be shared more robustly (e.g., via user_ctx or global)
// For the purpose of the GET handler to show correct status after POST:
((httpd_uri_t*)req->user_ctx)->user_ctx = (void*)1; // A bit of a hack for demo
} else if (strstr(buf, "led_action=off")) {
ESP_LOGI(TAG, "Turning LED OFF");
gpio_set_level(LED_GPIO, 0);
((httpd_uri_t*)req->user_ctx)->user_ctx = (void*)0; // A bit of a hack for demo
} else {
ESP_LOGW(TAG, "Unknown action: %s", buf);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Unknown LED action");
return ESP_FAIL;
}
// Redirect back to the control page (or send a success message)
// httpd_resp_set_status(req, "303 See Other");
// httpd_resp_set_hdr(req, "Location", "/led");
// httpd_resp_send(req, NULL, 0);
// For simplicity, let's just send a success message and let the user refresh or go back
const char* resp_str = "LED action processed. Refresh to see status.";
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
// URI configurations
static const httpd_uri_t led_control_page_uri = {
.uri = "/led", // Serves the HTML page on GET
.method = HTTP_GET,
.handler = led_control_html_get_handler,
.user_ctx = NULL
};
static const httpd_uri_t led_action_uri = {
.uri = "/led", // Handles form submission on POST
.method = HTTP_POST,
.handler = led_action_post_handler,
.user_ctx = (void*)&led_control_page_uri // Pass the GET URI config to update its context (simplified state)
};
// Modify start_webserver to include these new handlers
static httpd_handle_t start_webserver(void) // Overwrite previous version
{
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.lru_purge_enable = true;
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &root_uri); // From Example 1
httpd_register_uri_handler(server, &hello_uri); // From Example 1
httpd_register_uri_handler(server, &led_control_page_uri);
httpd_register_uri_handler(server, &led_action_uri);
return server;
}
ESP_LOGI(TAG, "Error starting server!");
return NULL;
}
void app_main(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
configure_led(); // Initialize LED GPIO
wifi_init_sta();
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi Connected. Starting HTTP server...");
start_webserver();
ESP_LOGI(TAG, "HTTP server started. Access at http://" IPSTR "/led", IP2STR(&ip_addr));
} else {
ESP_LOGE(TAG, "WiFi connection failed. Cannot start HTTP server.");
}
}
Build, Flash, Observe:
- Modify
app_main
to callconfigure_led()
and ensure the new handlers are registered instart_webserver
. - Build, flash, and monitor.
- Navigate to
http://<ESP32_IP_ADDRESS>/led
. You should see the control page. - Clicking the buttons will send POST requests, and the LED state should change. The page itself won’t auto-update the “LED is currently” status without a redirect or JavaScript in this simple example. The POST handler sends a simple text response. A redirect (
303 See Other
withLocation: /led
) would be a common pattern to refresh the page.
Note on state in
led_control_html_get_handler
: The example uses a static variableled_state
within the GET handler for simplicity. The POST handler attempts to update this viauser_ctx
which is a simplification. In more complex applications, shared state should be managed more carefully, perhaps using theuser_ctx
of the server or a dedicated module/task.
Example 3: Basic HTTPS Server
This example sets up a basic HTTPS server using a self-signed certificate.
Prerequisites:
- Generate
server.crt
andserver.key
using OpenSSL (as described in the Theory section). - Place these files in your
main
directory. - Add them to
main/CMakeLists.txt
:idf_component_register(SRCS "main.c" INCLUDE_DIRS "." EMBED_FILES "server.crt" "server.key")
main.c
(relevant part for HTTPS):
// ... (includes, Wi-Fi setup, hello_get_handler, root_get_handler from Example 1) ...
// Symbols for embedded certificate and key (generated by build system)
extern const unsigned char server_crt_start[] asm("_binary_server_crt_start");
extern const unsigned char server_crt_end[] asm("_binary_server_crt_end");
extern const unsigned char server_key_start[] asm("_binary_server_key_start");
extern const unsigned char server_key_end[] asm("_binary_server_key_end");
static httpd_handle_t start_https_webserver(void)
{
httpd_handle_t server = NULL;
// Server config for HTTPS
httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT(); // Use this for https_server
conf.httpd.lru_purge_enable = true;
conf.httpd.server_port = 443; // Standard HTTPS port
conf.httpd.ctrl_port = 32769; // Different from HTTP if running both
// Assign the embedded certificate and key
conf.servercert = server_crt_start;
conf.servercert_len = server_crt_end - server_crt_start;
conf.prvtkey_pem = server_key_start;
conf.prvtkey_len = server_key_end - server_key_start;
ESP_LOGI(TAG, "Starting HTTPS server on port: '%d'", conf.httpd.server_port);
esp_err_t ret = httpd_ssl_start(&server, &conf); // Use httpd_ssl_start
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error starting HTTPS server: %s", esp_err_to_name(ret));
return NULL;
}
// Register URI handlers (same as for HTTP server)
ESP_LOGI(TAG, "Registering URI handlers for HTTPS");
httpd_register_uri_handler(server, &root_uri); // From Example 1
httpd_register_uri_handler(server, &hello_uri); // From Example 1
// httpd_register_uri_handler(server, &led_control_page_uri); // From Example 2
// httpd_register_uri_handler(server, &led_action_uri); // From Example 2
return server;
}
// In app_main, call start_https_webserver instead of start_webserver
void app_main(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// configure_led(); // If using LED example
wifi_init_sta();
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi Connected. Starting HTTPS server...");
// stop_webserver(start_webserver()); // Stop HTTP if it was running
start_https_webserver();
ESP_LOGI(TAG, "HTTPS server started. Access at https://" IPSTR "/", IP2STR(&ip_addr));
} else {
ESP_LOGE(TAG, "WiFi connection failed. Cannot start HTTPS server.");
}
}
Build, Flash, Observe:
- Generate
server.crt
andserver.key
, place them inmain/
, and updateCMakeLists.txt
. - Build, flash, monitor.
- Open a browser and navigate to
https://<ESP32_IP_ADDRESS>/
. - The browser will show a security warning (e.g., “Your connection is not private”). This is expected for self-signed certificates. Proceed by accepting the risk (e.g., “Advanced” -> “Proceed to <IP_ADDRESS> (unsafe)”).
- You should then see the content served from your ESP32 over HTTPS.
Variant Notes
Running a web server, especially HTTPS, has implications for ESP32 variants:
- ESP32 (Original): Dual cores and ample RAM make it well-suited for moderately complex web servers, including HTTPS. Hardware crypto acceleration is beneficial.
- ESP32-S2: Single-core. Performance for HTTPS might be lower than dual-core variants under heavy load. RAM management is crucial. Has hardware crypto.
- ESP32-S3: Dual-core with AI extensions and good PSRAM support. Excellent for more demanding web server applications, including those with larger web assets or more concurrent users. Has hardware crypto.
- ESP32-C3: Single RISC-V core. More memory-constrained. Simple HTTP servers are fine. HTTPS is feasible but requires careful optimization of stack sizes, buffer usage, and number of concurrent connections. Hardware crypto support helps.
- ESP32-C6: Single RISC-V core with Wi-Fi 6. Similar considerations to ESP32-C3 regarding memory and processing for web server tasks. Hardware crypto is present.
- ESP32-H2: Primarily for Thread/Zigbee and BLE, with Wi-Fi as a secondary capability. If used as a web server, expect it to be for very lightweight, local configuration tasks due to resource constraints. Hardware crypto is available.
General Considerations:
- RAM: HTTPS is memory-intensive due to TLS buffers and certificate handling.
httpd_config_t.stack_size
andhttpd_ssl_config_t.httpd.stack_size
should be adequate (e.g., 4096-8192 bytes, or more for complex handlers).max_open_sockets
(inhttpd_config_t
orhttpd_ssl_config_t.httpd
) impacts RAM. Reduce for constrained devices. Default is 7 for HTTP, 4 forHTTPD_SSL_CONFIG_DEFAULT
.- Large request/response bodies also consume RAM.
- Flash: Storing web assets (HTML, CSS, JS, images) directly in flash (e.g., via SPIFFS/LittleFS or embedding) consumes flash memory. HTTPS certificates and keys also add to this.
- CPU Load: TLS handshakes for HTTPS are CPU-intensive. Hardware cryptographic accelerators on ESP32 variants significantly mitigate this, but high connection rates on single-core devices can still lead to performance bottlenecks.
- Task Priority: The
httpd_config_t.task_priority
should be set appropriately relative to other tasks in your system. - Wi-Fi Mode:
- STA Mode: The ESP32 connects to an existing Wi-Fi network. Clients on the same network can access the server. This is common for devices integrated into a home/office network.
- AP Mode: The ESP32 creates its own Wi-Fi network. Clients connect directly to the ESP32’s network. This is excellent for initial device configuration (provisioning) or when no existing Wi-Fi infrastructure is available.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Wi-Fi Not Connected / Incorrect IP | Clients (browser) cannot connect to the ESP32 server (“Site can’t be reached”). ESP32 logs show no incoming connections. | 1. Ensure Wi-Fi (STA or AP mode) is initialized and active. 2. Verify the IP address logged by ESP32 (STA mode) or the default AP IP (e.g., 192.168.4.1). 3. Ensure client device is on the same network (STA) or connected to ESP32’s AP. 4. Check for firewall issues on the client or network. |
404 Not Found Errors | Browser shows a 404 error page for a requested URI. | 1. Double-check URI string in httpd_uri_t (case-sensitive, exact match or correct wildcard usage). 2. Verify httpd_register_uri_handler() was called successfully for that URI after server start. 3. Check for typos in the URL entered in the browser. 4. Ensure the correct HTTP method (GET, POST) is used by client and defined in handler. |
POST Data Not Received/Parsed Incorrectly | Handler for POST request doesn’t receive data, or data is corrupted/incomplete. req->content_len might be 0. |
1. Check req->content_len . 2. Allocate a sufficiently large buffer for httpd_req_recv() . 3. Check return value of httpd_req_recv() . 4. Ensure client sends correct Content-Type (e.g., application/x-www-form-urlencoded , application/json ). 5. Null-terminate received buffer: buf[bytes_read] = '\\0'; . |
HTTPS Connection Failures / Certificate Warnings | Browser refuses connection or shows persistent security warnings (beyond the expected self-signed cert warning). Errors like “SSL_ERROR_RX_RECORD_TOO_LONG”. | 1. Self-signed certs always cause browser warnings – this is normal; proceed past warning. 2. Ensure server.crt and server.key are a matched pair and correctly embedded (check _binary_..._start/end symbols and lengths in httpd_ssl_config_t ). 3. Verify correct port (443) is used. 4. Check ESP32 logs for mbedTLS errors. 5. Insufficient RAM/stack for HTTPS (see below). 6. Ensure client and server support compatible TLS versions/ciphers (usually handled by mbedTLS defaults). |
Stack Overflow / Guru Meditation Error | ESP32 crashes or reboots, especially when handling requests (more common with HTTPS or complex handlers). | 1. Increase stack_size in httpd_config_t or httpd_ssl_config_t.httpd (e.g., to 6144, 8192, or more). 2. Profile stack usage ( uxTaskGetStackHighWaterMark() ). 3. Avoid large local variables or deep recursion in handlers. 4. Reduce max_open_sockets if memory is tight. |
Server Stops Responding / Max Connections Reached | New clients cannot connect, existing connections might timeout. ESP32 logs might show “max open sockets reached”. | 1. Ensure connections are closed properly by clients or server (e.g., Connection: close header, or client closes socket). 2. Enable lru_purge_enable = true in config to automatically close idle sockets. 3. Increase max_open_sockets if resources permit, but be mindful of RAM. 4. Check for handlers that don’t send a response or block indefinitely. |
Incorrect Content-Type for Served Files | Browser downloads file instead of displaying it (e.g., HTML shown as text, CSS not applied, images broken). | Set the correct Content-Type header using httpd_resp_set_type(req, "your_content_type") . Examples: "text/html" , "text/css" , "application/javascript" , "image/png" , "application/json" . |
Forgetting to Stop the Server | Resources (sockets, memory, task) are not freed if server is started multiple times without stopping, or on program termination. | Call httpd_stop(server_handle) or httpd_ssl_stop(server_handle) when the server is no longer needed or before re-initializing. |
Exercises
- Custom 404 Error Page:
- Create a new URI handler that is registered with
httpd_register_err_handler()
forHTTPD_404_NOT_FOUND
. - This handler should send a user-friendly HTML page indicating the requested resource was not found, perhaps with a link back to the root page.
- Create a new URI handler that is registered with
- Display ESP32 Chip Information:
- Create a new GET URI handler (e.g.,
/chipinfo
). - Inside the handler, use
esp_chip_info()
to get information like chip model, cores, revision, and features. - Format this information into an HTML table and send it as the response.
- Create a new GET URI handler (e.g.,
- Simple JSON API Endpoint:
- Create a GET URI handler (e.g.,
/api/status
). - This handler should construct and send a JSON response containing some mock device status (e.g.,
{"deviceName": "MyESP32", "uptimeSeconds": 12345, "isLedOn": true}
). - Set the
Content-Type
toapplication/json
. - Test this endpoint using a browser or a tool like
curl
or Postman.
- Create a GET URI handler (e.g.,
- Serve a Small Image:
- Find a very small image (e.g., a 1KB PNG or GIF icon).
- Convert the image to a C byte array (e.g., using an online tool like
xxd -i image.png > image_array.c
or a web-based converter). - Embed this array in your firmware.
- Create a GET URI handler (e.g.,
/image.png
). - This handler should send the byte array as the response body, ensuring you set the correct
Content-Type
(e.g.,image/png
). - Create another HTML page that includes an
<img>
tag referencing this image URI.
- AP Mode Configuration Server:
- Modify your
wifi_init_sta()
towifi_init_softap()
(refer to Chapter 31 for AP mode setup). The ESP32 will create its own Wi-Fi network (e.g., “ESP32-Config-XXXX”). - Implement the LED control page (Example 2) or a simpler status page.
- Connect your phone or laptop to the ESP32’s Wi-Fi network and access the server via the ESP32’s AP IP address (usually 192.168.4.1 by default). This demonstrates a common pattern for device provisioning.
- Modify your
Summary
- The ESP32 can host HTTP/HTTPS web servers using the
esp_http_server
component. - Server setup involves
httpd_config_t
(for HTTP) orhttpd_ssl_config_t
(for HTTPS) and starting withhttpd_start()
orhttpd_ssl_start()
. - Functionality is built by registering URI handlers (
httpd_uri_t
) that map specific URIs and HTTP methods to C functions. - Handler functions receive an
httpd_req_t
object to access request details and usehttpd_resp_send()
(and related functions) to send responses. - Static content (HTML, CSS, JS) can be embedded or served from filesystems; dynamic content is generated in code.
- HTTPS requires an SSL certificate and private key; self-signed certificates are common for local/development use but will cause browser warnings.
- Resource management (RAM, CPU, Flash, task stacks) is critical, especially for HTTPS and on more constrained ESP32 variants.
- Running a server in AP mode allows direct client connections for configuration or local control without an existing network.
Further Reading
- ESP-IDF HTTP Server Documentation:
- ESP-IDF HTTP Server API Reference (Select your ESP-IDF version and target chip if necessary).
- ESP-IDF HTTPS Server Documentation (Covers
esp_http_server
with SSL): - Embedding Files in ESP-IDF:
- Search for “Embedding Files” in the ESP-IDF Programming Guide for details on
EMBED_FILES
in CMake.
- Search for “Embedding Files” in the ESP-IDF Programming Guide for details on
- OpenSSL (for generating self-signed certificates):
- OpenSSL Official Website
- Numerous online tutorials for “OpenSSL self-signed certificate generation.”
- MDN Web Docs for HTTP:
