Code: Untitled.cpp Select all
#ifndef HTTP_H
#define HTTP_H
#include <string>
#include "sdkconfig.h"
#include "esp_littlefs.h"
#include "esp_http_server.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include <mutex>
#include <unordered_map>
#define PARTITION "storage"
class Http {
public:
Http();
~Http();
void Start();
void Stop();
private:
static constexpr const char* TAG = "HttpServer";
static constexpr size_t CONFIG_MIN_ASYNC_REQUESTS = 2;
static constexpr size_t WORKER_STACK_SIZE = 4096;
static constexpr size_t WORKER_PRIORITY = 2;
static constexpr size_t FILE_BUFFER_SIZE = 1024;
httpd_handle_t m_server;
QueueHandle_t m_request_queue;
SemaphoreHandle_t m_worker_ready_count;
TaskHandle_t m_worker_handles[CONFIG_MAX_ASYNC_REQUESTS];
std::mutex m_mutex;
size_t m_current_workers;
bool ForkWorker();
void KillWorker();
void ProcessRequest(httpd_req_t* req);
esp_err_t UniversalHandler(httpd_req_t* req);
static esp_err_t AsyncReqHandler(httpd_req_t* req);
static void RequestHandleWorker(void* arg);
};
#endif // HTTP_H
#include "http.h"
#include <esp_log.h>
#include <esp_littlefs.h>
#include <cstring>
#include <sys/stat.h>
#include <unordered_map>
Http::Http() : m_current_workers(0) {
esp_log_level_set("httpd", ESP_LOG_DEBUG);
// 初始化请求队列和信号量
m_request_queue = xQueueCreate(10, sizeof(httpd_req_t*));
m_worker_ready_count = xSemaphoreCreateCounting(CONFIG_MAX_ASYNC_REQUESTS, 0);
// 挂载 LittleFS 文件系统
esp_vfs_littlefs_conf_t conf = {
.base_path = "/web",
.partition_label = PARTITION,
.format_if_mount_failed = false,
.dont_mount = false,
};
esp_err_t ret = esp_vfs_littlefs_register(&conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount LittleFS: %s", esp_err_to_name(ret));
return;
}
// 打印文件系统信息
size_t total = 0, used = 0;
ret = esp_littlefs_info(conf.partition_label, &total, &used);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "LittleFS mounted: total=%d, used=%d", total, used);
} else {
ESP_LOGE(TAG, "Failed to get LittleFS info: %s", esp_err_to_name(ret));
}
// 配置 HTTP 服务器
httpd_config_t httpd_conf = HTTPD_DEFAULT_CONFIG();
httpd_conf.uri_match_fn = httpd_uri_match_wildcard;
httpd_conf.max_uri_handlers = 10;
httpd_conf.keep_alive_enable = true;
httpd_conf.keep_alive_idle = 3;
httpd_conf.enable_so_linger = true;
httpd_conf.lru_purge_enable = true;
httpd_conf.max_open_sockets = CONFIG_MAX_ASYNC_REQUESTS;
ESP_ERROR_CHECK(httpd_start(&m_server, &httpd_conf));
// 注册万能 URI 处理器
httpd_uri_t universal_handler = {
.uri = "*",
.method = HTTP_GET,
.handler = AsyncReqHandler,
.user_ctx = this,
};
ESP_ERROR_CHECK(httpd_register_uri_handler(m_server, &universal_handler));
}
Http::~Http() {
Stop();
httpd_stop(m_server);
esp_vfs_littlefs_unregister(PARTITION);
vQueueDelete(m_request_queue);
vSemaphoreDelete(m_worker_ready_count);
}
bool Http::ForkWorker() {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_current_workers <= CONFIG_MAX_ASYNC_REQUESTS)
{
for (int i = 0; i < CONFIG_MAX_ASYNC_REQUESTS; i++) {
if (m_worker_handles[i] == nullptr) {
if (xTaskCreate(RequestHandleWorker, "AsyncWorker", WORKER_STACK_SIZE, this, WORKER_PRIORITY, &m_worker_handles[i])) {
m_current_workers++;
ESP_LOGI(TAG, "Worker created. Current: %d", m_current_workers);
return true;
}
break;
}
}
}
return false;
}
void Http::KillWorker() {
std::unique_lock<std::mutex> lock(m_mutex);
if (m_current_workers > CONFIG_MIN_ASYNC_REQUESTS) {
m_current_workers--;
xSemaphoreTake(m_worker_ready_count, 0);
TaskHandle_t task = xTaskGetCurrentTaskHandle();
for (int i = 0; i < CONFIG_MAX_ASYNC_REQUESTS; i++) {
if (task == m_worker_handles[i]) {
m_worker_handles[i] = nullptr;
break;
}
}
ESP_LOGI(TAG, "Worker killed. Current: %d", m_current_workers);
lock.unlock();
vTaskDelete(nullptr);
}
}
void Http::Start() {
for (int i = 0; i < CONFIG_MIN_ASYNC_REQUESTS; i++) {
ForkWorker();
}
}
void Http::Stop() {
for (int i = 0; i < CONFIG_MAX_ASYNC_REQUESTS; i++) {
if (m_worker_handles[i] != nullptr) {
vTaskDelete(m_worker_handles[i]);
m_worker_handles[i] = nullptr;
}
}
}
esp_err_t Http::AsyncReqHandler(httpd_req_t* req) {
return static_cast<Http*>(req->user_ctx)->UniversalHandler(req);
}
esp_err_t Http::UniversalHandler(httpd_req_t* req) {
httpd_req_t* copy = nullptr;
if (httpd_req_async_handler_begin(req, ©) != ESP_OK) {
ESP_LOGI(TAG, "Failed to begin async handler");
return ESP_FAIL;
}
// 检查工作线程是否就绪
// if (xSemaphoreTake(m_worker_ready_count, 0) == pdFALSE) {
// if (!ForkWorker() && xSemaphoreTake(m_worker_ready_count, pdMS_TO_TICKS(100)) == pdFALSE) {
// httpd_req_async_handler_complete(copy);
// ESP_LOGW(TAG, "Take Semaphore Failed, workers: %d, uri:%s", m_current_workers, req->uri);
// return ESP_FAIL;
// }
// }
// 将请求加入队列
if (xQueueSend(m_request_queue, ©, pdMS_TO_TICKS(20)) == pdFALSE) {
httpd_req_async_handler_complete(copy);
ESP_LOGI(TAG, "Failed to add request to queue");
return ESP_FAIL;
} else {
ESP_LOGI(TAG, "Send to queue success, %s", req->uri);
if(uxQueueMessagesWaiting(m_request_queue) > 1)
ForkWorker();
}
return ESP_OK;
}
void Http::ProcessRequest(httpd_req_t* req) {
// 1. 构建文件路径
std::string uri = req->uri;
if (uri == "/" || uri.empty()) uri = "/index.html";
std::string full_path = "/web" + uri;
// 2. 安全校验
if (uri.find("..") != std::string::npos) {
httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Invalid path");
return;
}
// 3. 检查文件是否存在
struct stat st;
if (stat(full_path.c_str(), &st) != 0) {
ESP_LOGE(TAG, "File not found: %s", full_path.c_str());
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File not found");
return;
}
// 4. 设置 MIME 类型
static const std::unordered_map<std::string, const char*> mime_map = {
{".html", "text/html"},
{".css", "text/css"},
{".js", "application/javascript"},
{".ico", "image/x-icon"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
};
const char* ext = strrchr(uri.c_str(), '.');
const char* mime = ext ? (mime_map.count(ext) ? mime_map.at(ext) : "text/plain") : "text/html";
httpd_resp_set_type(req, mime);
// 5. 分块传输文件内容
FILE* fp = fopen(full_path.c_str(), "rb");
if (!fp) {
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Failed to open file");
return;
}
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char buffer[FILE_BUFFER_SIZE];
size_t read_size;
while ((read_size = fread(buffer, 1, FILE_BUFFER_SIZE, fp)) > 0) {
if (httpd_resp_send_chunk(req, buffer, read_size) != ESP_OK) {
ESP_LOGE(TAG, "Failed to send chunk, file: %s", full_path.c_str());
break;
}
if(read_size < FILE_BUFFER_SIZE) {
if(feof(fp)) {
// 结束分块传输
httpd_resp_send_chunk(req, nullptr, 0);
ESP_LOGI(TAG, "File sent: %s", full_path.c_str());
break;
} else if (ferror(fp)) {
httpd_resp_send_chunk(req, nullptr, 0);
ESP_LOGE(TAG, "Error reading file: %s", full_path.c_str());
break;
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
fclose(fp);
}
void Http::RequestHandleWorker(void* arg) {
Http* http = static_cast<Http*>(arg);
httpd_req_t* req = nullptr;
xSemaphoreGive(http->m_worker_ready_count);
while (true) {
if (xQueueReceive(http->m_request_queue, &req, pdMS_TO_TICKS(60*1000)) == pdFALSE) {
http->KillWorker();
continue;
}
http->ProcessRequest(req);
httpd_req_async_handler_complete(req);
}
ESP_LOGE(TAG, "Error, worker run here!!!");
vTaskDelete(nullptr);
}background-image: url("../img/gear_fff.png");
}),会出现一个很奇怪的现象:wireshark抓包显示已经对该资源(gear_fff.png)发出GET请求了,但是ESP32没有响应。连http的匹配函数都没有被调用!!!但是,使用curl手动请求1个资源(不论是否存在),这个请求会马上被匹配函数处理(如果不手动请求资源,这个图片需要约30s才会被匹配函数处理)!
资源处理不正确(响应慢,像是请求到达后,没有正确流转到http的处理器)调试信息:
I (15224) HttpServer: Send to queue success, /index.html
I (15234) HttpServer: File sent: /web/index.html
I (15314) HttpServer: Send to queue success, /js/index.js
I (15314) HttpServer: Send to queue success, /css/pomp.css
I (15424) HttpServer: File sent: /web/js/index.js
I (15534) HttpServer: File sent: /web/css/pomp.css
I (273464) HttpServer: Send to queue success, /img/gear_fff.png
I (273464) HttpServer: Send to queue success, /index.html22-----------手动请求1个不存在的资源(curl http://SER_IP/index.html22)
I (273524) HttpServer: File sent: /web/img/gear_fff.png
E (273524) HttpServer: File not found: /web/index.html22
W (273524) httpd_txrx: httpd_resp_send_err: 404 Not Found - File not found