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:

  1. Listening Socket: The server creates a socket bound to a specific IP address and port on the ESP32, waiting for client connections.
  2. Client Connection: When a client attempts to connect, the server accepts the connection, creating a new socket for communication with that specific client.
  3. 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.
  4. URI Routing/Dispatching: The server examines the requested URI and HTTP method to determine which internal function (handler) should process the request.
  5. Request Handling: The designated handler function executes, performing actions like reading sensor data, controlling GPIOs, or preparing content to be sent back.
  6. 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).
  7. Sending Response: The server transmits the HTTP response back to the client over the established connection.
  8. Connection Handling: The connection might be closed after the response or kept alive for further requests, depending on HTTP headers (Connection: keep-alive or Connection: 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 if max_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.
C
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.
C
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.
    Handler function signature: 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.
C
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() and httpd_req_get_hdr_value_str(): To get request header values.
    • httpd_req_get_url_query_len() and httpd_req_get_url_query_str(): To get the query string part of the URI (e.g., param1=value1&param2=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 the Content-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:
C
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

Bash
# 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.

C
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):

C
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):

C
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:

  1. ESP-IDF v5.x is set up with VS Code.
  2. Your ESP32 board is connected.
  3. Wi-Fi is configured (the examples will use STA mode; adapt wifi_init_sta() from Chapter 111 or your own Wi-Fi connection logic).
  4. 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.)

C
#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):

C
// ... (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:

  1. Save as main/main.c. Update WIFI_SSID and WIFI_PASS.
  2. Ensure main/CMakeLists.txt is: idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")
  3. Build: idf.py build

Run/Flash/Observe Steps:

  1. Flash: idf.py -p /dev/ttyUSB0 flash (replace port if needed).
  2. Monitor: idf.py -p /dev/ttyUSB0 monitor. Note the IP address logged by the ESP32.
  3. Open a web browser and navigate to http://<ESP32_IP_ADDRESS>/ and http://<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).

C
// ... (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:

  1. Modify app_main to call configure_led() and ensure the new handlers are registered in start_webserver.
  2. Build, flash, and monitor.
  3. Navigate to http://<ESP32_IP_ADDRESS>/led. You should see the control page.
  4. 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 with Location: /led) would be a common pattern to refresh the page.

Note on state in led_control_html_get_handler: The example uses a static variable led_state within the GET handler for simplicity. The POST handler attempts to update this via user_ctx which is a simplification. In more complex applications, shared state should be managed more carefully, perhaps using the user_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:

  1. Generate server.crt and server.key using OpenSSL (as described in the Theory section).
  2. Place these files in your main directory.
  3. 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):

C
// ... (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:

  1. Generate server.crt and server.key, place them in main/, and update CMakeLists.txt.
  2. Build, flash, monitor.
  3. Open a browser and navigate to https://<ESP32_IP_ADDRESS>/.
  4. 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)”).
  5. 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 and httpd_ssl_config_t.httpd.stack_size should be adequate (e.g., 4096-8192 bytes, or more for complex handlers).
    • max_open_sockets (in httpd_config_t or httpd_ssl_config_t.httpd) impacts RAM. Reduce for constrained devices. Default is 7 for HTTP, 4 for HTTPD_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

  1. Custom 404 Error Page:
    • Create a new URI handler that is registered with httpd_register_err_handler() for HTTPD_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.
  2. 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.
  3. 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 to application/json.
    • Test this endpoint using a browser or a tool like curl or Postman.
  4. 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.
  5. AP Mode Configuration Server:
    • Modify your wifi_init_sta() to wifi_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.

Summary

  • The ESP32 can host HTTP/HTTPS web servers using the esp_http_server component.
  • Server setup involves httpd_config_t (for HTTP) or httpd_ssl_config_t (for HTTPS) and starting with httpd_start() or httpd_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 use httpd_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

Leave a Comment

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

Scroll to Top