Merge branch 'feature/http_server_optimizations' into 'master'

esp_http_server optimisations

Closes IDFGH-4484 and IDFGH-4741

See merge request espressif/esp-idf!12240
This commit is contained in:
Mahavir Jain
2021-03-08 09:01:20 +00:00
4 changed files with 431 additions and 306 deletions

View File

@@ -117,9 +117,11 @@ struct httpd_data {
int msg_fd; /*!< Ctrl message sender FD */ int msg_fd; /*!< Ctrl message sender FD */
struct thread_data hd_td; /*!< Information for the HTTPD thread */ struct thread_data hd_td; /*!< Information for the HTTPD thread */
struct sock_db *hd_sd; /*!< The socket database */ struct sock_db *hd_sd; /*!< The socket database */
int hd_sd_active_count; /*!< The number of the active sockets */
httpd_uri_t **hd_calls; /*!< Registered URI handlers */ httpd_uri_t **hd_calls; /*!< Registered URI handlers */
struct httpd_req hd_req; /*!< The current HTTPD request */ struct httpd_req hd_req; /*!< The current HTTPD request */
struct httpd_req_aux hd_req_aux; /*!< Additional data about the HTTPD request kept unexposed */ struct httpd_req_aux hd_req_aux; /*!< Additional data about the HTTPD request kept unexposed */
uint64_t lru_counter; /*!< LRU counter */
/* Array of registered error handler functions */ /* Array of registered error handler functions */
httpd_err_handler_func_t *err_handler_fns; httpd_err_handler_func_t *err_handler_fns;
@@ -131,6 +133,29 @@ struct httpd_data {
* @{ * @{
*/ */
// Enum function, which will be called for each session
typedef int (*httpd_session_enum_function)(struct sock_db *session, void *context);
/**
* @brief Enumerates all sessions
*
* @param[in] hd Server instance data
* @param[in] enum_function Enumeration function, which will be called for each session
* @param[in] context Context, which will be passed to the enumeration function
*/
void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context);
/**
* @brief Returns next free session slot (fd<0)
*
* @param[in] hd Server instance data
*
* @return
* - +VE : Free session slot
* - NULL: End of iteration
*/
struct sock_db *httpd_sess_get_free(struct httpd_data *hd);
/** /**
* @brief Retrieve a session by its descriptor * @brief Retrieve a session by its descriptor
* *
@@ -172,32 +197,22 @@ esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd);
* @brief Processes incoming HTTP requests * @brief Processes incoming HTTP requests
* *
* @param[in] hd Server instance data * @param[in] hd Server instance data
* @param[in] clifd Descriptor of the client from which data is to be received * @param[in] session Session
* *
* @return * @return
* - ESP_OK : on successfully receiving, parsing and responding to a request * - ESP_OK : on successfully receiving, parsing and responding to a request
* - ESP_FAIL : in case of failure in any of the stages of processing * - ESP_FAIL : in case of failure in any of the stages of processing
*/ */
esp_err_t httpd_sess_process(struct httpd_data *hd, int clifd); esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session);
/** /**
* @brief Remove client descriptor from the session / socket database * @brief Remove client descriptor from the session / socket database
* and close the connection for this client. * and close the connection for this client.
* *
* @note The returned descriptor should be used by httpd_sess_iterate()
* to continue the iteration correctly. This ensures that the
* iteration is not restarted abruptly which may cause reading from
* a socket which has been already processed and thus blocking
* the server loop until data appears on that socket.
*
* @param[in] hd Server instance data * @param[in] hd Server instance data
* @param[in] clifd Descriptor of the client to be removed from the session. * @param[in] session Session
*
* @return
* - +VE : Client descriptor preceding the one being deleted
* - -1 : No descriptor preceding the one being deleted
*/ */
int httpd_sess_delete(struct httpd_data *hd, int clifd); void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session);
/** /**
* @brief Free session context * @brief Free session context
@@ -205,7 +220,7 @@ int httpd_sess_delete(struct httpd_data *hd, int clifd);
* @param[in] ctx Pointer to session context * @param[in] ctx Pointer to session context
* @param[in] free_fn Free function to call on session context * @param[in] free_fn Free function to call on session context
*/ */
void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn); void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn);
/** /**
* @brief Add descriptors present in the socket database to an fdset and * @brief Add descriptors present in the socket database to an fdset and
@@ -218,21 +233,6 @@ void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn);
*/ */
void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd); void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd);
/**
* @brief Iterates through the list of client fds in the session /socket database.
* Passing the value of a client fd returns the fd for the next client
* in the database. In order to iterate from the beginning pass -1 as fd.
*
* @param[in] hd Server instance data
* @param[in] fd Last accessed client descriptor.
* -1 to reset iterator to start of database.
*
* @return
* - +VE : Client descriptor next in the database
* - -1 : End of iteration
*/
int httpd_sess_iterate(struct httpd_data *hd, int fd);
/** /**
* @brief Checks if session can accept another connection from new client. * @brief Checks if session can accept another connection from new client.
* If sockets database is full then this returns false. * If sockets database is full then this returns false.
@@ -258,11 +258,11 @@ bool httpd_is_sess_available(struct httpd_data *hd);
* buffer. * buffer.
* *
* @param[in] hd Server instance data * @param[in] hd Server instance data
* @param[in] fd Client descriptor * @param[in] session Session
* *
* @return True if there is any pending data * @return True if there is any pending data
*/ */
bool httpd_sess_pending(struct httpd_data *hd, int fd); bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session);
/** /**
* @brief Removes the least recently used client from the session * @brief Removes the least recently used client from the session
@@ -279,6 +279,14 @@ bool httpd_sess_pending(struct httpd_data *hd, int fd);
*/ */
esp_err_t httpd_sess_close_lru(struct httpd_data *hd); esp_err_t httpd_sess_close_lru(struct httpd_data *hd);
/**
* @brief Closes all sessions
*
* @param[in] hd Server instance data
*
*/
void httpd_sess_close_all(struct httpd_data *hd);
/** End of Group : Session Management /** End of Group : Session Management
* @} * @}
*/ */
@@ -519,6 +527,23 @@ esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char *suppor
*/ */
esp_err_t httpd_ws_get_frame_type(httpd_req_t *req); esp_err_t httpd_ws_get_frame_type(httpd_req_t *req);
/**
* @brief Trigger an httpd session close externally
*
* @note Calling this API is only required in special circumstances wherein
* some application requires to close an httpd client session asynchronously.
*
* @param[in] handle Handle to server returned by httpd_start
* @param[in] session Session to be closed
*
* @return
* - ESP_OK : On successfully initiating closure
* - ESP_FAIL : Failure to queue work
* - ESP_ERR_NOT_FOUND : Socket fd not found
* - ESP_ERR_INVALID_ARG : Null arguments
*/
esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session);
/** End of WebSocket related functions /** End of WebSocket related functions
* @} * @}
*/ */

