Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HTTPD] support for multiple simultaneous requests (try 3) (IDFGH-9868) #11190

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions components/esp_http_server/include/esp_http_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,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: You must call httpd_req_async_handler_complete() when you are done with the request.
*
* @note This function is necessary in order to handle multiple requests simultaneously.
* See examples/async_requests for example usage.
*
* @param[in] r The request to create an async copy of
* @param[out] out A newly allocated request which can be used on a 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
*
Expand Down
1 change: 1 addition & 0 deletions components/esp_http_server/src/esp_httpd_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,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) */
Expand Down
11 changes: 7 additions & 4 deletions components/esp_http_server/src/httpd_sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
47 changes: 47 additions & 0 deletions components/esp_http_server/src/httpd_txrx.c
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,53 @@ 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;
} else {
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;
} else {
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) {
Expand Down
10 changes: 10 additions & 0 deletions examples/protocols/http_server/async_handlers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
41 changes: 41 additions & 0 deletions examples/protocols/http_server/async_handlers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
| 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

* A development board with ESP32/ESP32-S2/ESP32-C3 SoC (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.

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading