mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-13 13:50:21 +00:00

1. In httpd_req_async_handler_begin, the httpd_req_aux is locally malloced and data is done memcpy to local httpd_req_aux from request'ss httpd_req_aux for async request use-case, this causes scartch pointer from these two structs pointing to same memory address. 2. In current workflow, the request's sratch buffer is freed in httpd_parse.c httpd_req_cleanup api. Therefore if the user try to fetch the data (like headers) from the scratch buffer, data will be not available. 3. Each request should have the deep copy of the scratch buffer. To retrive the data later. Closes https://github.com/espressif/esp-idf/issues/15587
388 lines
12 KiB
C
388 lines
12 KiB
C
/* Async Request Handlers HTTP Server Example
|
|
|
|
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
|
|
|
Unless required by applicable law or agreed to in writing, this
|
|
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
CONDITIONS OF ANY KIND, either express or implied.
|
|
*/
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/semphr.h"
|
|
#include <esp_wifi.h>
|
|
#include <esp_event.h>
|
|
#include <esp_log.h>
|
|
#include <esp_system.h>
|
|
#include <nvs_flash.h>
|
|
#include <sys/param.h>
|
|
#include "nvs_flash.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_eth.h"
|
|
#include "protocol_examples_common.h"
|
|
#include "esp_tls_crypto.h"
|
|
#include <esp_http_server.h>
|
|
|
|
/* An example that demonstrates multiple
|
|
long running http requests running in parallel.
|
|
|
|
In this example, multiple long http request can run at
|
|
the same time. (uri: /long)
|
|
|
|
While these long requests are running, the server can still
|
|
respond to other incoming synchronous requests. (uri: /quick)
|
|
*/
|
|
|
|
#define ASYNC_WORKER_TASK_PRIORITY 5
|
|
#define ASYNC_WORKER_TASK_STACK_SIZE CONFIG_EXAMPLE_ASYNC_WORKER_TASK_STACK_SIZE
|
|
|
|
static const char *TAG = "example";
|
|
|
|
// Async requests are queued here while they wait to
|
|
// be processed by the workers
|
|
static QueueHandle_t request_queue;
|
|
|
|
// Track the number of free workers at any given time
|
|
static SemaphoreHandle_t worker_ready_count;
|
|
|
|
// Each worker has its own thread
|
|
static TaskHandle_t worker_handles[CONFIG_EXAMPLE_MAX_ASYNC_REQUESTS];
|
|
|
|
typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req);
|
|
|
|
typedef struct {
|
|
httpd_req_t* req;
|
|
httpd_req_handler_t handler;
|
|
} httpd_async_req_t;
|
|
|
|
// queue an HTTP req to the worker queue
|
|
static esp_err_t queue_request(httpd_req_t *req, httpd_req_handler_t handler)
|
|
{
|
|
// must create a copy of the request that we own
|
|
httpd_req_t* copy = NULL;
|
|
esp_err_t err = httpd_req_async_handler_begin(req, ©);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
httpd_async_req_t async_req = {
|
|
.req = copy,
|
|
.handler = handler,
|
|
};
|
|
|
|
// How should we handle resource exhaustion?
|
|
// In this example, we immediately respond with an
|
|
// http error if no workers are available.
|
|
int ticks = 0;
|
|
|
|
// counting semaphore: if success, we know 1 or
|
|
// more asyncReqTaskWorkers are available.
|
|
if (xSemaphoreTake(worker_ready_count, ticks) == false) {
|
|
ESP_LOGE(TAG, "No workers are available");
|
|
httpd_req_async_handler_complete(copy); // cleanup
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
// Since worker_ready_count > 0 the queue should already have space.
|
|
// But lets wait up to 100ms just to be safe.
|
|
if (xQueueSend(request_queue, &async_req, pdMS_TO_TICKS(100)) == false) {
|
|
ESP_LOGE(TAG, "worker queue is full");
|
|
httpd_req_async_handler_complete(copy); // cleanup
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/* handle long request (on async thread) */
|
|
static esp_err_t long_async(httpd_req_t *req)
|
|
{
|
|
char* buf;
|
|
size_t buf_len;
|
|
|
|
/* Get URI */
|
|
ESP_LOGI(TAG, "Request URI: %s", req->uri);
|
|
|
|
/* Get header value string length and allocate memory for length + 1,
|
|
* extra byte for null termination */
|
|
buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
|
|
if (buf_len > 1) {
|
|
buf = malloc(buf_len);
|
|
if (buf == NULL) {
|
|
ESP_LOGE(TAG, "Failed to allocate memory for headers");
|
|
return ESP_FAIL;
|
|
}
|
|
/* 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);
|
|
}
|
|
/* Get query string length and allocate memory for length + 1,
|
|
* extra byte for null termination */
|
|
buf_len = httpd_req_get_url_query_len(req) + 1;
|
|
if (buf_len > 1) {
|
|
buf = malloc(buf_len);
|
|
if (buf == NULL) {
|
|
ESP_LOGE(TAG, "Failed to allocate memory for query string");
|
|
return ESP_FAIL;
|
|
}
|
|
/* Copy null terminated query string into buffer */
|
|
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
|
|
ESP_LOGI(TAG, "Found query string => %s", buf);
|
|
}
|
|
free(buf);
|
|
}
|
|
|
|
// track the number of long requests
|
|
static uint8_t req_count = 0;
|
|
req_count++;
|
|
|
|
// send a request count
|
|
char s[100];
|
|
snprintf(s, sizeof(s), "<div>req: %u</div>\n", req_count);
|
|
esp_err_t err = httpd_resp_sendstr_chunk(req, s);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to send string chunk: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
// then every second, send a "tick"
|
|
for (int i = 0; i < 60; i++) {
|
|
|
|
// This delay makes this a "long running task".
|
|
// In a real application, this may be a long calculation,
|
|
// or some IO dependent code for instance.
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
|
|
// send a tick
|
|
snprintf(s, sizeof(s), "<div>%u</div>\n", i);
|
|
err = httpd_resp_sendstr_chunk(req, s);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to send string chunk: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
}
|
|
|
|
// send "complete"
|
|
err = httpd_resp_sendstr_chunk(req, NULL);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to send string chunk: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
// each worker thread loops forever, processing requests
|
|
static void worker_task(void *p)
|
|
{
|
|
ESP_LOGI(TAG, "starting async req task worker");
|
|
|
|
while (true) {
|
|
|
|
// counting semaphore - this signals that a worker
|
|
// is ready to accept work
|
|
xSemaphoreGive(worker_ready_count);
|
|
|
|
// wait for a request
|
|
httpd_async_req_t async_req;
|
|
if (xQueueReceive(request_queue, &async_req, portMAX_DELAY)) {
|
|
|
|
ESP_LOGI(TAG, "invoking %s", async_req.req->uri);
|
|
|
|
// call the handler
|
|
async_req.handler(async_req.req);
|
|
|
|
// Inform the server that it can purge the socket used for
|
|
// this request, if needed.
|
|
if (httpd_req_async_handler_complete(async_req.req) != ESP_OK) {
|
|
ESP_LOGE(TAG, "failed to complete async req");
|
|
}
|
|
}
|
|
}
|
|
|
|
ESP_LOGW(TAG, "worker stopped");
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
// start worker threads
|
|
static void start_workers(void)
|
|
{
|
|
|
|
// counting semaphore keeps track of available workers
|
|
worker_ready_count = xSemaphoreCreateCounting(
|
|
CONFIG_EXAMPLE_MAX_ASYNC_REQUESTS, // Max Count
|
|
0); // Initial Count
|
|
if (worker_ready_count == NULL) {
|
|
ESP_LOGE(TAG, "Failed to create workers counting Semaphore");
|
|
return;
|
|
}
|
|
|
|
// create queue
|
|
request_queue = xQueueCreate(1, sizeof(httpd_async_req_t));
|
|
if (request_queue == NULL){
|
|
ESP_LOGE(TAG, "Failed to create request_queue");
|
|
vSemaphoreDelete(worker_ready_count);
|
|
return;
|
|
}
|
|
|
|
// start worker tasks
|
|
for (int i = 0; i < CONFIG_EXAMPLE_MAX_ASYNC_REQUESTS; i++) {
|
|
|
|
bool success = xTaskCreate(worker_task, "async_req_worker",
|
|
ASYNC_WORKER_TASK_STACK_SIZE, // stack size
|
|
(void *)0, // argument
|
|
ASYNC_WORKER_TASK_PRIORITY, // priority
|
|
&worker_handles[i]);
|
|
|
|
if (!success) {
|
|
ESP_LOGE(TAG, "Failed to start asyncReqWorker");
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* adds /long request to the request queue */
|
|
static esp_err_t long_handler(httpd_req_t *req)
|
|
{
|
|
ESP_LOGI(TAG, "uri: /long");
|
|
|
|
// add to the async request queue
|
|
if (queue_request(req, long_async) == ESP_OK) {
|
|
return ESP_OK;
|
|
} else {
|
|
httpd_resp_set_status(req, "503 Busy");
|
|
httpd_resp_sendstr(req, "<div> no workers available. server busy.</div>");
|
|
return ESP_OK;
|
|
}
|
|
}
|
|
|
|
/* A quick HTTP GET handler, which does not
|
|
use any asynchronous features */
|
|
static esp_err_t quick_handler(httpd_req_t *req)
|
|
{
|
|
ESP_LOGI(TAG, "uri: /quick");
|
|
char s[100];
|
|
snprintf(s, sizeof(s), "random: %u\n", rand());
|
|
httpd_resp_sendstr(req, s);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t index_handler(httpd_req_t *req)
|
|
{
|
|
ESP_LOGI(TAG, "uri: /");
|
|
const char* html = "<div><a href=\"/long\">long</a></div>"
|
|
"<div><a href=\"/quick\">quick</a></div>";
|
|
httpd_resp_sendstr(req, html);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static httpd_handle_t start_webserver(void)
|
|
{
|
|
httpd_handle_t server = NULL;
|
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
config.lru_purge_enable = true;
|
|
|
|
// It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS
|
|
// Why? This leaves at least one socket still available to handle
|
|
// quick synchronous requests. Otherwise, all the sockets will
|
|
// get taken by the long async handlers, and your server will no
|
|
// longer be responsive.
|
|
config.max_open_sockets = CONFIG_EXAMPLE_MAX_ASYNC_REQUESTS + 1;
|
|
|
|
// Start the httpd server
|
|
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
|
if (httpd_start(&server, &config) != ESP_OK) {
|
|
ESP_LOGI(TAG, "Error starting server!");
|
|
return NULL;
|
|
}
|
|
|
|
const httpd_uri_t index_uri = {
|
|
.uri = "/",
|
|
.method = HTTP_GET,
|
|
.handler = index_handler,
|
|
};
|
|
|
|
const httpd_uri_t long_uri = {
|
|
.uri = "/long",
|
|
.method = HTTP_GET,
|
|
.handler = long_handler,
|
|
};
|
|
|
|
const httpd_uri_t quick_uri = {
|
|
.uri = "/quick",
|
|
.method = HTTP_GET,
|
|
.handler = quick_handler,
|
|
};
|
|
|
|
// Set URI handlers
|
|
ESP_LOGI(TAG, "Registering URI handlers");
|
|
httpd_register_uri_handler(server, &index_uri);
|
|
httpd_register_uri_handler(server, &long_uri);
|
|
httpd_register_uri_handler(server, &quick_uri);
|
|
|
|
return server;
|
|
}
|
|
|
|
static esp_err_t stop_webserver(httpd_handle_t server)
|
|
{
|
|
// Stop the httpd server
|
|
return httpd_stop(server);
|
|
}
|
|
|
|
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
|
int32_t event_id, void* event_data)
|
|
{
|
|
httpd_handle_t* server = (httpd_handle_t*) arg;
|
|
if (*server) {
|
|
ESP_LOGI(TAG, "Stopping webserver");
|
|
if (stop_webserver(*server) == ESP_OK) {
|
|
*server = NULL;
|
|
} else {
|
|
ESP_LOGE(TAG, "Failed to stop http server");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void connect_handler(void* arg, esp_event_base_t event_base,
|
|
int32_t event_id, void* event_data)
|
|
{
|
|
httpd_handle_t* server = (httpd_handle_t*) arg;
|
|
if (*server == NULL) {
|
|
ESP_LOGI(TAG, "Starting webserver");
|
|
*server = start_webserver();
|
|
}
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
static httpd_handle_t server = NULL;
|
|
|
|
ESP_ERROR_CHECK(nvs_flash_init());
|
|
ESP_ERROR_CHECK(esp_netif_init());
|
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
|
|
|
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
|
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
|
* examples/protocols/README.md for more information about this function.
|
|
*/
|
|
ESP_ERROR_CHECK(example_connect());
|
|
|
|
/* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
|
|
* and re-start it upon connection.
|
|
*/
|
|
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
|
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
|
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
|
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
|
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
|
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
|
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
|
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
|
|
|
// start workers
|
|
start_workers();
|
|
|
|
/* Start the server for the first time */
|
|
server = start_webserver();
|
|
}
|