kopia lustrzana https://github.com/espressif/esp-idf
Merge branch 'feature/http_server_optimizations' into 'master'
esp_http_server optimisations Closes IDFGH-4484 and IDFGH-4741 See merge request espressif/esp-idf!12240pull/6718/head
commit
907ea44ee9
|
@ -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
|
||||
* @}
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Ładowanie…
Reference in New Issue