Kaynağa Gözat

Merge branch 'bugfix/httpd_ws_sock_type' into 'master'

https_server: Added WSS server example and some http(s)+ws updates

Closes IDFGH-3822, IDFGH-3668, IDFGH-3766, and IDFGH-3444

See merge request espressif/esp-idf!10262
David Čermák 5 yıl önce
ebeveyn
işleme
e6f0087448
29 değiştirilmiş dosya ile 840 ekleme ve 69 silme
  1. 42 0
      components/esp_http_server/include/esp_http_server.h
  2. 1 0
      components/esp_http_server/src/esp_httpd_priv.h
  3. 20 0
      components/esp_http_server/src/httpd_main.c
  4. 6 5
      components/esp_http_server/src/httpd_parse.c
  5. 2 0
      components/esp_http_server/src/httpd_uri.c
  6. 11 0
      components/esp_http_server/src/httpd_ws.c
  7. 29 8
      components/esp_https_server/src/https_server.c
  8. 12 55
      examples/protocols/http_server/ws_echo_server/ws_server_example_test.py
  9. 0 0
      examples/protocols/https_server/simple/CMakeLists.txt
  10. 0 0
      examples/protocols/https_server/simple/Makefile
  11. 1 1
      examples/protocols/https_server/simple/README.md
  12. 0 0
      examples/protocols/https_server/simple/main/CMakeLists.txt
  13. 0 0
      examples/protocols/https_server/simple/main/certs/cacert.pem
  14. 0 0
      examples/protocols/https_server/simple/main/certs/prvtkey.pem
  15. 0 0
      examples/protocols/https_server/simple/main/component.mk
  16. 0 0
      examples/protocols/https_server/simple/main/main.c
  17. 0 0
      examples/protocols/https_server/simple/sdkconfig.ci
  18. 0 0
      examples/protocols/https_server/simple/sdkconfig.defaults
  19. 10 0
      examples/protocols/https_server/wss_server/CMakeLists.txt
  20. 11 0
      examples/protocols/https_server/wss_server/Makefile
  21. 28 0
      examples/protocols/https_server/wss_server/README.md
  22. 4 0
      examples/protocols/https_server/wss_server/main/CMakeLists.txt
  23. 19 0
      examples/protocols/https_server/wss_server/main/certs/cacert.pem
  24. 28 0
      examples/protocols/https_server/wss_server/main/certs/prvtkey.pem
  25. 7 0
      examples/protocols/https_server/wss_server/main/component.mk
  26. 228 0
      examples/protocols/https_server/wss_server/main/keep_alive.c
  27. 96 0
      examples/protocols/https_server/wss_server/main/keep_alive.h
  28. 282 0
      examples/protocols/https_server/wss_server/main/wss_server_example.c
  29. 3 0
      examples/protocols/https_server/wss_server/sdkconfig.defaults

+ 42 - 0
components/esp_http_server/include/esp_http_server.h

@@ -406,6 +406,12 @@ typedef struct httpd_uri {
      * If this flag is true, then method must be HTTP_GET. Otherwise the handshake will not be handled.
      */
     bool is_websocket;
+
+    /**
+     * Flag indicating that control frames (PING, PONG, CLOSE) are also passed to the handler
+     * This is used if a custom processing of the control frames is needed
+     */
+    bool handle_ws_control_frames;
 #endif
 } httpd_uri_t;
 
@@ -1466,6 +1472,20 @@ esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd);
  */
 esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd);
 
+/**
+ * @brief   Returns list of current socket descriptors of active sessions
+ *
+ * @param[in] handle    Handle to server returned by httpd_start
+ * @param[in,out] fds   In: Number of fds allocated in the supplied structure client_fds
+ *                      Out: Number of valid client fds returned in client_fds,
+ * @param[out] client_fds  Array of client fds
+ *
+ * @return
+ *  - ESP_OK              : Successfully retrieved session list
+ *  - ESP_ERR_INVALID_ARG : Wrong arguments or list is longer than allocated
+ */
+esp_err_t httpd_get_client_list(httpd_handle_t handle, size_t *fds, int *client_fds);
+
 /** End of Session
  * @}
  */
@@ -1526,6 +1546,15 @@ typedef enum {
     HTTPD_WS_TYPE_PONG       = 0xA
 } httpd_ws_type_t;
 
+/**
+ * @brief Enum for client info description
+ */
+typedef enum {
+    HTTPD_WS_CLIENT_INVALID        = 0x0,
+    HTTPD_WS_CLIENT_HTTP           = 0x1,
+    HTTPD_WS_CLIENT_WEBSOCKET      = 0x2,
+} httpd_ws_client_info_t;
+
 /**
  * @brief WebSocket frame format
  */
@@ -1586,6 +1615,19 @@ esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *pkt);
  */
 esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame);
 
