| 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-соединения от клиентов. Round robin означает, что серверная задача (поток) поочерёдно, циклически опрашивает каждый из этих сокетов на наличие событий (данных для чтения, новых подключений и т.п.). Вместо того чтобы, например, блокироваться на одном сокете до появления данных, а затем переходить к другому, сервер равномерно распределяет время между ними. На практике это обычно реализуется с помощью системного вызова select() или poll() с таймаутом, либо просто циклом с неблокирующими сокетами: на каждой итерации проверяется сначала TCP-сокет, затем UDP-сокет, потом снова TCP и так далее. Такой подход гарантирует, что: - Входящие HTTP-запросы обрабатываются своевременно, без длительных задержек. Иными словами, это механизм псевдопараллельной обработки двух независимых потоков событий в рамках одного цикла, что упрощает архитектуру и избавляет от необходимости создавать отдельные потоки для управления и данных. Приоритет задачи и размер стека конфигурируется в момент создания экземпляра сервера путем передачи в функцию 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 (псевдокод) для наглядности: // Ваша структура контекста Итог: это архитектурное решение даёт вам гибкость Stateful-общения поверх stateless-протокола HTTP, но перекладывает на вас ответственность за управление памятью, предоставляя чёткий механизм (callback) для её корректной очистки, чтобы embedded-система работала стабильно и без утечек. Пример постоянных соединений: /* Пользовательская функция для освобождения контекста */ Просмотрите пример в коде 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) { 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 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) { В этом примере функция 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].
[Ссылки] 1. ESP-IDF HTTP Server site:espressif.com. |