Компонент 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-запроса, и как устанавливать заголовки ответа.
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 (псевдокод) для наглядности:
// Функция очистки - её вызовет сервер при закрытии сокета voidfree_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_tmy_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 ... returnESP_OK;
}
Итог: это архитектурное решение даёт вам гибкость Stateful-общения поверх stateless-протокола HTTP, но перекладывает на вас ответственность за управление памятью, предоставляя чёткий механизм (callback) для её корректной очистки, чтобы embedded-система работала стабильно и без утечек.
Пример постоянных соединений:
/* Пользовательская функция для освобождения контекста */ voidfree_ctx_func(void*ctx)
{ /* Тут могут быть и другие варианты освобождения выделенных ресурсов */ free(ctx);
}
esp_err_tadder_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: */ ............... ............... ...............
returnESP_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 в конфигурации вашего проекта.
staticesp_err_tws_auth_handler(httpd_req_t*req)
{ // Здесь находится ваша логика аутентификации. Возвращенное // значение ESP_OK разрешает handshake, другое значение // отменяет соединение. returnESP_OK;
}
// Регистрация обработчика WebSocket URI с pre-handshake аутентификацией: staticconsthttpd_uri_tws={ .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.
В этом примере функция my_uri_handler обрабатывает запросы для URI-ссылки /my_uri. Если обработчик возвратил ESP_OK, то соединение остается открытым. Если же будет возвращено любое другое значение, то соединение закрывается. Такое поведение позволяет приложению управлять закрытием соединения на основе определенных событий или условий.
Этот заголовочный файл описывает часть 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
Получение длины необработанных данных запроса, полученных от клиента.