Parcourir la source

https_server example: Add new WSS server example

Added a new https server example with WS support which runs
* keep-alive thread to send PINGs to clients
* async message to all active WS clients
Moved the existing https-server example to a subfolder

Closes https://github.com/espressif/esp-idf/issues/5733
Closes https://github.com/espressif/esp-idf/issues/5686
David Cermak il y a 5 ans
Parent
commit
76ca826758

+ 0 - 0
examples/protocols/https_server/CMakeLists.txt → examples/protocols/https_server/simple/CMakeLists.txt


+ 0 - 0
examples/protocols/https_server/Makefile → examples/protocols/https_server/simple/Makefile


+ 1 - 1
examples/protocols/https_server/README.md → examples/protocols/https_server/simple/README.md

@@ -4,7 +4,7 @@ This example creates a SSL server that returns a simple HTML page when you visit
 
 See the `esp_https_server` component documentation for details.
 
-Before using the example, open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../README.md) for more details.
+Before using the example, open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
 
 ## Certificates
 

+ 0 - 0
examples/protocols/https_server/main/CMakeLists.txt → examples/protocols/https_server/simple/main/CMakeLists.txt


+ 0 - 0
examples/protocols/https_server/main/certs/cacert.pem → examples/protocols/https_server/simple/main/certs/cacert.pem


+ 0 - 0
examples/protocols/https_server/main/certs/prvtkey.pem → examples/protocols/https_server/simple/main/certs/prvtkey.pem


+ 0 - 0
examples/protocols/https_server/main/component.mk → examples/protocols/https_server/simple/main/component.mk


+ 0 - 0
examples/protocols/https_server/main/main.c → examples/protocols/https_server/simple/main/main.c


+ 0 - 0
examples/protocols/https_server/sdkconfig.ci → examples/protocols/https_server/simple/sdkconfig.ci


+ 0 - 0
examples/protocols/https_server/sdkconfig.defaults → examples/protocols/https_server/simple/sdkconfig.defaults


+ 10 - 0
examples/protocols/https_server/wss_server/CMakeLists.txt

@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(wss_server)

+ 11 - 0
examples/protocols/https_server/wss_server/Makefile

@@ -0,0 +1,11 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := wss_server
+
+EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
+
+include $(IDF_PATH)/make/project.mk
+

+ 28 - 0
examples/protocols/https_server/wss_server/README.md

@@ -0,0 +1,28 @@
+# HTTP Websocket server with SSL support
+
+This example creates a SSL server and employs a simple Websocket request handler. It demonstrates handling multiple clients from the server including:
+* PING-PONG mechanism
+* Sending asynchronous messages to all clients
+
+See the `esp_https_server` component documentation for details.
+
+Before using the example, open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
+
+## Certificates
+
+You will need to approve a security exception in your browser. This is because of a self signed
+certificate; this will be always the case, unless you preload the CA root into your browser/system
+as trusted.
+
+You can generate a new certificate using the OpenSSL command line tool:
+
+```
+openssl req -newkey rsa:2048 -nodes -keyout prvtkey.pem -x509 -days 3650 -out cacert.pem -subj "/CN=ESP32 HTTPS server example"
+```
+
+Expiry time and metadata fields can be adjusted in the invocation.
+
+Please see the openssl man pages (man openssl-req) for more details.
+
+It is **strongly recommended** to not reuse the example certificate in your application;
+it is included only for demonstration.

+ 4 - 0
examples/protocols/https_server/wss_server/main/CMakeLists.txt

@@ -0,0 +1,4 @@
+idf_component_register(SRCS "wss_server_example.c" "keep_alive.c"
+                    INCLUDE_DIRS "."
+                    EMBED_TXTFILES "certs/cacert.pem"
+                                   "certs/prvtkey.pem")

+ 19 - 0
examples/protocols/https_server/wss_server/main/certs/cacert.pem

@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL
+BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx
+MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ
+UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T
+sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k
+qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd
+GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4
+sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb
+jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/
+ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud
+EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3
+emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY
+W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx
+bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN
+ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl
+hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo=
+-----END CERTIFICATE-----

+ 28 - 0
examples/protocols/https_server/wss_server/main/certs/prvtkey.pem

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH
+JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw
+h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT
+aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al
+3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg
+0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB
+vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui
+f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9
+Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y
+JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX
+49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc
++3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6
+pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D
+0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG
+YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV
+MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL
+CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin
+7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1
+noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8
+4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g
+Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/
+nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3
+q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2
+lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB
+jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr
+v/t+MeGJP/0Zw8v/X2CFll96
+-----END PRIVATE KEY-----

