The httpd_sess_* functions have been re-designed to speed up the httpd server

Signed-off-by: Shubham Kulkarni <shubham.kulkarni@espressif.com>

Merges: https://github.com/espressif/esp-idf/pull/6313
This commit is contained in:
Jermolinski
2021-01-30 10:27:06 +01:00
committed by bot
parent e9ecc4a0af
commit 282726b619
3 changed files with 429 additions and 304 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
* *
@@ -171,33 +196,23 @@ 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() * @param[in] hd Server instance data
* to continue the iteration correctly. This ensures that the * @param[in] session Session
* 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] clifd Descriptor of the client to be removed from the 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.
@@ -257,12 +257,12 @@ bool httpd_is_sess_available(struct httpd_data *hd);
* comes in use, as it checks the socket's pending data * comes in use, as it checks the socket's pending data
* 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 return hd->hd_req_aux.sd;
* corresponding to the request */
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,103 @@ 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) { ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i])); return ESP_FAIL;
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 */ // Clear session data
if (hd->config.open_fn) { memset(session, 0, sizeof (struct sock_db));
esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd); session->fd = newfd;
if (ret != ESP_OK) { session->handle = (httpd_handle_t) hd;
httpd_sess_delete(hd, hd->hd_sd[i].fd); session->send_fn = httpd_default_send;
ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd); session->recv_fn = httpd_default_recv;
return ret;
} // Call user-defined session opening function
} if (hd->config.open_fn) {
return ESP_OK; 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("unable to launch session for fd = %d"), newfd);
return ESP_FAIL; // 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) void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn)
{ {
if (ctx) { if ((!ctx) || (!*ctx)) {
if (free_fn) { return;
free_fn(ctx); }
} else { if (free_fn) {
free(ctx); 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 +303,123 @@ 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 */
if (hd->config.close_fn) { // Call close function if defined
hd->config.close_fn(hd, fd); if (hd->config.close_fn) {
} hd->config.close_fn(hd, session->fd);
}
/* release 'user' context */ else {
if (hd->hd_sd[i].ctx) { close(session->fd);
if (hd->hd_sd[i].free_ctx) { }
hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx);
} else { // clear all contexts
free(hd->hd_sd[i].ctx); httpd_sess_clear_ctx(session);
}
hd->hd_sd[i].ctx = NULL; // mark session slot as available
hd->hd_sd[i].free_ctx = NULL; session->fd = -1;
}
// decrement number of sessions
/* release 'transport' context */ hd->hd_sd_active_count--;
if (hd->hd_sd[i].transport_ctx) { ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
if (hd->hd_sd[i].free_transport_ctx) { if (!hd->hd_sd_active_count) {
hd->hd_sd[i].free_transport_ctx(hd->hd_sd[i].transport_ctx); hd->lru_counter = 0;
} 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 */
hd->hd_sd[i].fd = -1;
break;
} else if (hd->hd_sd[i].fd != -1) {
/* Return the fd just preceding the one being
* deleted so that iterator can continue from
* the correct fd */
pre_sess_fd = hd->hd_sd[i].fd;
}
} }
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 +427,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 +437,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
return ESP_OK; };
} 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; 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) {
*/ return ESP_OK;
if (hd->hd_sd[i].fd == -1) {
return ESP_OK;
}
if (hd->hd_sd[i].lru_counter < lru_counter) {
lru_counter = hd->hd_sd[i].lru_counter;
lru_fd = hd->hd_sd[i].fd;
}
} }
ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd); ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
struct sock_db *sd = httpd_sess_get(hd, lru_fd); context.session->lru_socket = true;
sd->lru_socket = true; return httpd_sess_trigger_close_(hd, context.session);
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 httpd_sess_trigger_close_(handle,session);
return ESP_ERR_NOT_FOUND; }
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);
} }