Quellcode durchsuchen

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 vor 4 Jahren
Ursprung
Commit
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 */
     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] clifd Descriptor of the client from which data is to be received
+ * @param[in] hd      Server instance data
+ * @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] 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
  * @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] fd  Client descriptor
+ * @param[in] hd      Server instance data
+ * @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
  * @}
  * @}
  */
  */

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

@@ -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)
@@ -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
              * therefore httpd_accept_conn() will be called again, but this time
              * with space available for one session
              * with space available for one session
              */
              */
-       }
+        }
     }
     }
 
 
     struct sockaddr_in addr_from;
     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 */
     /* Set recv timeout of this fd as per config */
     tv.tv_sec = hd->config.recv_wait_timeout;
     tv.tv_sec = hd->config.recv_wait_timeout;
     tv.tv_usec = 0;
     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 */
     /* Set send timeout of this fd as per config */
     tv.tv_sec = hd->config.send_wait_timeout;
     tv.tv_sec = hd->config.send_wait_timeout;
     tv.tv_usec = 0;
     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)) {
     if (ESP_OK != httpd_sess_new(hd, new_fd)) {
         ESP_LOGW(TAG, LOG_FMT("session creation failed"));
         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;
     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;
-    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
     /* 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();
@@ -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) {
     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"
         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;
         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.
 // 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";
 
 
-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)
 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
-     * 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)) {
     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;
-    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)
 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;
-    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)
 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;
         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;
     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 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)
 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;
         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;
     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
-             * 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;
             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 */
-    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)
 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)
 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;
         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)
 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)
         // 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 (sd->pending_len != 0);
+    return (session->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 (! sd) {
+    if ((!hd) || (!session)) {
         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++) {
-        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;
     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;
-    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"
                 * 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
             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.