Просмотр исходного кода

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
Mahavir Jain 4 лет назад
Родитель
Сommit
907ea44ee9

+ 60 - 35
components/esp_http_server/src/esp_httpd_priv.h

@@ -117,9 +117,11 @@ struct httpd_data {
     int msg_fd;                             /*!< Ctrl message sender FD */
     struct thread_data hd_td;               /*!< Information for the HTTPD thread */
     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 */
     struct httpd_req hd_req;                /*!< The current HTTPD request */
     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 */
     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
  *
@@ -171,33 +196,23 @@ esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd);
 /**
  * @brief   Processes incoming HTTP requests
  *
- * @param[in] hd    Server instance data
- * @param[in] clifd Descriptor of the client from which data is to be received
+ * @param[in] hd      Server instance data
+ * @param[in] session Session
  *
  * @return
  *  - 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_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
  *          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] 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
+ * @param[in] hd      Server instance data
+ * @param[in] session Session
  */
-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
@@ -205,7 +220,7 @@ int httpd_sess_delete(struct httpd_data *hd, int clifd);
  * @param[in] ctx     Pointer to 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
@@ -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);
 
-/**
- * @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.
  *          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
  * buffer.
  *
- * @param[in] hd  Server instance data
- * @param[in] fd  Client descriptor
+ * @param[in] hd      Server instance data
+ * @param[in] session Session
  *
  * @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
@@ -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);
 
+/**
+ * @brief   Closes all sessions
+ *
+ * @param[in] hd  Server instance data
+ *
+ */
+void httpd_sess_close_all(struct httpd_data *hd);
+
 /** 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);
 
+/**
+ * @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
  * @}
  */

+ 39 - 28
components/esp_http_server/src/httpd_main.c

@@ -24,6 +24,11 @@
 #include "esp_httpd_priv.h"
 #include "ctrl_sock.h"
 
+typedef struct {
+    fd_set *fdset;
+    struct httpd_data *hd;
+} process_session_context_t;
+
 static const char *TAG = "httpd";
 
 static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
@@ -39,7 +44,7 @@ static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
              * therefore httpd_accept_conn() will be called again, but this time
              * with space available for one session
              */
-       }
+        }
     }
 
     struct sockaddr_in addr_from;
@@ -55,12 +60,12 @@ static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd)
     /* Set recv timeout of this fd as per config */
     tv.tv_sec = hd->config.recv_wait_timeout;
     tv.tv_usec = 0;
-    setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
+    setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
 
     /* Set send timeout of this fd as per config */
     tv.tv_sec = hd->config.send_wait_timeout;
     tv.tv_usec = 0;
-    setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
+    setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
 
     if (ESP_OK != httpd_sess_new(hd, new_fd)) {
         ESP_LOGW(TAG, LOG_FMT("session creation failed"));
@@ -132,15 +137,6 @@ void *httpd_get_global_transport_ctx(httpd_handle_t handle)
     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)
 {
@@ -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 */
 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
      * sessions? */
-    int fd = -1;
-    while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
-        if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
-            ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
-            if (httpd_sess_process(hd, fd) != ESP_OK) {
-                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);
-            }
-        }
-    }
+    process_session_context_t context = {
+        .fdset = &read_set,
+        .hd = hd
+    };
+    httpd_sess_enum(hd, httpd_process_session, &context);
 
     /* Case2: Do we have any incoming connection requests to
      * process? */
@@ -253,7 +264,7 @@ static void httpd_thread(void *arg)
     ESP_LOGD(TAG, LOG_FMT("web server exiting"));
     close(hd->msg_fd);
     cs_free_ctrl_sock(hd->ctrl_fd);
-    httpd_close_all_sessions(hd);
+    httpd_sess_close_all(hd);
     close(hd->listen_fd);
     hd->hd_td.status = THREAD_STOPPED;
     httpd_os_thread_delete();
@@ -406,8 +417,8 @@ esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
      */
     if (CONFIG_LWIP_MAX_SOCKETS < config->max_open_sockets + 3) {
         ESP_LOGE(TAG, "Configuration option max_open_sockets is too large (max allowed %d)\n\t"
-                      "Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
-                      CONFIG_LWIP_MAX_SOCKETS - 3);
+                 "Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
+                 CONFIG_LWIP_MAX_SOCKETS - 3);
         return ESP_ERR_INVALID_ARG;
     }
 

