Skip to content

Commit

Permalink
[HTTPD] support for multiple simultaneous requests
Browse files Browse the repository at this point in the history
  • Loading branch information
chipweinberger committed Apr 17, 2023
1 parent 0025915 commit 1289d69
Show file tree
Hide file tree
Showing 11 changed files with 504 additions and 4 deletions.
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

0 comments on commit 1289d69

Please sign in to comment.