forked from espressif/esp-idf
Merge branch 'contrib/github_pr_11190' into 'master'
[HTTPD] support for multiple simultaneous requests (try 3) (GitHub PR) Closes IDFGH-9868 and IDFGH-9204 See merge request espressif/esp-idf!23281
This commit is contained in:
@@ -812,6 +812,40 @@ esp_err_t httpd_sess_set_send_override(httpd_handle_t hd, int sockfd, httpd_send
|
||||
*/
|
||||
esp_err_t httpd_sess_set_pending_override(httpd_handle_t hd, int sockfd, httpd_pending_func_t pending_func);
|
||||
|
||||
/**
|
||||
* @brief Start an asynchronous request. This function can be called
|
||||
* in a request handler to get a request copy that can be used on a async thread.
|
||||
*
|
||||
* @note
|
||||
* - This function is necessary in order to handle multiple requests simultaneously.
|
||||
* See examples/async_requests for example usage.
|
||||
* - You must call httpd_req_async_handler_complete() when you are done with the request.
|
||||
*
|
||||
* @param[in] r The request to create an async copy of
|
||||
* @param[out] out A newly allocated request which can be used on an async thread
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : async request object created
|
||||
*/
|
||||
esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out);
|
||||
|
||||
/**
|
||||
* @brief Mark an asynchronous request as completed. This will
|
||||
* - free the request memory
|
||||
* - relinquish ownership of the underlying socket, so it can be reused.
|
||||
* - allow the http server to close our socket if needed (lru_purge_enable)
|
||||
*
|
||||
* @note If async requests are not marked completed, eventually the server
|
||||
* will no longer accept incoming connections. The server will log a
|
||||
* "httpd_accept_conn: error in accept (23)" message if this happens.
|
||||
*
|
||||
* @param[in] r The request to mark async work as completed
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : async request was marked completed
|
||||
*/
|
||||
esp_err_t httpd_req_async_handler_complete(httpd_req_t *r);
|
||||
|
||||
/**
|
||||
* @brief Get the Socket Descriptor from the HTTP request
|
||||
*
|
||||
|
@@ -72,6 +72,7 @@ struct sock_db {
|
||||
bool lru_socket; /*!< Flag indicating LRU socket */
|
||||
char pending_data[PARSER_BLOCK_SIZE]; /*!< Buffer for pending data to be received */
|
||||
size_t pending_len; /*!< Length of pending data to be received */
|
||||
bool for_async_req; /*!< If true, the socket will not be LRU purged */
|
||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||
bool ws_handshake_done; /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */
|
||||
bool ws_close; /*!< Set to true to close the socket later (when WS Close frame received) */
|
||||
|
@@ -107,10 +107,13 @@ static int enum_function(struct sock_db *session, void *context)
|
||||
if (session->fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
// Check/update lowest lru
|
||||
if (session->lru_counter < ctx->lru_counter) {
|
||||
ctx->lru_counter = session->lru_counter;
|
||||
ctx->session = session;
|
||||
// Only close sockets that are not in use
|
||||
if (session->for_async_req == false) {
|
||||
// Check/update lowest lru
|
||||
if (session->lru_counter < ctx->lru_counter) {
|
||||
ctx->lru_counter = session->lru_counter;
|
||||
ctx->session = session;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HTTPD_TASK_CLOSE:
|
||||
|
@@ -554,6 +554,51 @@ int httpd_req_recv(httpd_req_t *r, char *buf, size_t buf_len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out)
|
||||
{
|
||||
if (r == NULL || out == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// alloc async req
|
||||
httpd_req_t *async = malloc(sizeof(httpd_req_t));
|
||||
if (async == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(async, r, sizeof(httpd_req_t));
|
||||
|
||||
// alloc async aux
|
||||
async->aux = malloc(sizeof(struct httpd_req_aux));
|
||||
if (async->aux == NULL) {
|
||||
free(async);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux));
|
||||
|
||||
// mark socket as "in use"
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
ra->sd->for_async_req = true;
|
||||
|
||||
*out = async;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t httpd_req_async_handler_complete(httpd_req_t *r)
|
||||
{
|
||||
if (r == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct httpd_req_aux *ra = r->aux;
|
||||
ra->sd->for_async_req = false;
|
||||
|
||||
free(r->aux);
|
||||
free(r);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int httpd_req_to_sockfd(httpd_req_t *r)
|
||||
{
|
||||
if (r == NULL) {
|
||||
|
10
examples/protocols/http_server/async_handlers/CMakeLists.txt
Normal file
10
examples/protocols/http_server/async_handlers/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(simple)
|
58
examples/protocols/http_server/async_handlers/README.md
Normal file
58
examples/protocols/http_server/async_handlers/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# Async Requests Handlers HTTPD Server Example
|
||||
|
||||
The Example demonstrates how to handle multiple long running simultaneous requests
|
||||
within the HTTPD server. It has the following URIs:
|
||||
|
||||
1. URI \long for demonstrating async requests running in the background
|
||||
2. URI \quick for demonstrating that quick requests are still responsive
|
||||
2. URI \ index page
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* An ESP-Dev-Board (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for power supply and programming
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
* Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (14257) example_common: Connected to example_netif_sta
|
||||
I (14267) example_common: - IPv4 address: 192.168.20.85,
|
||||
I (14277) example_common: - IPv6 address: fe80:0000:0000:0000:7edf:a1ff:fea4:3454, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (14287) example: starting async req task worker
|
||||
I (14287) example: starting async req task worker
|
||||
I (14297) example: Starting server on port: '80'
|
||||
I (14307) example: Registering URI handlers
|
||||
I (14307) main_task: Returned from app_main()
|
||||
I (15547) wifi:<ba-add>idx:1 (ifx:0, 68:d7:9a:81:26:1e), tid:0, ssn:0, winSize:64
|
||||
I (19627) example: uri: /
|
||||
I (25877) example: uri: /quick
|
||||
I (33247) example: uri: /long
|
||||
I (33247) example: invoking /long
|
||||
I (33247) example: uri: /long
|
||||
```
|
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,10 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_MAX_ASYNC_REQUESTS
|
||||
int "Max Simultaneous Requests"
|
||||
default 2
|
||||
help
|
||||
The maximum number of simultaneous async requests that the
|
||||
web server can handle.
|
||||
|
||||
endmenu
|
353
examples/protocols/http_server/async_handlers/main/main.c
Normal file
353
examples/protocols/http_server/async_handlers/main/main.c
Normal file
@@ -0,0 +1,353 @@
|
||||
/* 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 2048
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
// Async reqeusts are queued here while they wait to
|
||||
// be processed by the workers
|
||||
static QueueHandle_t async_req_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;
|
||||
|
||||
|
||||
static bool is_on_async_worker_thread(void)
|
||||
{
|
||||
// is our handle one of the known async handles?
|
||||
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
|
||||
for (int i = 0; i < CONFIG_EXAMPLE_MAX_ASYNC_REQUESTS; i++) {
|
||||
if (worker_handles[i] == handle) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Submit an HTTP req to the async worker queue
|
||||
static esp_err_t submit_async_req(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(async_req_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;
|
||||
}
|
||||
|
||||
|
||||
/* A long running HTTP GET handler */
|
||||
static esp_err_t long_async_handler(httpd_req_t *req)
|
||||
{
|
||||
ESP_LOGI(TAG, "uri: /long");
|
||||
// This handler is first invoked on the httpd thread.
|
||||
// In order to free the httpd thread to handle other requests,
|
||||
// we must resubmit our request to be handled on an async worker thread.
|
||||
if (is_on_async_worker_thread() == false) {
|
||||
|
||||
// submit
|
||||
if (submit_async_req(req, long_async_handler) == 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
httpd_resp_sendstr_chunk(req, s);
|
||||
|
||||
// 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);
|
||||
httpd_resp_sendstr_chunk(req, s);
|
||||
}
|
||||
|
||||
// send "complete"
|
||||
httpd_resp_sendstr_chunk(req, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void async_req_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(async_req_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);
|
||||
}
|
||||
|
||||
static void start_async_req_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
|
||||
async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t));
|
||||
if (async_req_queue == NULL){
|
||||
ESP_LOGE(TAG, "Failed to create async_req_queue");
|
||||
vSemaphoreDelete(worker_ready_count);
|
||||
return;
|
||||
}
|
||||
|
||||
// start worker tasks
|
||||
for (int i = 0; i < CONFIG_EXAMPLE_MAX_ASYNC_REQUESTS; i++) {
|
||||
|
||||
bool success = xTaskCreate(async_req_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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_async_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_async_req_workers();
|
||||
|
||||
/* Start the server for the first time */
|
||||
server = start_webserver();
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
CONFIG_EXAMPLE_MAX_ASYNC_REQUESTS=2
|
||||
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
|
@@ -1421,6 +1421,7 @@ examples/protocols/http_server/advanced_tests/http_server_advanced_test.py
|
||||
examples/protocols/http_server/advanced_tests/main/include/tests.h
|
||||
examples/protocols/http_server/advanced_tests/main/main.c
|
||||
examples/protocols/http_server/advanced_tests/main/tests.c
|
||||
examples/protocols/http_server/async_handlers/main/main.c
|
||||
examples/protocols/http_server/captive_portal/example_test.py
|
||||
examples/protocols/http_server/captive_portal/main/dns_server.c
|
||||
examples/protocols/http_server/captive_portal/main/include/dns_server.h
|
||||
|
Reference in New Issue
Block a user