mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
3824eba04d
This commit adds support for handling multiple requests simultaneously by introducing two new functions: `httpd_req_async_handler_begin()` and `httpd_req_async_handler_complete()`. These functions allow creating an asynchronous copy of a request that can be used on a separate thread and marking the asynchronous request as completed, respectively. Additionally, a new flag `for_async_req` has been added to the `httpd_sess_t` struct to indicate if a socket is being used for an asynchronous request and should not be purged from the LRU cache. An example have been added to demonstrate the usage of these new functions. Closes https://github.com/espressif/esp-idf/issues/10594 Signed-off-by: Harshit Malpani <harshit.malpani@espressif.com>
499 lines
14 KiB
C
499 lines
14 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <esp_log.h>
|
|
#include <esp_err.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#include <esp_http_server.h>
|
|
#include "esp_httpd_priv.h"
|
|
|
|
static const char *TAG = "httpd_sess";
|
|
|
|
typedef enum {
|
|
HTTPD_TASK_NONE = 0,
|
|
HTTPD_TASK_INIT, // Init session
|
|
HTTPD_TASK_GET_ACTIVE, // Get active session (fd!=-1)
|
|
HTTPD_TASK_GET_FREE, // Get free session slot (fd<0)
|
|
HTTPD_TASK_FIND_FD, // Find session with specific fd
|
|
HTTPD_TASK_SET_DESCRIPTOR, // Set descriptor
|
|
HTTPD_TASK_DELETE_INVALID, // Delete invalid session
|
|
HTTPD_TASK_FIND_LOWEST_LRU, // Find session with lowest lru
|
|
HTTPD_TASK_CLOSE // Close session
|
|
} task_t;
|
|
|
|
typedef struct {
|
|
task_t task;
|
|
int fd;
|
|
fd_set *fdset;
|
|
int max_fd;
|
|
struct httpd_data *hd;
|
|
uint64_t lru_counter;
|
|
struct sock_db *session;
|
|
} enum_context_t;
|
|
|
|
void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context)
|
|
{
|
|
if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
|
|
return;
|
|
}
|
|
|
|
struct sock_db *current = hd->hd_sd;
|
|
struct sock_db *end = hd->hd_sd + hd->config.max_open_sockets - 1;
|
|
|
|
while (current <= end) {
|
|
if (enum_function && (!enum_function(current, context))) {
|
|
break;
|
|
}
|
|
current++;
|
|
}
|
|
}
|
|
|
|
// Check if a FD is valid
|
|
static int fd_is_valid(int fd)
|
|
{
|
|
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
|
|
}
|
|
|
|
static int enum_function(struct sock_db *session, void *context)
|
|
{
|
|
if ((!session) || (!context)) {
|
|
return 0;
|
|
}
|
|
enum_context_t *ctx = (enum_context_t *) context;
|
|
int found = 0;
|
|
switch (ctx->task) {
|
|
// Initialize session
|
|
case HTTPD_TASK_INIT:
|
|
session->fd = -1;
|
|
session->ctx = NULL;
|
|
break;
|
|
// Get active session
|
|
case HTTPD_TASK_GET_ACTIVE:
|
|
found = (session->fd != -1);
|
|
break;
|
|
// Get free slot
|
|
case HTTPD_TASK_GET_FREE:
|
|
found = (session->fd < 0);
|
|
break;
|
|
// Find fd
|
|
case HTTPD_TASK_FIND_FD:
|
|
found = (session->fd == ctx->fd);
|
|
break;
|
|
// Set descriptor
|
|
case HTTPD_TASK_SET_DESCRIPTOR:
|
|
if (session->fd != -1) {
|
|
FD_SET(session->fd, ctx->fdset);
|
|
if (session->fd > ctx->max_fd) {
|
|
ctx->max_fd = session->fd;
|
|
}
|
|
}
|
|
break;
|
|
// Delete invalid session
|
|
case HTTPD_TASK_DELETE_INVALID:
|
|
if (!fd_is_valid(session->fd)) {
|
|
ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), session->fd);
|
|
httpd_sess_delete(ctx->hd, session);
|
|
}
|
|
break;
|
|
// Find lowest lru
|
|
case HTTPD_TASK_FIND_LOWEST_LRU:
|
|
// Found free slot - no need to check other sessions
|
|
if (session->fd == -1) {
|
|
return 0;
|
|
}
|
|
// 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:
|
|
if (session->fd != -1) {
|
|
ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), session->fd);
|
|
httpd_sess_delete(ctx->hd, session);
|
|
}
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
if (found) {
|
|
ctx->session = session;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void httpd_sess_close(void *arg)
|
|
{
|
|
struct sock_db *sock_db = (struct sock_db *) arg;
|
|
if (!sock_db) {
|
|
return;
|
|
}
|
|
|
|
if (!sock_db->lru_counter && !sock_db->lru_socket) {
|
|
ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
|
|
return;
|
|
}
|
|
sock_db->lru_socket = false;
|
|
struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
|
|
httpd_sess_delete(hd, sock_db);
|
|
}
|
|
|
|
struct sock_db *httpd_sess_get_free(struct httpd_data *hd)
|
|
{
|
|
if ((!hd) || (hd->hd_sd_active_count == hd->config.max_open_sockets)) {
|
|
return NULL;
|
|
}
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_GET_FREE
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
return context.session;
|
|
}
|
|
|
|
bool httpd_is_sess_available(struct httpd_data *hd)
|
|
{
|
|
return httpd_sess_get_free(hd) ? true : false;
|
|
}
|
|
|
|
struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
|
|
{
|
|
if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Check if called inside a request handler, and the session sockfd in use is same as the parameter
|
|
// => Just return the pointer to the sock_db corresponding to the request
|
|
if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
|
|
return hd->hd_req_aux.sd;
|
|
}
|
|
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_FIND_FD,
|
|
.fd = sockfd
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
return context.session;
|
|
}
|
|
|
|
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
|
|
{
|
|
ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
|
|
|
|
if (httpd_sess_get(hd, newfd)) {
|
|
ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
struct sock_db *session = httpd_sess_get_free(hd);
|
|
if (!session) {
|
|
ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
// Clear session data
|
|
memset(session, 0, sizeof (struct sock_db));
|
|
session->fd = newfd;
|
|
session->handle = (httpd_handle_t) hd;
|
|
session->send_fn = httpd_default_send;
|
|
session->recv_fn = httpd_default_recv;
|
|
|
|
// increment number of sessions
|
|
hd->hd_sd_active_count++;
|
|
|
|
// Call user-defined session opening function
|
|
if (hd->config.open_fn) {
|
|
esp_err_t ret = hd->config.open_fn(hd, session->fd);
|
|
if (ret != ESP_OK) {
|
|
httpd_sess_delete(hd, session);
|
|
ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
|
|
return ESP_OK;
|
|
}
|
|
|
|
void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn)
|
|
{
|
|
if ((!ctx) || (!*ctx)) {
|
|
return;
|
|
}
|
|
if (free_fn) {
|
|
free_fn(*ctx);
|
|
} else {
|
|
free(*ctx);
|
|
}
|
|
*ctx = NULL;
|
|
}
|
|
|
|
void httpd_sess_clear_ctx(struct sock_db *session)
|
|
{
|
|
if ((!session) || ((!session->ctx) && (!session->transport_ctx))) {
|
|
return;
|
|
}
|
|
|
|
// free user ctx
|
|
if (session->ctx) {
|
|
httpd_sess_free_ctx(&session->ctx, session->free_ctx);
|
|
session->free_ctx = NULL;
|
|
}
|
|
|
|
// Free 'transport' context
|
|
if (session->transport_ctx) {
|
|
httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
|
|
session->free_transport_ctx = NULL;
|
|
}
|
|
}
|
|
|
|
void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
|
|
{
|
|
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
|
if (!session) {
|
|
return NULL;
|
|
}
|
|
|
|
// Check if the function has been called from inside a
|
|
// request handler, in which case fetch the context from
|
|
// the httpd_req_t structure
|
|
struct httpd_data *hd = (struct httpd_data *) handle;
|
|
if (hd->hd_req_aux.sd == session) {
|
|
return hd->hd_req.sess_ctx;
|
|
}
|
|
return session->ctx;
|
|
}
|
|
|
|
void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
|
|
{
|
|
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
|
if (!session) {
|
|
return;
|
|
}
|
|
|
|
// Check if the function has been called from inside a
|
|
// request handler, in which case set the context inside
|
|
// the httpd_req_t structure
|
|
struct httpd_data *hd = (struct httpd_data *) handle;
|
|
if (hd->hd_req_aux.sd == session) {
|
|
if (hd->hd_req.sess_ctx != ctx) {
|
|
// Don't free previous context if it is in sockdb
|
|
// as it will be freed inside httpd_req_cleanup()
|
|
if (session->ctx != hd->hd_req.sess_ctx) {
|
|
httpd_sess_free_ctx(&hd->hd_req.sess_ctx, hd->hd_req.free_ctx); // Free previous context
|
|
}
|
|
hd->hd_req.sess_ctx = ctx;
|
|
}
|
|
hd->hd_req.free_ctx = free_fn;
|
|
return;
|
|
}
|
|
|
|
// Else set the context inside the sock_db structure
|
|
if (session->ctx != ctx) {
|
|
// Free previous context
|
|
httpd_sess_free_ctx(&session->ctx, session->free_ctx);
|
|
session->ctx = ctx;
|
|
}
|
|
session->free_ctx = free_fn;
|
|
}
|
|
|
|
void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
|
|
{
|
|
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
|
return session ? session->transport_ctx : NULL;
|
|
}
|
|
|
|
void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
|
|
{
|
|
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
|
if (!session) {
|
|
return;
|
|
}
|
|
|
|
if (session->transport_ctx != ctx) {
|
|
// Free previous transport context
|
|
httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
|
|
session->transport_ctx = ctx;
|
|
}
|
|
session->free_transport_ctx = free_fn;
|
|
}
|
|
|
|
void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
|
|
{
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_SET_DESCRIPTOR,
|
|
.max_fd = -1,
|
|
.fdset = fdset
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
if (maxfd) {
|
|
*maxfd = context.max_fd;
|
|
}
|
|
}
|
|
|
|
void httpd_sess_delete_invalid(struct httpd_data *hd)
|
|
{
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_DELETE_INVALID,
|
|
.hd = hd
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
}
|
|
|
|
void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
|
|
{
|
|
if ((!hd) || (!session) || (session->fd < 0)) {
|
|
return;
|
|
}
|
|
|
|
ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd);
|
|
if (hd->config.enable_so_linger) {
|
|
struct linger so_linger = {
|
|
.l_onoff = true,
|
|
.l_linger = hd->config.linger_timeout,
|
|
};
|
|
if (setsockopt(session->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(struct linger)) < 0) {
|
|
ESP_LOGW(TAG, LOG_FMT("error enabling SO_LINGER (%d)"), errno);
|
|
}
|
|
}
|
|
|
|
// Call close function if defined
|
|
if (hd->config.close_fn) {
|
|
hd->config.close_fn(hd, session->fd);
|
|
} else {
|
|
close(session->fd);
|
|
}
|
|
esp_http_server_dispatch_event(HTTP_SERVER_EVENT_DISCONNECTED, &session->fd, sizeof(int));
|
|
|
|
// clear all contexts
|
|
httpd_sess_clear_ctx(session);
|
|
|
|
// mark session slot as available
|
|
session->fd = -1;
|
|
|
|
// decrement number of sessions
|
|
hd->hd_sd_active_count--;
|
|
ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
|
|
if (!hd->hd_sd_active_count) {
|
|
hd->lru_counter = 0;
|
|
}
|
|
}
|
|
|
|
void httpd_sess_init(struct httpd_data *hd)
|
|
{
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_INIT
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
}
|
|
|
|
bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session)
|
|
{
|
|
if (!session) {
|
|
return false;
|
|
}
|
|
if (session->pending_fn) {
|
|
// test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
|
|
// this should check e.g. for the SSL data buffer
|
|
if (session->pending_fn(hd, session->fd) > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return (session->pending_len != 0);
|
|
}
|
|
|
|
/* This MUST return ESP_OK on successful execution. If any other
|
|
* value is returned, everything related to this socket will be
|
|
* cleaned up and the socket will be closed.
|
|
*/
|
|
esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session)
|
|
{
|
|
if ((!hd) || (!session)) {
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
|
|
if (httpd_req_new(hd, session) != ESP_OK) {
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
|
|
if (httpd_req_delete(hd) != ESP_OK) {
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGD(TAG, LOG_FMT("success"));
|
|
session->lru_counter = ++hd->lru_counter;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
|
|
{
|
|
if (handle == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
struct httpd_data *hd = (struct httpd_data *) handle;
|
|
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_FIND_FD,
|
|
.fd = sockfd
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
if (context.session) {
|
|
context.session->lru_counter = ++hd->lru_counter;
|
|
return ESP_OK;
|
|
}
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
|
|
{
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_FIND_LOWEST_LRU,
|
|
.lru_counter = UINT64_MAX,
|
|
.fd = -1
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
if (!context.session) {
|
|
return ESP_OK;
|
|
}
|
|
ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
|
|
context.session->lru_socket = true;
|
|
return httpd_sess_trigger_close_(hd, context.session);
|
|
}
|
|
|
|
esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session)
|
|
{
|
|
if (!session) {
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
return httpd_queue_work(handle, httpd_sess_close, session);
|
|
}
|
|
|
|
esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
|
|
{
|
|
struct sock_db *session = httpd_sess_get(handle, sockfd);
|
|
if (!session) {
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
return httpd_sess_trigger_close_(handle, session);
|
|
}
|
|
|
|
void httpd_sess_close_all(struct httpd_data *hd)
|
|
{
|
|
enum_context_t context = {
|
|
.task = HTTPD_TASK_CLOSE,
|
|
.hd = hd
|
|
};
|
|
httpd_sess_enum(hd, enum_function, &context);
|
|
}
|