| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 | // 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 <string.h>#include <sys/socket.h>#include <sys/param.h>#include <errno.h>#include <esp_log.h>#include <esp_err.h>#include <assert.h>#include <esp_http_server.h>#include <_esp_http_server.h>#include "esp_httpd_priv.h"#include "ctrl_sock.h"static const char *TAG = "_httpd";struct httpd_ctrl_data {    enum httpd_ctrl_msg {        HTTPD_CTRL_SHUTDOWN,        HTTPD_CTRL_WORK,    } hc_msg;    httpd_work_fn_t hc_work;    void *hc_work_arg;};static esp_err_t _httpd_server_init(struct httpd_data *hd){    int fd = socket(PF_INET6, SOCK_STREAM, 0);    if (fd < 0) {        ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);        return ESP_FAIL;    }    struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;    struct sockaddr_in6 serv_addr = {        .sin6_family  = PF_INET6,        .sin6_addr    = inaddr_any,        .sin6_port    = htons(hd->config.server_port)    };    /* Enable SO_REUSEADDR to allow binding to the same     * address and port when restarting the server */    int enable = 1;    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {        /* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But         * it does not affect the normal working of the HTTP Server */        ESP_LOGW(TAG, LOG_FMT("error enabling SO_REUSEADDR (%d)"), errno);    }    int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));    if (ret < 0) {        ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);        close(fd);        return ESP_FAIL;    }    ret = listen(fd, hd->config.backlog_conn);    if (ret < 0) {        ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);        close(fd);        return ESP_FAIL;    }    int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);    if (ctrl_fd < 0) {        ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);        close(fd);        return ESP_FAIL;    }    int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);    if (msg_fd < 0) {        ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);        close(fd);        close(ctrl_fd);        return ESP_FAIL;    }    hd->listen_fd = fd;    hd->ctrl_fd = ctrl_fd;    hd->msg_fd  = msg_fd;    return ESP_OK;}static void _httpd_process_ctrl_msg(struct httpd_data *hd){    struct httpd_ctrl_data msg;    int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);    if (ret <= 0) {        ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);        return;    }    if (ret != sizeof(msg)) {        ESP_LOGW(TAG, LOG_FMT("incomplete msg"));        return;    }    switch (msg.hc_msg) {    case HTTPD_CTRL_WORK:        if (msg.hc_work) {            ESP_LOGD(TAG, LOG_FMT("work"));            (*msg.hc_work)(msg.hc_work_arg);        }        break;    case HTTPD_CTRL_SHUTDOWN:        ESP_LOGD(TAG, LOG_FMT("shutdown"));        hd->hd_td.status = THREAD_STOPPING;        break;    default:        break;    }}static esp_err_t _httpd_accept_conn(struct httpd_data *hd, int listen_fd){    /* If no space is available for new session, close the least recently used one */    if (hd->config.lru_purge_enable == true) {        if (!httpd_is_sess_available(hd)) {            /* Queue asynchronous closure of the least recently used session */            return httpd_sess_close_lru(hd);            /* Returning from this allowes the main server thread to process             * the queued asynchronous control message for closing LRU session.             * Since connection request hasn't been addressed yet using accept()             * therefore _httpd_accept_conn() will be called again, but this time             * with space available for one session             */       }    }    struct sockaddr_in addr_from;    socklen_t addr_from_len = sizeof(addr_from);    int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);    if (new_fd < 0) {        ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);        return ESP_FAIL;    }    ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);    struct timeval tv;    /* 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));    /* 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));    if (ESP_OK != httpd_sess_new(hd, new_fd)) {        ESP_LOGW(TAG, LOG_FMT("session creation failed"));        close(new_fd);        return ESP_FAIL;    }    ESP_LOGD(TAG, LOG_FMT("complete"));    return ESP_OK;}/* Manage in-coming connection or data requests */static esp_err_t _httpd_server(struct httpd_data *hd){    fd_set read_set;    FD_ZERO(&read_set);    if (hd->config.lru_purge_enable || httpd_is_sess_available(hd)) {        /* Only listen for new connections if server has capacity to         * handle more (or when LRU purge is enabled, in which case         * older connections will be closed) */        FD_SET(hd->listen_fd, &read_set);    }    FD_SET(hd->ctrl_fd, &read_set);    int tmp_max_fd;    httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);    int maxfd = MAX(hd->listen_fd, tmp_max_fd);    tmp_max_fd = maxfd;    maxfd = MAX(hd->ctrl_fd, tmp_max_fd);    ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);    int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);    if (active_cnt < 0) {        ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);        httpd_sess_delete_invalid(hd);        return ESP_OK;    }    /* Case0: Do we have a control message? */    if (FD_ISSET(hd->ctrl_fd, &read_set)) {        ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));        _httpd_process_ctrl_msg(hd);        if (hd->hd_td.status == THREAD_STOPPING) {            ESP_LOGD(TAG, LOG_FMT("stopping thread"));            return ESP_FAIL;        }    }    /* 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);            }        }    }    /* Case2: Do we have any incoming connection requests to     * process? */    if (FD_ISSET(hd->listen_fd, &read_set)) {        ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);        if (_httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {            ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));        }    }    return ESP_OK;}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);    }}/* The main HTTPD thread */static void _httpd_thread(void *arg){    int ret;    struct httpd_data *hd = (struct httpd_data *) arg;    hd->hd_td.status = THREAD_RUNNING;    ESP_LOGD(TAG, LOG_FMT("web server started"));    while (1) {        ret = _httpd_server(hd);        if (ret != ESP_OK) {            break;        }    }    ESP_LOGD(TAG, LOG_FMT("web server exiting"));    close(hd->msg_fd);    cs_free_ctrl_sock(hd->ctrl_fd);    _httpd_close_all_sessions(hd);    close(hd->listen_fd);    hd->hd_td.status = THREAD_STOPPED;    httpd_os_thread_delete();}static struct httpd_data *__httpd_create(const httpd_config_t *config){    /* Allocate memory for httpd instance data */    struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));    if (!hd) {        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));        return NULL;    }    hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));    if (!hd->hd_calls) {        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));        free(hd);        return NULL;    }    hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));    if (!hd->hd_sd) {        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));        free(hd->hd_calls);        free(hd);        return NULL;    }    struct httpd_req_aux *ra = &hd->hd_req_aux;    ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));    if (!ra->resp_hdrs) {        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));        free(hd->hd_sd);        free(hd->hd_calls);        free(hd);        return NULL;    }    hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));    if (!hd->err_handler_fns) {        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));        free(ra->resp_hdrs);        free(hd->hd_sd);        free(hd->hd_calls);        free(hd);        return NULL;    }    /* Save the configuration for this instance */    hd->config = *config;    return hd;}static void _httpd_delete(struct httpd_data *hd){    struct httpd_req_aux *ra = &hd->hd_req_aux;    /* Free memory of httpd instance data */    free(hd->err_handler_fns);    free(ra->resp_hdrs);    free(hd->hd_sd);    /* Free registered URI handlers */    httpd_unregister_all_uri_handlers(hd);    free(hd->hd_calls);    free(hd);}esp_err_t __httpd_start(httpd_handle_t *handle, const httpd_config_t *config){    if (handle == NULL || config == NULL) {        return ESP_ERR_INVALID_ARG;    }    /* Sanity check about whether LWIP is configured for providing the     * maximum number of open sockets sufficient for the server. Though,     * this check doesn't guarantee that many sockets will actually be     * available at runtime as other processes may use up some sockets.     * Note that server also uses 3 sockets for its internal use :     *     1) listening for new TCP connections     *     2) for sending control messages over UDP     *     3) for receiving control messages over UDP     * So the total number of required sockets is 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"                      "Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",                      CONFIG_LWIP_MAX_SOCKETS - 3);        return ESP_ERR_INVALID_ARG;    }    struct httpd_data *hd = __httpd_create(config);    if (hd == NULL) {        /* Failed to allocate memory */        return ESP_ERR_HTTPD_ALLOC_MEM;    }    if (_httpd_server_init(hd) != ESP_OK) {        _httpd_delete(hd);        return ESP_FAIL;    }    httpd_sess_init(hd);    if (__httpd_os_thread_create_static(&hd->hd_td.handle, "httpd",                               hd->config.stack_size,                               hd->config.task_priority,                               _httpd_thread, hd,                               hd->config.core_id) != ESP_OK) {        /* Failed to launch task */        _httpd_delete(hd);        return ESP_ERR_HTTPD_TASK;    }    *handle = (httpd_handle_t *)hd;    return ESP_OK;}
 |