Replaced the os timer in http server by LRU counter. The timestamp is unnecessary for LRU.
392 lines
11 KiB
C
392 lines
11 KiB
C
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// 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 <esp_http_server.h>
|
|
#include "esp_httpd_priv.h"
|
|
|
|
static const char *TAG = "httpd_sess";
|
|
|
|
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;
|
|
}
|
|
|
|
struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
|
|
{
|
|
if (hd == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Check if called inside a request handler, and the
|
|
* session sockfd in use is same as the parameter */
|
|
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;
|
|
}
|
|
|
|
esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
|
|
{
|
|
ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
|
|
|
|
if (httpd_sess_get(hd, newfd)) {
|
|
ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; i < hd->config.max_open_sockets; i++) {
|
|
if (hd->hd_sd[i].fd == -1) {
|
|
memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i]));
|
|
hd->hd_sd[i].fd = newfd;
|
|
hd->hd_sd[i].handle = (httpd_handle_t) hd;
|
|
hd->hd_sd[i].send_fn = httpd_default_send;
|
|
hd->hd_sd[i].recv_fn = httpd_default_recv;
|
|
|
|
/* Call user-defined session opening function */
|
|
if (hd->config.open_fn) {
|
|
esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd);
|
|
if (ret != ESP_OK) return ret;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
}
|
|
ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
|
|
{
|
|
struct sock_db *sd = httpd_sess_get(handle, sockfd);
|
|
if (sd == 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 */
|
|
struct httpd_data *hd = (struct httpd_data *) handle;
|
|
if (hd->hd_req_aux.sd == sd) {
|
|
return hd->hd_req.sess_ctx;
|
|
}
|
|
|
|
return sd->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) {
|
|
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 */
|
|
struct httpd_data *hd = (struct httpd_data *) handle;
|
|
if (hd->hd_req_aux.sd == sd) {
|
|
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);
|
|
}
|
|
hd->hd_req.sess_ctx = ctx;
|
|
}
|
|
hd->hd_req.free_ctx = free_fn;
|
|
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;
|
|
}
|
|
sd->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;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
sd->free_transport_ctx = free_fn;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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()
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool httpd_sess_pending(struct httpd_data *hd, int fd)
|
|
{
|
|
struct sock_db *sd = httpd_sess_get(hd, fd);
|
|
if (! sd) {
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (sd->pending_fn) {
|
|
// test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
|
|
// this should check e.g. for the SSL data buffer
|
|
if (sd->pending_fn(hd, fd) > 0) return true;
|
|
}
|
|
|
|
return (sd->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)
|
|
{
|
|
struct sock_db *sd = httpd_sess_get(hd, newfd);
|
|
if (! sd) {
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
|
|
if (httpd_req_new(hd, sd) != ESP_OK) {
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
|
|
if (httpd_req_delete(hd) != ESP_OK) {
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGD(TAG, LOG_FMT("success"));
|
|
sd->lru_counter = httpd_sess_get_lru_counter();
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
|
|
{
|
|
if (handle == NULL) {
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd);
|
|
return httpd_sess_trigger_close(hd, lru_fd);
|
|
}
|
|
|
|
int httpd_sess_iterate(struct httpd_data *hd, int start_fd)
|
|
{
|
|
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) {
|
|
int fd = sock_db->fd;
|
|
struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
|
|
httpd_sess_delete(hd, fd);
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|