+/**
+ * @brief Checks the supplied socket descriptor if it belongs to any active client
+ * of this server instance and if the websoket protocol is active
+ *
+ * @param[in] hd      Server instance data
+ * @param[in] fd      Socket descriptor
+ * @return
+ *  - HTTPD_WS_CLIENT_INVALID   : This fd is not a client of this httpd
+ *  - HTTPD_WS_CLIENT_HTTP      : This fd is an active client, protocol is not WS
+ *  - HTTPD_WS_CLIENT_WEBSOCKET : This fd is an active client, protocol is WS
+ */
+httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t hd, int fd);
+
 #endif /* CONFIG_HTTPD_WS_SUPPORT */
 /** End of WebSocket related stuff
  * @}

+ 1 - 0
components/esp_http_server/src/esp_httpd_priv.h

@@ -75,6 +75,7 @@ struct sock_db {
     bool ws_handshake_done;                 /*!< True if it has done WebSocket handshake (if this socket is a valid WS) */
     bool ws_close;                          /*!< Set to true to close the socket later (when WS Close frame received) */
     esp_err_t (*ws_handler)(httpd_req_t *r);   /*!< WebSocket handler, leave to null if it's not WebSocket */
+    bool ws_control_frames;                         /*!< WebSocket flag indicating that control frames should be passed to user handlers */
 #endif
 };
 

+ 20 - 0
components/esp_http_server/src/httpd_main.c

@@ -104,6 +104,26 @@ esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *ar
     return ESP_OK;
 }
 
+esp_err_t httpd_get_client_list(httpd_handle_t handle, size_t *fds, int *client_fds)
+{
+    struct httpd_data *hd = (struct httpd_data *) handle;
+    if (hd == NULL || fds == NULL || *fds == 0 || client_fds == NULL || *fds < hd->config.max_open_sockets) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    size_t max_fds = *fds;
+    *fds = 0;
+    for (int i = 0; i < hd->config.max_open_sockets; ++i) {
+        if (hd->hd_sd[i].fd != -1) {
+            if (*fds < max_fds) {
+                client_fds[(*fds)++] = hd->hd_sd[i].fd;
+            } else {
+                return ESP_ERR_INVALID_ARG;
+            }
+        }
+    }
+    return ESP_OK;
+}
+
 void *httpd_get_global_user_ctx(httpd_handle_t handle)
 {
     return ((struct httpd_data *)handle)->config.global_user_ctx;

+ 6 - 5
components/esp_http_server/src/httpd_parse.c

@@ -758,8 +758,8 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
              sd->ws_handler != NULL ? "Yes" : "No",
              sd->ws_close ? "Yes" : "No");
     if (sd->ws_handshake_done && sd->ws_handler != NULL) {
-        ESP_LOGD(TAG, LOG_FMT("New WS request from existing socket"));
         ret = httpd_ws_get_frame_type(r);
+        ESP_LOGD(TAG, LOG_FMT("New WS request from existing socket, ws_type=%d"), ra->ws_type);
 
         /*  Stop and return here immediately if it's a CLOSE frame */
         if (ra->ws_type == HTTPD_WS_TYPE_CLOSE) {
@@ -767,13 +767,14 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
             return ret;
         }
 
-        /* Ignore PONG frame, as this is a server */
         if (ra->ws_type == HTTPD_WS_TYPE_PONG) {
-            return ret;
+            /* Pass the PONG frames to the handler as well, as user app might send PINGs */
+            ESP_LOGD(TAG, LOG_FMT("Received PONG frame"));
         }
 
-        /* Call handler if it's a non-control frame */
-        if (ret == ESP_OK && ra->ws_type < HTTPD_WS_TYPE_CLOSE) {
+        /* Call handler if it's a non-control frame (or if handler requests control frames, as well) */
+        if (ret == ESP_OK &&
+            (ra->ws_type < HTTPD_WS_TYPE_CLOSE || sd->ws_control_frames)) {
             ret = sd->ws_handler(r);
         }
 

+ 2 - 0
components/esp_http_server/src/httpd_uri.c

@@ -174,6 +174,7 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle,
             hd->hd_calls[i]->user_ctx = uri_handler->user_ctx;
 #ifdef CONFIG_HTTPD_WS_SUPPORT
             hd->hd_calls[i]->is_websocket = uri_handler->is_websocket;
+            hd->hd_calls[i]->handle_ws_control_frames = uri_handler->handle_ws_control_frames;
 #endif
             ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri);
             return ESP_OK;
@@ -322,6 +323,7 @@ esp_err_t httpd_uri(struct httpd_data *hd)
 
         aux->sd->ws_handshake_done = true;
         aux->sd->ws_handler = uri->handler;
+        aux->sd->ws_control_frames = uri->handle_ws_control_frames;
 
         /* Return immediately after handshake, no need to call handler here */
         return ESP_OK;

+ 11 - 0
components/esp_http_server/src/httpd_ws.c

@@ -383,4 +383,15 @@ esp_err_t httpd_ws_get_frame_type(httpd_req_t *req)
     return ESP_OK;
 }
 