View File

@@ -24,6 +24,11 @@
#include "esp_httpd_priv.h" #include "esp_httpd_priv.h"
#include "ctrl_sock.h" #include "ctrl_sock.h"
typedef struct {
fd_set *fdset;
struct httpd_data *hd;
} process_session_context_t;
static const char *TAG = "httpd"; static const char *TAG = "httpd";
static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd) static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
@@ -132,15 +137,6 @@ void *httpd_get_global_transport_ctx(httpd_handle_t handle)
return ((struct httpd_data *)handle)->config.global_transport_ctx; return ((struct httpd_data *)handle)->config.global_transport_ctx;
} }
static void httpd_close_all_sessions(struct httpd_data *hd)
{
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
httpd_sess_delete(hd, fd);
close(fd);
}
}
static void httpd_process_ctrl_msg(struct httpd_data *hd) static void httpd_process_ctrl_msg(struct httpd_data *hd)
{ {
@@ -171,6 +167,29 @@ static void httpd_process_ctrl_msg(struct httpd_data *hd)
} }
} }
// Called for each session from httpd_server
static int httpd_process_session(struct sock_db *session, void *context)
{
if ((!session) || (!context)) {
return 0;
}
if (session->fd < 0) {
return 1;
}
process_session_context_t *ctx = (process_session_context_t *)context;
int fd = session->fd;
if (FD_ISSET(fd, ctx->fdset) || httpd_sess_pending(ctx->hd, session)) {
ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
if (httpd_sess_process(ctx->hd, session) != ESP_OK) {
httpd_sess_delete(ctx->hd, session); // Delete session
}
}
return 1;
}
/* Manage in-coming connection or data requests */ /* Manage in-coming connection or data requests */
static esp_err_t httpd_server(struct httpd_data *hd) static esp_err_t httpd_server(struct httpd_data *hd)
{ {
@@ -210,19 +229,11 @@ static esp_err_t httpd_server(struct httpd_data *hd)
/* Case1: Do we have any activity on the current data /* Case1: Do we have any activity on the current data
* sessions? */ * sessions? */
int fd = -1; process_session_context_t context = {
while ((fd = httpd_sess_iterate(hd, fd)) != -1) { .fdset = &read_set,
if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) { .hd = hd
ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd); };
if (httpd_sess_process(hd, fd) != ESP_OK) { httpd_sess_enum(hd, httpd_process_session, &context);
ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
close(fd);
/* Delete session and update fd to that
* preceding the one being deleted */
fd = httpd_sess_delete(hd, fd);
}
}
}
/* Case2: Do we have any incoming connection requests to /* Case2: Do we have any incoming connection requests to
* process? */ * process? */
@@ -253,7 +264,7 @@ static void httpd_thread(void *arg)
ESP_LOGD(TAG, LOG_FMT("web server exiting")); ESP_LOGD(TAG, LOG_FMT("web server exiting"));
close(hd->msg_fd); close(hd->msg_fd);
cs_free_ctrl_sock(hd->ctrl_fd); cs_free_ctrl_sock(hd->ctrl_fd);
httpd_close_all_sessions(hd); httpd_sess_close_all(hd);
close(hd->listen_fd); close(hd->listen_fd);
hd->hd_td.status = THREAD_STOPPED; hd->hd_td.status = THREAD_STOPPED;
httpd_os_thread_delete(); httpd_os_thread_delete();

