Программирование ARM ESP-IDF HTTP Server Tue, July 28 2026  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


ESP-IDF HTTP Server Печать
Добавил(а) microsin   

Компонент HTTP Server (esp_http_server) предоставляет возможность запуска облегченного web-сервера на ESP32-C3 (или другом микроконтроллере Espressif, на котором реализован сетевой интерфейс). Здесь приведен перевод оригинальной документации [1].

Выполните следующие шаги для использования API-функций, предоставляемых HTTP Server:

httpd_start(): создаст экземпляр сервера HTTP, выделит для этого память/ресурсы в зависимости от указанной конфигурации, и вернет дескриптор экземпляра сервера. Сервер содержит в себе две составляющие: сокет прослушивания (listening socket, TCP) для HTTP-трафика, и сокет управления (control socket, UDP) для управляющих сигналов, обработка которых осуществляется по принципу round robin в цикле задачи сервера.

Фраза "which are selected in a round robin fashion in the server task loop" оригинальной документации [1] описывает, как внутренний цикл обработки HTTP-сервера ESP-IDF распределяет процессорное время между двумя типами сокетов:

Listening socket (TCP) – принимает входящие HTTP-соединения от клиентов.
Control socket (UDP) – принимает управляющие сигналы (например, команду остановить сервер, изменить параметры или перезагрузить конфигурацию).

Round robin означает, что серверная задача (поток) поочерёдно, циклически опрашивает каждый из этих сокетов на наличие событий (данных для чтения, новых подключений и т.п.). Вместо того чтобы, например, блокироваться на одном сокете до появления данных, а затем переходить к другому, сервер равномерно распределяет время между ними.

На практике это обычно реализуется с помощью системного вызова select() или poll() с таймаутом, либо просто циклом с неблокирующими сокетами: на каждой итерации проверяется сначала TCP-сокет, затем UDP-сокет, потом снова TCP и так далее. Такой подход гарантирует, что:

- Входящие HTTP-запросы обрабатываются своевременно, без длительных задержек.
- Управляющие команды (например, остановка сервера) также получают шанс быть обработанными в разумные сроки, даже если TCP-трафик очень интенсивен.
- Ни один из каналов не "голодает" – оба обслуживаются справедливо.

Иными словами, это механизм псевдопараллельной обработки двух независимых потоков событий в рамках одного цикла, что упрощает архитектуру и избавляет от необходимости создавать отдельные потоки для управления и данных.

Приоритет задачи и размер стека конфигурируется в момент создания экземпляра сервера путем передачи в функцию httpd_start() структуры httpd_config_t. Трафик TCP парсится как HTTP-запросы, и в зависимости от выданного клиентом запрошенной ссылки URI, программист регистрирует необходимые обработчики, вовлекаемые для отправки обратно пакетов HTTP-ответа.

httpd_stop(): остановит сервер по указанному дескриптору и освободит связанные с ним память и ресурсы. Это блокирующая функция, которая сначала посылает сигнал остановки (halt) задаче сервера, и затем ждет завершения этой задачи. При остановке задача сервера закрывает все открытые сетевые соединения, удаляет зарегистрированные URI-обработчики и сбрасывает данные контекста всех сессий.

httpd_register_uri_handler(): URI-обработчик, зарегистрированный путем передачи объекта типа структуры httpd_uri_t, в полях которой находится имя ссылки (uri name), тип метода (например HTTP_GET/HTTP_POST/HTTP_PUT и т. п.), указатель на функцию типа esp_err_t *handler (httpd_req_t *req) и указатель user_ctx на данные контекста пользователя (user context data).

Примечание: API-функции сервера HTTP не являются потокобезопасными (not thread-safe). Если требуется безопасность для потока (thread safety), то это должно обеспечиваться на уровне приложения для обеспечения правильной синхронизации между несколькими потоками.

Примеры приложений:

protocols/http_server/simple - демонстрирует, как обрабатывать контент произвольной длины, читать заголовки запроса и параметры URL-запроса, и как устанавливать заголовки ответа.

protocols/http_server/advanced_tests - демонстрирует, как использовать HTTP-сервер для продвинутого тестирования.