+httpd_ws_client_info_t httpd_ws_get_fd_info(httpd_handle_t hd, int fd)
+{
+    struct sock_db *sess = httpd_sess_get(hd, fd);
+
+    if (sess == NULL) {
+        return HTTPD_WS_CLIENT_INVALID;
+    }
+    bool is_active_ws = sess->ws_handshake_done && (!sess->ws_close);
+    return is_active_ws ? HTTPD_WS_CLIENT_WEBSOCKET : HTTPD_WS_CLIENT_HTTP;
+}
+
 #endif /* CONFIG_HTTPD_WS_SUPPORT */

+ 29 - 8
components/esp_https_server/src/https_server.c

@@ -20,6 +20,11 @@
 
 const static char *TAG = "esp_https_server";
 
+typedef struct httpd_ssl_ctx {
+    esp_tls_cfg_server_t *tls_cfg;
+    httpd_open_func_t open_fn;
+} httpd_ssl_ctx_t;
+
 /**
  * SSL socket close handler
  *
@@ -93,7 +98,7 @@ static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd)
     assert(server != NULL);
 
     // Retrieve the SSL context from the global context field (set in config)
-    esp_tls_cfg_server_t *global_ctx = httpd_get_global_transport_ctx(server);
+    httpd_ssl_ctx_t *global_ctx = httpd_get_global_transport_ctx(server);
     assert(global_ctx != NULL);
 
     esp_tls_t *tls = (esp_tls_t *)calloc(1, sizeof(esp_tls_t));
@@ -101,7 +106,7 @@ static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd)
         return ESP_ERR_NO_MEM;
     }
     ESP_LOGI(TAG, "performing session handshake");
-    int ret = esp_tls_server_session_create(global_ctx, sockfd, tls);
+    int ret = esp_tls_server_session_create(global_ctx->tls_cfg, sockfd, tls);
     if (ret != 0) {
         ESP_LOGE(TAG, "esp_tls_create_server_session failed");
         goto fail;
@@ -119,6 +124,9 @@ static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd)
 
     ESP_LOGD(TAG, "Secure socket open");
 
+    if (global_ctx->open_fn) {
+        (global_ctx->open_fn)(server, sockfd);
+    }
     return ESP_OK;
 fail:
     esp_tls_server_session_delete(tls);
@@ -133,7 +141,8 @@ fail:
 static void free_secure_context(void *ctx)
 {
     assert(ctx != NULL);
-    esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)ctx;
+    httpd_ssl_ctx_t *ssl_ctx = ctx;
+    esp_tls_cfg_server_t *cfg = ssl_ctx->tls_cfg;
     ESP_LOGI(TAG, "Server shuts down, releasing SSL context");
     if (cfg->cacert_buf) {
         free((void *)cfg->cacert_buf);
@@ -145,14 +154,21 @@ static void free_secure_context(void *ctx)
         free((void *)cfg->serverkey_buf);
     }
     free(cfg);
+    free(ssl_ctx);
 }
 
-static esp_tls_cfg_server_t *create_secure_context(const struct httpd_ssl_config *config)
+static httpd_ssl_ctx_t *create_secure_context(const struct httpd_ssl_config *config)
 {
+    httpd_ssl_ctx_t *ssl_ctx = calloc(1, sizeof(httpd_ssl_ctx_t));
+    if (!ssl_ctx) {
+        return NULL;
+    }
     esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)calloc(1, sizeof(esp_tls_cfg_server_t));
     if (!cfg) {
+        free(ssl_ctx);
         return NULL;
     }
+    ssl_ctx->tls_cfg = cfg;
 /* cacert = CA which signs client cert, or client cert itself , which is mapped to client_verify_cert_pem */
     if(config->client_verify_cert_pem != NULL) {
         cfg->cacert_buf = (unsigned char *)malloc(config->client_verify_cert_len);
@@ -186,7 +202,7 @@ static esp_tls_cfg_server_t *create_secure_context(const struct httpd_ssl_config
     memcpy((char *)cfg->serverkey_buf, config->prvtkey_pem, config->prvtkey_len);
     cfg->serverkey_bytes = config->prvtkey_len;
 
-    return cfg;
+    return ssl_ctx;
 }
 
 /** Start the server */
@@ -199,16 +215,21 @@ esp_err_t httpd_ssl_start(httpd_handle_t *pHandle, struct httpd_ssl_config *conf
 
     if (HTTPD_SSL_TRANSPORT_SECURE == config->transport_mode) {
 
-        esp_tls_cfg_server_t *esp_tls_cfg = create_secure_context(config);
-        if (!esp_tls_cfg) {
+        httpd_ssl_ctx_t *ssl_ctx = create_secure_context(config);
+        if (!ssl_ctx) {
             return -1;
         }
 
         ESP_LOGD(TAG, "SSL context ready");
 
         // set SSL specific config
-        config->httpd.global_transport_ctx = esp_tls_cfg;
+        config->httpd.global_transport_ctx = ssl_ctx;
         config->httpd.global_transport_ctx_free_fn = free_secure_context;
+        if (config->httpd.open_fn) {
+            // since the httpd's open_fn is used for opening the SSL session, we save the configured
+            // user pointer and call it upon opening the ssl socket
+            ssl_ctx->open_fn = config->httpd.open_fn;
+        }
         config->httpd.open_fn = httpd_ssl_open; // the open function configures the created SSL sessions
 
         config->httpd.server_port = config->port_secure;

+ 12 - 55
examples/protocols/http_server/ws_echo_server/ws_server_example_test.py

@@ -21,11 +21,7 @@ import re
 from tiny_test_fw import Utility
 import ttfw_idf
 import os
-import six
-import socket
-import hashlib
-import base64
-import struct
+import websocket
 
 
 OPCODE_TEXT = 0x1
@@ -38,63 +34,24 @@ class WsClient:
     def __init__(self, ip, port):
         self.port = port
         self.ip = ip
-        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.client_key = "abcdefghjk"
-        self.socket.settimeout(10.0)
+        self.ws = websocket.WebSocket()
 
     def __enter__(self):
-        self.socket.connect((self.ip, self.port))
-        self._handshake()
+        self.ws.connect("ws://{}:{}/ws".format(self.ip, self.port))
         return self
 
     def __exit__(self, exc_type, exc_value, traceback):
-        self.socket.close()
-
-    def _handshake(self):
-        MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-        client_key = self.client_key + MAGIC_STRING
-        expected_accept = base64.standard_b64encode(hashlib.sha1(client_key.encode()).digest())
-        request = ('GET /ws HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: '
-                   'Upgrade\r\nSec-WebSocket-Key: {}\r\n'
-                   'Sec-WebSocket-Version: 13\r\n\r\n'.format(self.client_key))
-        self.socket.send(request.encode('utf-8'))
-        response = self.socket.recv(1024)
-        ws_accept = re.search(b'Sec-WebSocket-Accept: (.*)\r\n', response, re.IGNORECASE)
-        if ws_accept and ws_accept.group(1) is not None and ws_accept.group(1) == expected_accept:
-            pass
-        else:
-            raise("Unexpected Sec-WebSocket-Accept, handshake response: {}".format(response))
-
-    def _masked(self, data):
-        mask = struct.unpack('B' * 4, os.urandom(4))
-        out = list(mask)
-        for i, d in enumerate(struct.unpack('B' * len(data), data)):
-            out.append(d ^ mask[i % 4])
-        return struct.pack('B' * len(out), *out)
-
-    def _ws_encode(self, data="", opcode=OPCODE_TEXT, mask=1):
-        data = data.encode('utf-8')
-        length = len(data)
-        if length >= 126:
-            raise("Packet length of {} not supported!".format(length))
-        frame_header = chr(1 << 7 | opcode)
-        frame_header += chr(mask << 7 | length)
-        frame_header = six.b(frame_header)
-        if not mask:
-            return frame_header + data
-        return frame_header + self._masked(data)
+        self.ws.close()
 
     def read(self):
-        header = self.socket.recv(2)
-        if not six.PY3:
-            header = [ord(character) for character in header]
-        opcode = header[0] & 15
-        length = header[1] & 127
-        payload = self.socket.recv(length)
-        return opcode, payload.decode('utf-8')
-
-    def write(self, data="", opcode=OPCODE_TEXT, mask=1):
-        return self.socket.sendall(self._ws_encode(data=data, opcode=opcode, mask=mask))
+        return self.ws.recv_data(control_frame=True)
+
+    def write(self, data="", opcode=OPCODE_TEXT):
+        if opcode == OPCODE_BIN:
+            return self.ws.send_binary(data.encode())
+        if opcode == OPCODE_PING:
+            return self.ws.ping(data)
+        return self.ws.send(data)
 
 
 @ttfw_idf.idf_example_test(env_tag="Example_WIFI")

+ 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);

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

@@ -0,0 +1,282 @@
+/* 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,
+        .handle_ws_control_frames = 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();
+    }
+}
+
+// Get all clients and send async message
+static void wss_server_send_messages(httpd_handle_t* server)
+{
+    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;
+        }
+        size_t clients = max_clients;
+        int    client_fds[max_clients];
+        if (httpd_get_client_list(*server, &clients, client_fds) == ESP_OK) {
+            for (size_t i=0; i < clients; ++i) {
+                int sock = 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