+ 7 - 0
examples/protocols/https_server/wss_server/main/component.mk

@@ -0,0 +1,7 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
+COMPONENT_EMBED_TXTFILES := certs/cacert.pem
+COMPONENT_EMBED_TXTFILES += certs/prvtkey.pem

+ 228 - 0
examples/protocols/https_server/wss_server/main/keep_alive.c

@@ -0,0 +1,228 @@
+/* Keep Alive engine for wss server example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <esp_log.h>
+#include <esp_system.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "freertos/task.h"
+#include "keep_alive.h"
+
+typedef enum {
+    NO_CLIENT = 0,
+    CLIENT_FD_ADD,
+    CLIENT_FD_REMOVE,
+    CLIENT_UPDATE,
+    CLIENT_ACTIVE,
+    STOP_TASK,
+} client_fd_action_type_t;
+
+typedef struct {
+    client_fd_action_type_t type;
+    int fd;
+    uint64_t last_seen;
+} client_fd_action_t;
+
+typedef struct wss_keep_alive_storage {
+    size_t max_clients;
+    wss_check_client_alive_cb_t check_client_alive_cb;
+    wss_check_client_alive_cb_t client_not_alive_cb;
+    size_t keep_alive_period_ms;
+    size_t not_alive_after_ms;
+    void * user_ctx;
+    QueueHandle_t q;
+    client_fd_action_t clients[];
+} wss_keep_alive_storage_t;
+
+typedef struct wss_keep_alive_storage* wss_keep_alive_t;
+
+static const char *TAG = "wss_keep_alive";
+
+static uint64_t _tick_get_ms(void)
+{
+    return esp_timer_get_time()/1000;
+}
+
+// Goes over active clients to find out how long we could sleep before checking who's alive
+static uint64_t get_max_delay(wss_keep_alive_t h)
+{
+    int64_t check_after_ms = 30000; // max delay, no need to check anyone
+    for (int i=0; i<h->max_clients; ++i) {
+        if (h->clients[i].type == CLIENT_ACTIVE) {
+            uint64_t check_this_client_at = h->clients[i].last_seen + h->keep_alive_period_ms;
+            if (check_this_client_at < check_after_ms + _tick_get_ms()) {
+                check_after_ms = check_this_client_at - _tick_get_ms();
+                if (check_after_ms < 0) {
+                    check_after_ms = 1000; // min delay, some client(s) not responding already
+                }
+            }
+        }
+    }
+    return check_after_ms;
+}
+
+
+static bool update_client(wss_keep_alive_t h, int sockfd, uint64_t timestamp)
+{
+    for (int i=0; i<h->max_clients; ++i) {
+        if (h->clients[i].type == CLIENT_ACTIVE && h->clients[i].fd == sockfd) {
+            h->clients[i].last_seen = timestamp;
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool remove_client(wss_keep_alive_t h, int sockfd)
+{
+    for (int i=0; i<h->max_clients; ++i) {
+        if (h->clients[i].type == CLIENT_ACTIVE && h->clients[i].fd == sockfd) {
+            h->clients[i].type = NO_CLIENT;
+            h->clients[i].fd = -1;
+            return true;
+        }
+    }
+    return false;
+}
+static bool add_new_client(wss_keep_alive_t h,int sockfd)
+{
+    for (int i=0; i<h->max_clients; ++i) {
+        if (h->clients[i].type == NO_CLIENT) {
+            h->clients[i].type = CLIENT_ACTIVE;
+            h->clients[i].fd = sockfd;
+            h->clients[i].last_seen = _tick_get_ms();
+            return true; // success
+        }
+    }
+    return false;
+}
+
+static void keep_alive_task(void* arg)
+{
+    wss_keep_alive_storage_t *keep_alive_storage = arg;
+    bool run_task = true;
+    client_fd_action_t client_action;
+    while (run_task) {
+        if (xQueueReceive(keep_alive_storage->q, (void *) &client_action,
+                get_max_delay(keep_alive_storage) / portTICK_PERIOD_MS) == pdTRUE) {
+            switch (client_action.type) {
+                case CLIENT_FD_ADD:
+                    if (!add_new_client(keep_alive_storage, client_action.fd)) {
+                        ESP_LOGE(TAG, "Cannot add new client");
+                    }
+                    break;
+                case CLIENT_FD_REMOVE:
+                    if (!remove_client(keep_alive_storage, client_action.fd)) {
+                        ESP_LOGE(TAG, "Cannot remove client fd:%d", client_action.fd);
+                    }
+                    break;
+                case CLIENT_UPDATE:
+                    if (!update_client(keep_alive_storage, client_action.fd, client_action.last_seen)) {
+                        ESP_LOGE(TAG, "Cannot find client fd:%d", client_action.fd);
+                    }
+                    break;
+                case STOP_TASK:
+                    run_task = false;
+                    break;
+                default:
+                    ESP_LOGE(TAG, "Unexpected client action");
+                    break;
+            }
+        } else {
+                // timeout: check if PING message needed
+                for (int i=0; i<keep_alive_storage->max_clients; ++i) {
+                    if (keep_alive_storage->clients[i].type == CLIENT_ACTIVE) {
+                        if (keep_alive_storage->clients[i].last_seen + keep_alive_storage->keep_alive_period_ms <= _tick_get_ms()) {
+                            ESP_LOGD(TAG, "Haven't seen the client (fd=%d) for a while", keep_alive_storage->clients[i].fd);
+                            if (keep_alive_storage->clients[i].last_seen + keep_alive_storage->not_alive_after_ms <= _tick_get_ms()) {
+                                ESP_LOGE(TAG, "Client (fd=%d) not alive!",  keep_alive_storage->clients[i].fd);
+                                keep_alive_storage->client_not_alive_cb(keep_alive_storage, keep_alive_storage->clients[i].fd);
+                            } else {
+                                keep_alive_storage->check_client_alive_cb(keep_alive_storage, keep_alive_storage->clients[i].fd);
+                            }
+                        }
+                    }
+                }
+            }
+    }
+    vQueueDelete(keep_alive_storage->q);
+    free(keep_alive_storage);
+
+    vTaskDelete(NULL);
+}
+
+wss_keep_alive_t wss_keep_alive_start(wss_keep_alive_config_t *config)
+{
+    size_t queue_size = config->max_clients/2;
+    size_t client_list_size = config->max_clients + queue_size;
+    wss_keep_alive_t keep_alive_storage = calloc(1,
+            sizeof(wss_keep_alive_storage_t) + client_list_size* sizeof(client_fd_action_t));
+    if (keep_alive_storage == NULL) {
+        return false;
+    }
+    keep_alive_storage->check_client_alive_cb = config->check_client_alive_cb;
+    keep_alive_storage->client_not_alive_cb = config->client_not_alive_cb;
+    keep_alive_storage->max_clients = config->max_clients;
+    keep_alive_storage->not_alive_after_ms = config->not_alive_after_ms;
+    keep_alive_storage->keep_alive_period_ms = config->keep_alive_period_ms;
+    keep_alive_storage->user_ctx = config->user_ctx;
+    keep_alive_storage->q =  xQueueCreate(queue_size, sizeof(client_fd_action_t));
+    if (xTaskCreate(keep_alive_task, "keep_alive_task", config->task_stack_size,
+                    keep_alive_storage, config->task_prio, NULL) != pdTRUE) {
+        wss_keep_alive_stop(keep_alive_storage);
+        return false;
+    }
+    return keep_alive_storage;
+}
+
+void wss_keep_alive_stop(wss_keep_alive_t h)
+{
+    client_fd_action_t stop = { .type = STOP_TASK };
+    xQueueSendToBack(h->q, &stop, 0);
+    // internal structs will be de-allocated in the task
+}
+
+esp_err_t wss_keep_alive_add_client(wss_keep_alive_t h, int fd)
+{
+    client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_FD_ADD};
+    if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+
+esp_err_t wss_keep_alive_remove_client(wss_keep_alive_t h, int fd)
+{
+    client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_FD_REMOVE};
+    if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+
+esp_err_t wss_keep_alive_client_is_active(wss_keep_alive_t h, int fd)
+{
+    client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_UPDATE,
+                                            .last_seen = _tick_get_ms()};
+    if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+
+}
+
+void wss_keep_alive_set_user_ctx(wss_keep_alive_t h, void *ctx)
+{
+    h->user_ctx = ctx;
+}
+
+void* wss_keep_alive_get_user_ctx(wss_keep_alive_t h)
+{
+    return h->user_ctx;
+}

+ 96 - 0
examples/protocols/https_server/wss_server/main/keep_alive.h

@@ -0,0 +1,96 @@
+/* Keep Alive engine for wss server example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#pragma once
+
+#define KEEP_ALIVE_CONFIG_DEFAULT() \
+    { \
+    .max_clients = 10,                      \
+    .task_stack_size = 2048,                \
+    .task_prio = tskIDLE_PRIORITY+1,        \
+    .keep_alive_period_ms = 5000,           \
+    .not_alive_after_ms = 10000,            \
+}
+
+struct wss_keep_alive_storage;
+typedef struct wss_keep_alive_storage* wss_keep_alive_t;
+typedef bool (*wss_check_client_alive_cb_t)(wss_keep_alive_t h, int fd);
+typedef bool (*wss_client_not_alive_cb_t)(wss_keep_alive_t h, int fd);
+
+/**
+ * @brief Confiuration struct
+ */
+typedef struct {
+    size_t max_clients;                                      /*!< max number of clients */
+    size_t task_stack_size;                                  /*!< stack size of the created task */
+    size_t task_prio;                                        /*!< priority of the created task */
+    size_t keep_alive_period_ms;                             /*!< check every client after this time */
+    size_t not_alive_after_ms;                               /*!< consider client not alive after this time */
+    wss_check_client_alive_cb_t check_client_alive_cb;       /*!< callback function to check if client is alive */
+    wss_client_not_alive_cb_t client_not_alive_cb;           /*!< callback function to notify that the client is not alive */
+    void *user_ctx;                                          /*!< user context available in the keep-alive handle */
+} wss_keep_alive_config_t;
+
+/**
+ * @brief Adds a new client to internal set of clients to keep an eye on
+ *
+ * @param h keep-alive handle
+ * @param fd socket file descriptor for this client
+ * @return ESP_OK on success
+ */
+esp_err_t wss_keep_alive_add_client(wss_keep_alive_t h, int fd);
+
+/**
+ * @brief Removes this client from the set
+ *
+ * @param h keep-alive handle
+ * @param fd socket file descriptor for this client
+ * @return ESP_OK on success
+ */
+esp_err_t wss_keep_alive_remove_client(wss_keep_alive_t h, int fd);
+
+/**
+ * @brief Notify that this client is alive
+ *
+ * @param h keep-alive handle
+ * @param fd socket file descriptor for this client
+ * @return ESP_OK on success
+ */
+
+esp_err_t wss_keep_alive_client_is_active(wss_keep_alive_t h, int fd);
+
+/**
+ * @brief Starts keep-alive engine
+ *
+ * @param config keep-alive configuration
+ * @return keep alive handle
+ */
+wss_keep_alive_t wss_keep_alive_start(wss_keep_alive_config_t *config);
+
+/**
+ * @brief Stops keep-alive engine
+ *
+ * @param h keep-alive handle
+ */
+void wss_keep_alive_stop(wss_keep_alive_t h);
+
+/**
+ * @brief Sets user defined context
+ *
+ * @param h keep-alive handle
+ * @param ctx user context
+ */
+void wss_keep_alive_set_user_ctx(wss_keep_alive_t h, void *ctx);
+
+/**
+ * @brief Gets user defined context
+ *
+ * @param h keep-alive handle
+ * @return ctx user context
+ */
+void* wss_keep_alive_get_user_ctx(wss_keep_alive_t h);