+ 324 - 237
components/esp_http_server/src/httpd_sess.c

@@ -11,49 +11,183 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-
-
 #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";
 
-bool httpd_is_sess_available(struct httpd_data *hd)
+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)
 {
-    int i;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        if (hd->hd_sd[i].fd == -1) {
-            return true;
+    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;
     }
-    return false;
+    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 == NULL) {
+    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 */
+    // 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)) {
-        /* Just return the pointer to the sock_db
-         * corresponding to the request */
         return hd->hd_req_aux.sd;
     }
 
-    int i;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        if (hd->hd_sd[i].fd == sockfd) {
-            return &hd->hd_sd[i];
-        }
-    }
-    return NULL;
+    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)
@@ -65,78 +199,102 @@ esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
         return ESP_FAIL;
     }
 
-    int i;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        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;
+    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;
+
+    // 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("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 (free_fn) {
-            free_fn(ctx);
-        } else {
-            free(ctx);
-        }
+    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)
 {
-    struct sock_db *sd = httpd_sess_get(handle, sockfd);
-    if (sd == NULL) {
+    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 */
+    // 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 == sd) {
+    if (hd->hd_req_aux.sd == session) {
         return hd->hd_req.sess_ctx;
     }
-
-    return sd->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 *sd = httpd_sess_get(handle, sockfd);
-    if (sd == NULL) {
+    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 */
+    // 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 == sd) {
+    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 (sd->ctx != hd->hd_req.sess_ctx) {
-                /* Free previous context */
-                httpd_sess_free_ctx(hd->hd_req.sess_ctx, hd->hd_req.free_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;
         }
@@ -144,164 +302,122 @@ void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free
         return;
     }
 
-    /* Else set the context inside the sock_db structure */
-    if (sd->ctx != ctx) {
-        /* Free previous context */
-        httpd_sess_free_ctx(sd->ctx, sd->free_ctx);
-        sd->ctx = ctx;
+    // 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;
     }
-    sd->free_ctx = free_fn;
+    session->free_ctx = free_fn;
 }
 
 void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
 {
-    struct sock_db *sd = httpd_sess_get(handle, sockfd);
-    if (sd == NULL) {
-        return NULL;
-    }
-
-    return sd->transport_ctx;
+    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 *sd = httpd_sess_get(handle, sockfd);
-    if (sd == NULL) {
+    struct sock_db *session = httpd_sess_get(handle, sockfd);
+    if (!session) {
         return;
     }
 
-    if (sd->transport_ctx != ctx) {
-        /* Free previous transport context */
-        httpd_sess_free_ctx(sd->transport_ctx, sd->free_transport_ctx);
-        sd->transport_ctx = ctx;
+    if (session->transport_ctx != ctx) {
+        // Free previous transport context
+        httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_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,
-                                fd_set *fdset, int *maxfd)
+void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
 {
-    int i;
-    *maxfd = -1;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        if (hd->hd_sd[i].fd != -1) {
-            FD_SET(hd->hd_sd[i].fd, fdset);
-            if (hd->hd_sd[i].fd > *maxfd) {
-                *maxfd = hd->hd_sd[i].fd;
-            }
-        }
+    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;
     }
 }
 
-/** 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)
+void httpd_sess_delete_invalid(struct httpd_data *hd)
 {
-    static uint64_t lru_counter = 0;
-    return ++lru_counter;
+    enum_context_t context = {
+        .task = HTTPD_TASK_DELETE_INVALID,
+        .hd = hd
+    };
+    httpd_sess_enum(hd, enum_function, &context);
 }
 
-void httpd_sess_delete_invalid(struct httpd_data *hd)
+void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
 {
-    for (int i = 0; i < hd->config.max_open_sockets; i++) {
-        if (hd->hd_sd[i].fd != -1 && !fd_is_valid(hd->hd_sd[i].fd)) {
-            ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), hd->hd_sd[i].fd);
-            httpd_sess_delete(hd, hd->hd_sd[i].fd);
-        }
+    if ((!hd) || (!session) || (session->fd < 0)) {
+        return;
     }
-}
 
-int httpd_sess_delete(struct httpd_data *hd, int fd)
-{
-    ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd);
-    int i;
-    int pre_sess_fd = -1;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        if (hd->hd_sd[i].fd == fd) {
-            /* global close handler */
-            if (hd->config.close_fn) {
-                hd->config.close_fn(hd, fd);
-            }
+    ESP_LOGD(TAG, LOG_FMT("fd = %d"), 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 {
-                    free(hd->hd_sd[i].ctx);
-                }
-                hd->hd_sd[i].ctx = NULL;
-                hd->hd_sd[i].free_ctx = NULL;
-            }
+    // Call close function if defined
+    if (hd->config.close_fn) {
+        hd->config.close_fn(hd, session->fd);
+    } else {
+        close(session->fd);
+    }
 
-            /* release 'transport' context */
-            if (hd->hd_sd[i].transport_ctx) {
-                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;
-            }
+    // clear all contexts
+    httpd_sess_clear_ctx(session);
 
-            /* 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;
-        }
+    // 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;
     }
-    return pre_sess_fd;
 }
 
 void httpd_sess_init(struct httpd_data *hd)
 {
-    int i;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        hd->hd_sd[i].fd = -1;
-        hd->hd_sd[i].ctx = NULL;
-    }
+    enum_context_t context = {
+        .task = HTTPD_TASK_INIT
+    };
+    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 (! sd) {
-        return ESP_FAIL;
+    if (!session) {
+        return false;
     }
-
-    if (sd->pending_fn) {
+    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 (sd->pending_fn(hd, fd) > 0) {
+        if (session->pending_fn(hd, session->fd) > 0) {
             return true;
         }
     }
-
-    return (sd->pending_len != 0);
+    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, 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 (! sd) {
+    if ((!hd) || (!session)) {
         return ESP_FAIL;
     }
 
     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;
     }
     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;
     }
     ESP_LOGD(TAG, LOG_FMT("success"));
-    sd->lru_counter = httpd_sess_get_lru_counter();
+    session->lru_counter = ++hd->lru_counter;
     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;
     }
 
-    /* Search for the socket database entry */
     struct httpd_data *hd = (struct httpd_data *) handle;
-    int i;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        if (hd->hd_sd[i].fd == sockfd) {
-            hd->hd_sd[i].lru_counter = httpd_sess_get_lru_counter();
-            return ESP_OK;
-        }
+
+    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)
 {
-    uint64_t lru_counter = UINT64_MAX;
-    int lru_fd = -1;
-    int i;
-    for (i = 0; i < hd->config.max_open_sockets; i++) {
-        /* 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
-         * session
-         */
-        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;
-        }
+    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("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);
+    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);
 }
 
-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;
-    int i;
-
-    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;
-            }
-        }
+    if (!session) {
+        return ESP_ERR_NOT_FOUND;
     }
-
-    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;
+    return httpd_queue_work(handle, httpd_sess_close, session);
 }
 
-static void httpd_sess_close(void *arg)
+esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
 {
-    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);
+    struct sock_db *session = httpd_sess_get(handle, sockfd);
+    if (!session) {
+        return ESP_ERR_NOT_FOUND;
     }
+    return httpd_sess_trigger_close_(handle, session);
 }
 
-esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
+void httpd_sess_close_all(struct httpd_data *hd)
 {
-    struct sock_db *sock_db = httpd_sess_get(handle, sockfd);
-    if (sock_db) {
-        return httpd_queue_work(handle, httpd_sess_close, sock_db);
-    }
-
-    return ESP_ERR_NOT_FOUND;
+    enum_context_t context = {
+        .task = HTTPD_TASK_CLOSE,
+        .hd = hd
+    };
+    httpd_sess_enum(hd, enum_function, &context);
 }

+ 2 - 0
examples/protocols/http_server/simple/README.md

@@ -20,5 +20,7 @@ The Example consists of HTTPD server demo with demostration of URI handling :
                 * since the server echoes back the request body, the two files should be same, as can be confirmed using : "cmp anyfile tmpfile"
             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
+            
+* 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.