diff --git a/components/esp_http_server/src/esp_httpd_priv.h b/components/esp_http_server/src/esp_httpd_priv.h index 5db4d922e2..423749afc7 100644 --- a/components/esp_http_server/src/esp_httpd_priv.h +++ b/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 * @} */ diff --git a/components/esp_http_server/src/httpd_main.c b/components/esp_http_server/src/httpd_main.c index fda2b241b8..8f89250edb 100644 --- a/components/esp_http_server/src/httpd_main.c +++ b/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; } diff --git a/components/esp_http_server/src/httpd_sess.c b/components/esp_http_server/src/httpd_sess.c index 5a8ecfdcff..462c6521c0 100644 --- a/components/esp_http_server/src/httpd_sess.c +++ b/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 #include #include +#include +#include +#include #include #include "esp_httpd_priv.h" static const char *TAG = "httpd_sess"; +typedef enum { + HTTPD_TASK_NONE = 0, + HTTPD_TASK_INIT, // Init session + HTTPD_TASK_GET_ACTIVE, // Get active session (fd!=-1) + HTTPD_TASK_GET_FREE, // Get free session slot (fd<0) + HTTPD_TASK_FIND_FD, // Find session with specific fd + HTTPD_TASK_SET_DESCRIPTOR, // Set descriptor + HTTPD_TASK_DELETE_INVALID, // Delete invalid session + HTTPD_TASK_FIND_LOWEST_LRU, // Find session with lowest lru + HTTPD_TASK_CLOSE // Close session +} task_t; + +typedef struct { + task_t task; + int fd; + fd_set *fdset; + int max_fd; + struct httpd_data *hd; + uint64_t lru_counter; + struct sock_db *session; +} enum_context_t; + +void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context) +{ + if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) { + return; + } + + struct sock_db *current = hd->hd_sd; + struct sock_db *end = hd->hd_sd + hd->config.max_open_sockets - 1; + + while (current <= end) { + if (enum_function && (!enum_function(current, context))) { + break; + } + current++; + } +} + +// Check if a FD is valid +static int fd_is_valid(int fd) +{ + return fcntl(fd, F_GETFD) != -1 || errno != EBADF; +} + +static int enum_function(struct sock_db *session, void *context) +{ + if ((!session) || (!context)) { + return 0; + } + enum_context_t *ctx = (enum_context_t *) context; + int found = 0; + switch (ctx->task) { + // Initialize session + case HTTPD_TASK_INIT: + session->fd = -1; + session->ctx = NULL; + break; + // Get active session + case HTTPD_TASK_GET_ACTIVE: + found = (session->fd != -1); + break; + // Get free slot + case HTTPD_TASK_GET_FREE: + found = (session->fd < 0); + break; + // Find fd + case HTTPD_TASK_FIND_FD: + found = (session->fd == ctx->fd); + break; + // Set descriptor + case HTTPD_TASK_SET_DESCRIPTOR: + if (session->fd != -1) { + FD_SET(session->fd, ctx->fdset); + if (session->fd > ctx->max_fd) { + ctx->max_fd = session->fd; + } + } + break; + // Delete invalid session + case HTTPD_TASK_DELETE_INVALID: + if (!fd_is_valid(session->fd)) { + ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), session->fd); + httpd_sess_delete(ctx->hd, session); + } + break; + // Find lowest lru + case HTTPD_TASK_FIND_LOWEST_LRU: + // Found free slot - no need to check other sessions + if (session->fd == -1) { + return 0; + } + // 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) { - int i; - for (i = 0; i < hd->config.max_open_sockets; i++) { - if (hd->hd_sd[i].fd == -1) { - return true; - } - } - return false; + 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; + 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; + } - /* 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; + // 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) -{ - static uint64_t lru_counter = 0; - return ++lru_counter; -} - void httpd_sess_delete_invalid(struct httpd_data *hd) { - 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); - } - } + enum_context_t context = { + .task = HTTPD_TASK_DELETE_INVALID, + .hd = hd + }; + 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); - 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); - } - - /* 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; - } - - /* 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; - } - - /* 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; - } + if ((!hd) || (!session) || (session->fd < 0)) { + return; + } + + ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd); + + // Call close function if defined + if (hd->config.close_fn) { + hd->config.close_fn(hd, session->fd); + } else { + close(session->fd); + } + + // clear all contexts + httpd_sess_clear_ctx(session); + + // mark session slot as available + session->fd = -1; + + // decrement number of sessions + hd->hd_sd_active_count--; + ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count); + if (!hd->hd_sd_active_count) { + hd->lru_counter = 0; } - 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; - } - } - } - - 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); + if (!session) { + return ESP_ERR_NOT_FOUND; } + return httpd_queue_work(handle, httpd_sess_close, session); } esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd) { - struct sock_db *sock_db = httpd_sess_get(handle, sockfd); - if (sock_db) { - return httpd_queue_work(handle, httpd_sess_close, sock_db); + struct sock_db *session = httpd_sess_get(handle, sockfd); + if (!session) { + return ESP_ERR_NOT_FOUND; } - - return ESP_ERR_NOT_FOUND; + return httpd_sess_trigger_close_(handle, session); +} + +void httpd_sess_close_all(struct httpd_data *hd) +{ + enum_context_t context = { + .task = HTTPD_TASK_CLOSE, + .hd = hd + }; + httpd_sess_enum(hd, enum_function, &context); } diff --git a/examples/protocols/http_server/simple/README.md b/examples/protocols/http_server/simple/README.md index 30eed559a5..5c6b9ab684 100644 --- a/examples/protocols/http_server/simple/README.md +++ b/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.