+ 285 - 0
examples/protocols/https_server/wss_server/main/wss_server_example.c

@@ -0,0 +1,285 @@
+/* Simple HTTP + SSL + WS Server Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <esp_event.h>
+#include <esp_log.h>
+#include <esp_system.h>
+#include <nvs_flash.h>
+#include <sys/param.h>
+#include "esp_netif.h"
+#include "esp_eth.h"
+#include "protocol_examples_common.h"
+
+#include <esp_https_server.h>
+#include "keep_alive.h"
+
+#if !CONFIG_HTTPD_WS_SUPPORT
+#error This example cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
+#endif
+
+struct async_resp_arg {
+    httpd_handle_t hd;
+    int fd;
+};
+
+static const char *TAG = "wss_echo_server";
+static const size_t max_clients = 4;
+
+static esp_err_t ws_handler(httpd_req_t *req)
+{
+    uint8_t buf[128] = { 0 };
+    httpd_ws_frame_t ws_pkt;
+    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+    ws_pkt.payload = buf;
+
+    // First receive the full ws message
+    esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 128);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
+        return ret;
+    }
+
+    // If it was a PONG, update the keep-alive
+    if (ws_pkt.type == HTTPD_WS_TYPE_PONG) {
+        ESP_LOGD(TAG, "Received PONG message");
+        return wss_keep_alive_client_is_active(httpd_get_global_user_ctx(req->handle),
+                httpd_req_to_sockfd(req));
+
+    // If it was a TEXT message, just echo it back
+    } else if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
+        ESP_LOGI(TAG, "Received packet with message: %s", ws_pkt.payload);
+        ret = httpd_ws_send_frame(req, &ws_pkt);
+        if (ret != ESP_OK) {
+            ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
+        }
+        ESP_LOGI(TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", req->handle,
+                 httpd_req_to_sockfd(req), httpd_ws_get_fd_info(req->handle, httpd_req_to_sockfd(req)));
+        return ret;
+    }
+    return ESP_OK;
+}
+
+esp_err_t wss_open_fd(httpd_handle_t hd, int sockfd)
+{
+    ESP_LOGI(TAG, "New client connected %d", sockfd);
+    wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
+    return wss_keep_alive_add_client(h, sockfd);
+}
+
+void wss_close_fd(httpd_handle_t hd, int sockfd)
+{
+    ESP_LOGI(TAG, "Client disconnected %d", sockfd);
+    wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
+    wss_keep_alive_remove_client(h, sockfd);
+}
+
+static const httpd_uri_t ws = {
+        .uri        = "/ws",
+        .method     = HTTP_GET,
+        .handler    = ws_handler,
+        .user_ctx   = NULL,
+        .is_websocket = true
+};
+
+
+static void send_hello(void *arg)
+{
+    static const char * data = "Hello client";
+    struct async_resp_arg *resp_arg = arg;
+    httpd_handle_t hd = resp_arg->hd;
+    int fd = resp_arg->fd;
+    httpd_ws_frame_t ws_pkt;
+    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+    ws_pkt.payload = (uint8_t*)data;
+    ws_pkt.len = strlen(data);
+    ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+
+    httpd_ws_send_frame_async(hd, fd, &ws_pkt);
+    free(resp_arg);
+}
+
+static void send_ping(void *arg)
+{
+    struct async_resp_arg *resp_arg = arg;
+    httpd_handle_t hd = resp_arg->hd;
+    int fd = resp_arg->fd;
+    httpd_ws_frame_t ws_pkt;
+    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+    ws_pkt.payload = NULL;
+    ws_pkt.len = 0;
+    ws_pkt.type = HTTPD_WS_TYPE_PING;
+
+    httpd_ws_send_frame_async(hd, fd, &ws_pkt);
+    free(resp_arg);
+}
+
+bool client_not_alive_cb(wss_keep_alive_t h, int fd)
+{
+    ESP_LOGE(TAG, "Client not alive, closing fd %d", fd);
+    httpd_sess_trigger_close(wss_keep_alive_get_user_ctx(h), fd);
+    return true;
+}
+
+bool check_client_alive_cb(wss_keep_alive_t h, int fd)
+{
+    ESP_LOGD(TAG, "Checking if client (fd=%d) is alive", fd);
+    struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
+    resp_arg->hd = wss_keep_alive_get_user_ctx(h);
+    resp_arg->fd = fd;
+
+    if (httpd_queue_work(resp_arg->hd, send_ping, resp_arg) == ESP_OK) {
+        return true;
+    }
+    return false;
+}
+
+static httpd_handle_t start_wss_echo_server(void)
+{
+    // Prepare keep-alive engine
+    wss_keep_alive_config_t keep_alive_config = KEEP_ALIVE_CONFIG_DEFAULT();
+    keep_alive_config.max_clients = max_clients;
+    keep_alive_config.client_not_alive_cb = client_not_alive_cb;
+    keep_alive_config.check_client_alive_cb = check_client_alive_cb;
+    wss_keep_alive_t keep_alive = wss_keep_alive_start(&keep_alive_config);
+
+    // Start the httpd server
+    httpd_handle_t server = NULL;
+    ESP_LOGI(TAG, "Starting server");
+
+    httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT();
+    conf.httpd.max_open_sockets = max_clients;
+    conf.httpd.global_user_ctx = keep_alive;
+    conf.httpd.open_fn = wss_open_fd;
+    conf.httpd.close_fn = wss_close_fd;
+
+    extern const unsigned char cacert_pem_start[] asm("_binary_cacert_pem_start");
+    extern const unsigned char cacert_pem_end[]   asm("_binary_cacert_pem_end");
+    conf.cacert_pem = cacert_pem_start;
+    conf.cacert_len = cacert_pem_end - cacert_pem_start;
+
+    extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
+    extern const unsigned char prvtkey_pem_end[]   asm("_binary_prvtkey_pem_end");
+    conf.prvtkey_pem = prvtkey_pem_start;
+    conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;
+
+    esp_err_t ret = httpd_ssl_start(&server, &conf);
+    if (ESP_OK != ret) {
+        ESP_LOGI(TAG, "Error starting server!");
+        return NULL;
+    }
+
+    // Set URI handlers
+    ESP_LOGI(TAG, "Registering URI handlers");
+    httpd_register_uri_handler(server, &ws);
+    wss_keep_alive_set_user_ctx(keep_alive, server);
+
+    return server;
+}
+
+static void stop_wss_echo_server(httpd_handle_t server)
+{
+    // Stop keep alive thread
+    wss_keep_alive_stop(httpd_get_global_user_ctx(server));
+    // Stop the httpd server
+    httpd_ssl_stop(server);
+}
+
+static void disconnect_handler(void* arg, esp_event_base_t event_base,
+                               int32_t event_id, void* event_data)
+{
+    httpd_handle_t* server = (httpd_handle_t*) arg;
+    if (*server) {
+        stop_wss_echo_server(*server);
+        *server = NULL;
+    }
+}
+
+static void connect_handler(void* arg, esp_event_base_t event_base,
+                            int32_t event_id, void* event_data)
+{
+    httpd_handle_t* server = (httpd_handle_t*) arg;
+    if (*server == NULL) {
+        *server = start_wss_echo_server();
+    }
+}
+
+static void wss_server_send_messages(httpd_handle_t* server)
+{
+    // Get all clients and send async message
+    struct {
+        size_t active_clients;
+        int    client_fds[max_clients];
+    } client_list;
+
+    bool send_messages = true;
+
+    // Send async message to all connected clients that use websocket protocol every 10 seconds
+    while (send_messages) {
+        vTaskDelay(10000 / portTICK_PERIOD_MS);
+
+        if (!*server) { // httpd might not have been created by now
+            continue;
+        }
+
+        if (httpd_get_client_list(*server, max_clients, (httpd_client_list_t*)&client_list) == ESP_OK) {
+            for (size_t i=0; i < client_list.active_clients; ++i) {
+                int sock = client_list.client_fds[i];
+                if (httpd_ws_get_fd_info(*server, sock) == HTTPD_WS_CLIENT_WEBSOCKET) {
+                    ESP_LOGI(TAG, "Active client (fd=%d) -> sending async message", sock);
+                    struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
+                    resp_arg->hd = *server;
+                    resp_arg->fd = sock;
+                    if (httpd_queue_work(resp_arg->hd, send_hello, resp_arg) != ESP_OK) {
+                        ESP_LOGE(TAG, "httpd_queue_work failed!");
+                        send_messages = false;
+                        break;
+                    }
+                }
+            }
+        } else {
+            ESP_LOGE(TAG, "httpd_get_client_list failed!");
+            return;
+        }
+    }
+}
+
+void app_main(void)
+{
+    static httpd_handle_t server = NULL;
+
+    ESP_ERROR_CHECK(nvs_flash_init());
+    ESP_ERROR_CHECK(esp_netif_init());
+    ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+    /* Register event handlers to start server when Wi-Fi or Ethernet is connected,
+     * and stop server when disconnection happens.
+     */
+
+#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
+    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
+    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
+#endif // CONFIG_EXAMPLE_CONNECT_WIFI
+#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
+    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
+    ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
+#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
+
+    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+     * Read "Establishing Wi-Fi or Ethernet Connection" section in
+     * examples/protocols/README.md for more information about this function.
+     */
+    ESP_ERROR_CHECK(example_connect());
+
+    /* This function demonstrates periodic sending Websocket messages
+     * to all connected clients to this server
+     */
+    wss_server_send_messages(&server);
+}
+
+

+ 3 - 0
examples/protocols/https_server/wss_server/sdkconfig.defaults

@@ -0,0 +1,3 @@
+CONFIG_ESP_HTTPS_SERVER_ENABLE=y
+CONFIG_ESP_NETIF_TCPIP_ADAPTER_COMPATIBLE_LAYER=n
+CONFIG_HTTPD_WS_SUPPORT=y