View File

@@ -11,49 +11,183 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <stdlib.h> #include <stdlib.h>
#include <esp_log.h> #include <esp_log.h>
#include <esp_err.h> #include <esp_err.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <esp_http_server.h> #include <esp_http_server.h>
#include "esp_httpd_priv.h" #include "esp_httpd_priv.h"
static const char *TAG = "httpd_sess"; 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;
}
// 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) bool httpd_is_sess_available(struct httpd_data *hd)
{ {
int i; return httpd_sess_get_free(hd) ? true : false;
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == -1) {
return true;
}
}
return false;
} }
struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd) struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
{ {
if (hd == NULL) { if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
return NULL; return NULL;
} }
/* Check if called inside a request handler, and the // Check if called inside a request handler, and the session sockfd in use is same as the parameter
* 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)) { if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
/* Just return the pointer to the sock_db
* corresponding to the request */
return hd->hd_req_aux.sd; return hd->hd_req_aux.sd;
} }
int i; enum_context_t context = {
for (i = 0; i < hd->config.max_open_sockets; i++) { .task = HTTPD_TASK_FIND_FD,
if (hd->hd_sd[i].fd == sockfd) { .fd = sockfd
return &hd->hd_sd[i]; };
} httpd_sess_enum(hd, enum_function, &context);
} return context.session;
return NULL;
} }
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd) esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
@@ -65,78 +199,102 @@ esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
return ESP_FAIL; return ESP_FAIL;
} }
int i; struct sock_db *session = httpd_sess_get_free(hd);
for (i = 0; i < hd->config.max_open_sockets; i++) { if (!session) {
if (hd->hd_sd[i].fd == -1) {
memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i]));
hd->hd_sd[i].fd = newfd;
hd->hd_sd[i].handle = (httpd_handle_t) hd;
hd->hd_sd[i].send_fn = httpd_default_send;
hd->hd_sd[i].recv_fn = httpd_default_recv;
/* Call user-defined session opening function */
if (hd->config.open_fn) {
esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd);
if (ret != ESP_OK) {
httpd_sess_delete(hd, hd->hd_sd[i].fd);
ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
return ret;
}
}
return ESP_OK;
}
}
ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd); ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
return ESP_FAIL; return ESP_FAIL;
} }
void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn) // Clear session data
{ memset(session, 0, sizeof (struct sock_db));
if (ctx) { session->fd = newfd;
if (free_fn) { session->handle = (httpd_handle_t) hd;
free_fn(ctx); session->send_fn = httpd_default_send;
} else { session->recv_fn = httpd_default_recv;
free(ctx);
// 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;
} }
} }
// increment number of sessions
hd->hd_sd_active_count++;
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)) {
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) void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
{ {
struct sock_db *sd = httpd_sess_get(handle, sockfd); struct sock_db *session = httpd_sess_get(handle, sockfd);
if (sd == NULL) { if (!session) {
return NULL; return NULL;
} }
/* Check if the function has been called from inside a // Check if the function has been called from inside a
* request handler, in which case fetch the context from // request handler, in which case fetch the context from
* the httpd_req_t structure */ // the httpd_req_t structure
struct httpd_data *hd = (struct httpd_data *) handle; struct httpd_data *hd = (struct httpd_data *) handle;
if (hd->hd_req_aux.sd == sd) { if (hd->hd_req_aux.sd == session) {
return hd->hd_req.sess_ctx; return hd->hd_req.sess_ctx;
} }
return session->ctx;
return sd->ctx;
} }
void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn) void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
{ {
struct sock_db *sd = httpd_sess_get(handle, sockfd); struct sock_db *session = httpd_sess_get(handle, sockfd);
if (sd == NULL) { if (!session) {
return; return;
} }
/* Check if the function has been called from inside a // Check if the function has been called from inside a
* request handler, in which case set the context inside // request handler, in which case set the context inside
* the httpd_req_t structure */ // the httpd_req_t structure
struct httpd_data *hd = (struct httpd_data *) handle; struct httpd_data *hd = (struct httpd_data *) handle;
if (hd->hd_req_aux.sd == sd) { if (hd->hd_req_aux.sd == session) {
if (hd->hd_req.sess_ctx != ctx) { if (hd->hd_req.sess_ctx != ctx) {
/* Don't free previous context if it is in sockdb // Don't free previous context if it is in sockdb
* as it will be freed inside httpd_req_cleanup() */ // as it will be freed inside httpd_req_cleanup()
if (sd->ctx != hd->hd_req.sess_ctx) { if (session->ctx != hd->hd_req.sess_ctx) {
/* Free previous context */ httpd_sess_free_ctx(&hd->hd_req.sess_ctx, hd->hd_req.free_ctx); // Free previous context
httpd_sess_free_ctx(hd->hd_req.sess_ctx, hd->hd_req.free_ctx);
} }
hd->hd_req.sess_ctx = ctx; hd->hd_req.sess_ctx = ctx;
} }
@@ -144,164 +302,122 @@ void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free
return; return;
} }
/* Else set the context inside the sock_db structure */ // Else set the context inside the sock_db structure
if (sd->ctx != ctx) { if (session->ctx != ctx) {
/* Free previous context */ // Free previous context
httpd_sess_free_ctx(sd->ctx, sd->free_ctx); httpd_sess_free_ctx(&session->ctx, session->free_ctx);
sd->ctx = ctx; session->ctx = ctx;
} }
sd->free_ctx = free_fn; session->free_ctx = free_fn;
} }
void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd) void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
{ {
struct sock_db *sd = httpd_sess_get(handle, sockfd); struct sock_db *session = httpd_sess_get(handle, sockfd);
if (sd == NULL) { return session ? session->transport_ctx : NULL;
return NULL;
}
return sd->transport_ctx;
} }
void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn) void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
{ {
struct sock_db *sd = httpd_sess_get(handle, sockfd); struct sock_db *session = httpd_sess_get(handle, sockfd);
if (sd == NULL) { if (!session) {
return; return;
} }
if (sd->transport_ctx != ctx) { if (session->transport_ctx != ctx) {
/* Free previous transport context */ // Free previous transport context
httpd_sess_free_ctx(sd->transport_ctx, sd->free_transport_ctx); httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
sd->transport_ctx = ctx; session->transport_ctx = ctx;
} }
sd->free_transport_ctx = free_fn; session->free_transport_ctx = free_fn;
} }
void httpd_sess_set_descriptors(struct httpd_data *hd, void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
fd_set *fdset, int *maxfd)
{ {
int i; enum_context_t context = {
*maxfd = -1; .task = HTTPD_TASK_SET_DESCRIPTOR,
for (i = 0; i < hd->config.max_open_sockets; i++) { .max_fd = -1,
if (hd->hd_sd[i].fd != -1) { .fdset = fdset
FD_SET(hd->hd_sd[i].fd, fdset); };
if (hd->hd_sd[i].fd > *maxfd) { httpd_sess_enum(hd, enum_function, &context);
*maxfd = hd->hd_sd[i].fd; if (maxfd) {
*maxfd = context.max_fd;
} }
} }
}
}
/** Check if a FD is valid */
static int fd_is_valid(int fd)
{
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
static inline uint64_t httpd_sess_get_lru_counter(void)
{
static uint64_t lru_counter = 0;
return ++lru_counter;
}
void httpd_sess_delete_invalid(struct httpd_data *hd) void httpd_sess_delete_invalid(struct httpd_data *hd)
{ {
for (int i = 0; i < hd->config.max_open_sockets; i++) { enum_context_t context = {
if (hd->hd_sd[i].fd != -1 && !fd_is_valid(hd->hd_sd[i].fd)) { .task = HTTPD_TASK_DELETE_INVALID,
ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), hd->hd_sd[i].fd); .hd = hd
httpd_sess_delete(hd, hd->hd_sd[i].fd); };
} httpd_sess_enum(hd, enum_function, &context);
}
} }
int httpd_sess_delete(struct httpd_data *hd, int fd) void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
{ {
ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd); if ((!hd) || (!session) || (session->fd < 0)) {
int i; return;
int pre_sess_fd = -1; }
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == fd) { ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd);
/* global close handler */
// Call close function if defined
if (hd->config.close_fn) { if (hd->config.close_fn) {
hd->config.close_fn(hd, fd); hd->config.close_fn(hd, session->fd);
}
/* release 'user' context */
if (hd->hd_sd[i].ctx) {
if (hd->hd_sd[i].free_ctx) {
hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx);
} else { } else {
free(hd->hd_sd[i].ctx); close(session->fd);
}
hd->hd_sd[i].ctx = NULL;
hd->hd_sd[i].free_ctx = NULL;
} }
/* release 'transport' context */ // clear all contexts
if (hd->hd_sd[i].transport_ctx) { httpd_sess_clear_ctx(session);
if (hd->hd_sd[i].free_transport_ctx) {
hd->hd_sd[i].free_transport_ctx(hd->hd_sd[i].transport_ctx);
} else {
free(hd->hd_sd[i].transport_ctx);
}
hd->hd_sd[i].transport_ctx = NULL;
hd->hd_sd[i].free_transport_ctx = NULL;
}
/* mark session slot as available */ // mark session slot as available
hd->hd_sd[i].fd = -1; session->fd = -1;
break;
} else if (hd->hd_sd[i].fd != -1) { // decrement number of sessions
/* Return the fd just preceding the one being hd->hd_sd_active_count--;
* deleted so that iterator can continue from ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
* the correct fd */ if (!hd->hd_sd_active_count) {
pre_sess_fd = hd->hd_sd[i].fd; hd->lru_counter = 0;
} }
} }
return pre_sess_fd;
}
void httpd_sess_init(struct httpd_data *hd) void httpd_sess_init(struct httpd_data *hd)
{ {
int i; enum_context_t context = {
for (i = 0; i < hd->config.max_open_sockets; i++) { .task = HTTPD_TASK_INIT
hd->hd_sd[i].fd = -1; };
hd->hd_sd[i].ctx = NULL; httpd_sess_enum(hd, enum_function, &context);
}
} }
bool httpd_sess_pending(struct httpd_data *hd, int fd) bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session)
{ {
struct sock_db *sd = httpd_sess_get(hd, fd); if (!session) {
if (! sd) { return false;
return ESP_FAIL;
} }
if (session->pending_fn) {
if (sd->pending_fn) {
// test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop) // 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 // this should check e.g. for the SSL data buffer
if (sd->pending_fn(hd, fd) > 0) { if (session->pending_fn(hd, session->fd) > 0) {
return true; return true;
} }
} }
return (session->pending_len != 0);
return (sd->pending_len != 0);
} }
/* This MUST return ESP_OK on successful execution. If any other /* This MUST return ESP_OK on successful execution. If any other
* value is returned, everything related to this socket will be * value is returned, everything related to this socket will be
* cleaned up and the socket will be closed. * cleaned up and the socket will be closed.
*/ */
esp_err_t httpd_sess_process(struct httpd_data *hd, int newfd) esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session)
{ {
struct sock_db *sd = httpd_sess_get(hd, newfd); if ((!hd) || (!session)) {
if (! sd) {
return ESP_FAIL; return ESP_FAIL;
} }
ESP_LOGD(TAG, LOG_FMT("httpd_req_new")); ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
if (httpd_req_new(hd, sd) != ESP_OK) { if (httpd_req_new(hd, session) != ESP_OK) {
return ESP_FAIL; return ESP_FAIL;
} }
ESP_LOGD(TAG, LOG_FMT("httpd_req_delete")); ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
@@ -309,7 +425,7 @@ esp_err_t httpd_sess_process(struct httpd_data *hd, int newfd)
return ESP_FAIL; return ESP_FAIL;
} }
ESP_LOGD(TAG, LOG_FMT("success")); ESP_LOGD(TAG, LOG_FMT("success"));
sd->lru_counter = httpd_sess_get_lru_counter(); session->lru_counter = ++hd->lru_counter;
return ESP_OK; return ESP_OK;
} }
@@ -319,87 +435,58 @@ esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
/* Search for the socket database entry */
struct httpd_data *hd = (struct httpd_data *) handle; struct httpd_data *hd = (struct httpd_data *) handle;
int i;
for (i = 0; i < hd->config.max_open_sockets; i++) { enum_context_t context = {
if (hd->hd_sd[i].fd == sockfd) { .task = HTTPD_TASK_FIND_FD,
hd->hd_sd[i].lru_counter = httpd_sess_get_lru_counter(); .fd = sockfd
};
httpd_sess_enum(hd, enum_function, &context);
if (context.session) {
context.session->lru_counter = ++hd->lru_counter;
return ESP_OK; return ESP_OK;
} }
}
return ESP_ERR_NOT_FOUND; return ESP_ERR_NOT_FOUND;
} }
esp_err_t httpd_sess_close_lru(struct httpd_data *hd) esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
{ {
uint64_t lru_counter = UINT64_MAX; enum_context_t context = {
int lru_fd = -1; .task = HTTPD_TASK_FIND_LOWEST_LRU,
int i; .lru_counter = UINT64_MAX,
for (i = 0; i < hd->config.max_open_sockets; i++) { .fd = -1
/* If a descriptor is -1, there is no need to close any session. };
* So, we can return from here, without finding the Least Recently Used httpd_sess_enum(hd, enum_function, &context);
* session if (!context.session) {
*/
if (hd->hd_sd[i].fd == -1) {
return ESP_OK; return ESP_OK;
} }
if (hd->hd_sd[i].lru_counter < lru_counter) { ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
lru_counter = hd->hd_sd[i].lru_counter; context.session->lru_socket = true;
lru_fd = hd->hd_sd[i].fd; return httpd_sess_trigger_close_(hd, context.session);
}
}
ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd);
struct sock_db *sd = httpd_sess_get(hd, lru_fd);
sd->lru_socket = true;
return httpd_sess_trigger_close(hd, lru_fd);
} }
int httpd_sess_iterate(struct httpd_data *hd, int start_fd) esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session)
{ {
int start_index = 0; if (!session) {
int i; return ESP_ERR_NOT_FOUND;
if (start_fd != -1) {
/* Take our index to where this fd is stored */
for (i = 0; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd == start_fd) {
start_index = i + 1;
break;
}
}
}
for (i = start_index; i < hd->config.max_open_sockets; i++) {
if (hd->hd_sd[i].fd != -1) {
return hd->hd_sd[i].fd;
}
}
return -1;
}
static void httpd_sess_close(void *arg)
{
struct sock_db *sock_db = (struct sock_db *)arg;
if (sock_db) {
if (sock_db->lru_counter == 0 && !sock_db->lru_socket) {
ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
return;
}
int fd = sock_db->fd;
sock_db->lru_socket = false;
struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
httpd_sess_delete(hd, fd);
close(fd);
} }
return httpd_queue_work(handle, httpd_sess_close, session);
} }
esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd) esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
{ {
struct sock_db *sock_db = httpd_sess_get(handle, sockfd); struct sock_db *session = httpd_sess_get(handle, sockfd);
if (sock_db) { if (!session) {
return httpd_queue_work(handle, httpd_sess_close, sock_db);
}
return ESP_ERR_NOT_FOUND; 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);
}

View File

@@ -21,4 +21,6 @@ The Example consists of HTTPD server demo with demostration of URI handling :
3. "curl -X PUT -d "0" 192.168.43.130:80/ctrl" - disable /hello and /echo handlers 3. "curl -X PUT -d "0" 192.168.43.130:80/ctrl" - disable /hello and /echo handlers
4. "curl -X PUT -d "1" 192.168.43.130:80/ctrl" - enable /hello and /echo handlers 4. "curl -X PUT -d "1" 192.168.43.130:80/ctrl" - enable /hello and /echo handlers
* If the server log shows "httpd_parse: parse_block: request URI/header too long", especially when handling POST requests, then you probably need to increase HTTPD_MAX_REQ_HDR_LEN, which you can find in the project configuration menu (`idf.py menuconfig`): Component config -> HTTP Server -> Max HTTP Request Header Length
See the README.md file in the upper level 'examples' directory for more information about examples. See the README.md file in the upper level 'examples' directory for more information about examples.