Persistent Connections. HTTP-сервер обеспечивает постоянные соединения (persistent connections), позволяющие повторно использовать то же самое соединение (сессию) для нескольких передач, с обеспечением поддержки специфичных данных контекста для сессии. Данные контекста могут выделяться динамически обработчиком, в этом случае требуется пользовательская функция для обеспечения освобождения этих данных, когда соединение/сессия закрывается.

1. Постоянные соединения (Persistent Connections / Keep-Alive)

Обычно в классическом HTTP/1.0 после каждого ответа сервер закрывал TCP-сокет. Чтобы получить следующую картинку или CSS-файл, браузеру приходилось открывать новое TCP-соединение (а это 3 пакета рукопожатия + медленный старт).

В ESP-IDF HTTP Server поддерживается режим Keep-Alive. Это означает, что после отправки ответа на первый запрос сервер не закрывает сокет, а оставляет его открытым. Клиент может отправить второй, третий запросы по этому же самому физическому каналу. Это сильно экономит ресурсы CPU и время в сетях с высокой задержкой.

2. Контекст сессии (Session Context)

Раз соединение живёт дольше одного запроса, у разработчика появляется возможность привязать к этому конкретному сокету пользовательские данные.

Представьте ситуацию: клиент (например, ESP32-устройство) подключается, отправляет логин/пароль в первом запросе. Вы проверяете данные и сохраняете в контексте сессии флаг is_authenticated = true и, скажем, идентификатор устройства. Когда следом приходит второй запрос (например, «передать показания датчиков») по этому же соединению, ваш обработчик (handler) может заглянуть в контекст сессии, увидеть, что устройство уже авторизовано, и сразу отдать данные, минуя повторную аутентификацию.

Технически это реализовано через httpd_session_ctx_get() и httpd_session_ctx_set(), где в качестве контекста выступает указатель void * на вашу собственную структуру.

3. Динамическое выделение памяти и пользовательская функция очистки (самый важный пункт)

Поскольку контекст сессии — это просто указатель, сервер понятия не имеет, что там лежит. Вы можете сохранить туда целое число, а можете выделить под данные целый блок памяти через malloc() (например, под буфер для накопления входящих JSON-фрагментов или под массив истории команд).

[Очистка мусора]

Здесь возникает проблема: кто и когда будет чистить этот мусор?

Сервер управляет временем жизни сокета. Когда клиент отключается, происходит таймаут бездействия, или сервер перезагружается — сокет закрывается. Если сервер просто закроет сокет и забудет про ваш указатель, выделенная вами память утечёт (memory leak). Для микроконтроллеров с малым объёмом RAM это катастрофа.

Чтобы этого избежать, документация требует, чтобы вы предоставили специальную функцию-колбэк (callback) для освобождения ресурсов. Вы регистрируете эту функцию при создании сессии или установке контекста. Как только сервер решает, что сессия завершена (сокет закрыт), он автоматически вызовет вашу функцию, передав в неё указатель на контекст. Внутри этой функции вы обязаны вызвать free() для динамически выделенных полей вашей структуры (и саму структуру, если она была в куче).

Примерная схема на языке C (псевдокод) для наглядности:

// Ваша структура контекста
typedef struct {
char user_id[32];
int counter;
void *temp_buffer; // динамически выделенный буфер } my_session_ctx_t;

// Функция очистки - её вызовет сервер при закрытии сокета
void free_session_ctx(void *ctx) {
my_session_ctx_t *my_ctx = (my_session_ctx_t *)ctx;
if (my_ctx->temp_buffer) {
free(my_ctx->temp_buffer); // освобождаем вложенный буфер
}
free(my_ctx); // освобождаем саму структуру }

// В обработчике запроса esp_err_t my_handler(httpd_req_t *req) {
// Выделяем память под контекст для этого соединения (если ещё не выделена)
my_session_ctx_t *ctx = httpd_session_ctx_get(req);
if (ctx == NULL) {
ctx = calloc(1, sizeof(my_session_ctx_t));
ctx->temp_buffer = malloc(1024);
// Устанавливаем контекст и указываем, как его освобождать
httpd_session_ctx_set(req, ctx, free_session_ctx);
}
// ... работаем с ctx ...
return ESP_OK; }

Итог: это архитектурное решение даёт вам гибкость Stateful-общения поверх stateless-протокола HTTP, но перекладывает на вас ответственность за управление памятью, предоставляя чёткий механизм (callback) для её корректной очистки, чтобы embedded-система работала стабильно и без утечек.

Пример постоянных соединений:

/* Пользовательская функция для освобождения контекста */
void free_ctx_func(void *ctx) {
/* Тут могут быть и другие варианты освобождения выделенных ресурсов */
free(ctx); }
esp_err_t adder_post_handler(httpd_req_t *req) {
/* Создание контекста сессии, если это еще не сделано */
if (! req->sess_ctx) {
// Указатель на данные пользователя:
req->sess_ctx = malloc(sizeof(ANY_DATA_TYPE));
// Функция для освобождения данных контекста:
req->free_ctx = free_ctx_func;
}

/* Доступ к данным контекста */
ANY_DATA_TYPE *ctx_data = (ANY_DATA_TYPE *)req->sess_ctx;

/* Ответ на запрос req: */
...............
...............
...............

return ESP_OK; }

Просмотрите пример в коде protocols/http_server/persistent_sockets. Этот пример демонстрирует, как настроить и использовать HTTP-сервер с поддержкой persistent sockets, позволяя иметь для каждого клиента независимые сессии или контексты.

[WebSocket Server]

Компонент HTTP-сервера предоставляет поддержку WebSocket [2]. Фича WebSocket может быть разрешена в menuconfig опцией CONFIG_HTTPD_WS_SUPPORT.

Проект protocols/http_server/ws_echo_server демонстрирует, как создавать WebSocket echo-сервер с использованием HTTP-сервера, который стартует в локально сети и требует для взаимодействия WebSocket-клиента, отправляя обратно принятые кадры WebSocket.

WebSocket Pre-Handshake Callback. Компонент HTTP-сервера предоставляет функцию обратного вызова, которая вызывается перед рукопожатием (pre-handshake callback) для конечных точек WebSocket. Этот callback запускается перед обработкой WebSocket handshake - в этот момент соединение все еще является соединением HTTP, и пока не прошло апгрейд до протокола WebSocket.

Pre-handshake callback может использоваться для аутентификации, авторизации, или для других проверок. Если callback возвратил ESP_OK, то произойдет переход к WebSocket handshake. Если callback возвратил любое другое значение, то handshake обрывается (aborted), и соединение будет закрыто.

Чтобы использовать WebSocket pre-handshake callback, вы должны разрешить опцию CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT в конфигурации вашего проекта.

WebSocket Post-Handshake Callback. Подобно pre-handshake callback, компонент HTTP-сервера также предоставляет функцию обратного вызова, вызываемую после рукопожатия (post-handshake callback) для конечных точек WebSocket. Этот callback запускается после того, как был обработан WebSocket.

В этой точке соединение было продвинуто (upgraded) до состояния WebSocket, и сервер ответил WebSocket handshake response. Этот post handshake callback может использоваться для лога, отправки начальных сообщений, или другие задачи настройки.

Для использования WebSocket post-handshake callback вы должны разрешить опцию CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT в конфигурации вашего проекта.

static esp_err_t ws_auth_handler(httpd_req_t *req)
{
// Здесь находится ваша логика аутентификации. Возвращенное
// значение ESP_OK разрешает handshake, другое значение
// отменяет соединение.
return ESP_OK; }

// Регистрация обработчика WebSocket URI с pre-handshake аутентификацией:
static const httpd_uri_t ws = {
.uri = "/ws",
.method = HTTP_GET,
.handler = handler, // Ваш обработчик данных WebSocket
.user_ctx = NULL,
.is_websocket = true,
.ws_pre_handshake_cb = ws_auth_handler // Установка pre-handshake callback };

// Регистрация обработчика после старта сервера: httpd_register_uri_handler(server, &ws);

Event Handling. ESP HTTP-сервер поддерживает различные события (events), для которых библиотека Event Loop может вызвать обработчик, когда возникнет определенное событие. Обработчик необходимо зарегистрировать с помощью функции esp_event_handler_register(). Это помогает в обработке событий (event handling) для ESP HTTP server.

Перечисление esp_http_server_event_id_t описывает все события, которые могут произойти для ESP HTTP server.

Ожидаемые типы данных для различных событий ESP HTTP server в цикле событий (event loop):

HTTP_SERVER_EVENT_ERROR : httpd_err_code_t
HTTP_SERVER_EVENT_START : NULL
HTTP_SERVER_EVENT_ON_CONNECTED : int
HTTP_SERVER_EVENT_ON_HEADER : int
HTTP_SERVER_EVENT_HEADERS_SENT : int
HTTP_SERVER_EVENT_ON_DATA : esp_http_server_event_data
HTTP_SERVER_EVENT_SENT_DATA : esp_http_server_event_data
HTTP_SERVER_EVENT_DISCONNECTED : int
HTTP_SERVER_EVENT_STOP : NULL

File Serving. Пример protocols/http_server/file_serving демонстрирует, как создать простой файловый HTTP-сервер, с функционалом выгрузки (upload) и загрузки (download).

Captive Portal. Пример protocols/http_server/captive_portal демонстрирует два метода создания captive portal, который направляет пользователей на страницу аутентификации перед предоставлением доступа к контенту, с использованием либо запросов DNS и перенаправления запросов HTTP, либо современный метод, применяющий поле в предложении DHCP.

Asynchronous Handlers. Пример protocols/http_server/async_handlers демонстрирует, как обработать несколько долго работающих одновременных запросов на HTTP-сервере, с использованием различных URI для асинхронных запросов (asynchronous requests), быстрых запросов (quick requests) и страницы index.

URI Handlers. HTTP-сервер позволяет вам зарегистрировать обработчики ссылок (URI handlers) для обработки различных запросов HTTP. Каждый URI-обработчик связывается с определенным URI и методом HTTP (GET, POST, и т. п.). Функция-обработчик вызывается при каждом получении запроса, соответствующего URI и методу.

Функция обработчика должна возвращать значение типа esp_err_t.

esp_err_t my_uri_handler(httpd_req_t *req)
{
// Обработка запроса
// ...

// Возврат ESP_OK, если запрос был успешно обработан
return ESP_OK;

// Возврат кода ошибки для закрытия соединения
// return ESP_FAIL; }

void register_uri_handlers(httpd_handle_t server) {
httpd_uri_t my_uri = {
.uri = "/my_uri",
.method = HTTP_GET,
.handler = my_uri_handler,
.user_ctx = NULL
};

httpd_register_uri_handler(server, &my_uri); }

В этом примере функция my_uri_handler обрабатывает запросы для URI-ссылки /my_uri. Если обработчик возвратил ESP_OK, то соединение остается открытым. Если же будет возвращено любое другое значение, то соединение закрывается. Такое поведение позволяет приложению управлять закрытием соединения на основе определенных событий или условий.

[Справочник API]

Декларация функций содержится в заголовке components/esp_http_server/include/esp_http_server.h, который подключается директивой:

#include "esp_http_server.h"

Этот заголовочный файл описывает часть API, предоставляемую компонентом esp_http_server. Для декларации, что ваш компонент (например приложение, которое тоже является компонентом) зависит от esp_http_server, добавьте следующее в свой CMakeLists.txt:

REQUIRES esp_http_server

или:

PRIV_REQUIRES esp_http_server

В следующей таблице приведено общее описание функций. Полное описание API-функций и используемых типов, макросов, перечислений и структур см. в документации [1].

Функция Описание
httpd_start Запустит web-сервер.
httpd_stop Остановит web-сервер.
httpd_sess_set_recv_override Переопределение функции приема web-сервера (по файловому дескриптору сеанса, session FD).
httpd_sess_set_send_override Переопределение функции отправки web-сервера (по файловому дескриптору сеанса, session FD).
httpd_sess_set_pending_override Переопределение ожидающую выполнения функцию web-сервера (по файловому дескриптору сеанса, session FD).
httpd_req_async_handler_begin Запустит асинхронный запрос. Эту функцию можно вызвать в обработчике запроса для получения копии запроса, которую можно использовать на асинхронном потоке.
httpd_req_async_handler_complete Пометит асинхронный запрос как завершенный.
httpd_req_to_sockfd Получение дескриптора сокета (Socket Descriptor) из запроса HTTP.
httpd_req_recv API для чтения данных контента из HTTP-запроса.
httpd_req_get_hdr_value_len Ищет поле в заголовках запроса и возвращает длину строки его значения.
httpd_req_get_hdr_value_str Получение значения строки поля из заголовков запроса.
httpd_req_get_url_query_len Получение длины строки запроса из URL запроса.
httpd_req_get_url_query_str Получение строки запроса из URL запроса.
httpd_query_key_value Вспомогательная функция для получения параметра из строки запроса вида param1=val1¶m2=val2.
httpd_req_get_cookie_val Получение значения строки из значения куки из заголовков запроса "Cookie" по имени куки.
httpd_uri_match_wildcard Проверка URI на совпадение с предоставленным шаблоном wildcard.
httpd_resp_send API для отправки полного HTTP-ответа.
httpd_resp_send_chunk API для отправки порции данных HTTP (чанка).
httpd_resp_sendstr API для отправки полной строки в виде HTTP ответа.
httpd_resp_sendstr_chunk API для отправки строки как порции (чанка) HTTP-ответа.
httpd_resp_set_status API для установки кода состояния HTTP (status code).
httpd_resp_set_type API для установки типа HTTP-контента.
httpd_resp_set_hdr API для прикрепления дополнительных заголовков.
httpd_resp_send_err Для отправки кода ошибки в ответе на HTTP-запрос.
httpd_resp_send_custom_err Для отправки пользовательского кода ошибки в ответе на HTTP-запрос.
httpd_resp_send_404 Вспомогательная функция для HTTP 404.
httpd_resp_send_408 Вспомогательная функция для HTTP 408.
httpd_resp_send_500 Вспомогательная функция для HTTP 500.
httpd_send Сырая отправка (Raw HTTP send).
httpd_socket_send Низкоуровневая API-функция для отправки данных на указанном сокете.
httpd_socket_recv Низкоуровневая API-функция для приема данных из указанного сокета.
httpd_register_err_handler Функция для регистрации обработчиков ошибок (HTTP error handlers).
httpd_ws_recv_frame Прием и парсинг кадра WebSocket.
httpd_ws_send_frame Конструирование и отправка кадра WebSocket.
httpd_ws_send_frame_async Низкоуровневая отправка кадра WebSocket вне контекста текущего запроса с использованием внутренней функции отправки httpd.
httpd_ws_get_fd_info Проверяет, принадлежит ли переданный дескриптор сокета какому-либо активному клиенту данного экземпляра сервера и активен ли протокол WebSocket.
httpd_ws_send_data Синхронно отправляет данные в указанный WebSocket.
httpd_ws_send_data_async Асинхронно отправляет данные в указанный WebSocket.
httpd_register_uri_handler Регистрирует URI-обработчик.
httpd_unregister_uri_handler Отменяет регистрацию URI-обработчика.
httpd_unregister_uri Отменяет регистрацию всех URI-обработчиков с указанной строкой uri.
httpd_queue_work Поставить в очередь выполнение функции в контексте HTTPD.
httpd_sess_get_ctx Получение контекста сессии из дескриптора сокета.
httpd_sess_set_ctx Установит контекст сессии по дескриптору сокета.
httpd_sess_get_transport_ctx Получение контекста 'транспорта' по дескриптору сокета.
httpd_sess_set_transport_ctx Установит контекст 'транспорта' по дескриптору сокета.
httpd_get_global_user_ctx Получит глобальный контекст пользователя HTTPD (он был задан в структуре конфигурации сервера).
httpd_get_global_transport_ctx Получит глобальный контекст транспорта HTTPD (он был задан в структуре конфигурации сервера).
httpd_sess_trigger_close Инициирует закрытие сеанса httpd извне.
httpd_sess_update_lru_counter Обновит счетчик LRU (Least Recently Used) для заданного сокета.
httpd_get_client_list Возвращает список текущих дескрипторов сокетов активных сеансов.
httpd_get_raw_req_data_len Получение длины необработанных данных запроса, полученных от клиента.
httpd_get_server_state Получение состояния сервера HTTPD.

[Ссылки]

1. ESP-IDF HTTP Server site:espressif.com.
2. Отправка запросов WebSocket.

 

Добавить комментарий


Защитный код
Обновить

Top of Page