mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
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
This commit is contained in:
commit
e6f0087448
@ -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
|
||||
* @}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
{
|
||||
esp_tls_cfg_server_t *cfg = (esp_tls_cfg_server_t *)calloc(1, sizeof(esp_tls_cfg_server_t));
|
||||
if (!cfg) {
|
||||
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;
|
||||
|
@ -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')
|
||||
return self.ws.recv_data(control_frame=True)
|
||||
|
||||
def write(self, data="", opcode=OPCODE_TEXT, mask=1):
|
||||
return self.socket.sendall(self._ws_encode(data=data, opcode=opcode, mask=mask))
|
||||
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")
|
||||
|
@ -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
|
||||
|
10
examples/protocols/https_server/wss_server/CMakeLists.txt
Normal file
10
examples/protocols/https_server/wss_server/CMakeLists.txt
Normal file
@ -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
examples/protocols/https_server/wss_server/Makefile
Normal file
11
examples/protocols/https_server/wss_server/Makefile
Normal file
@ -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
examples/protocols/https_server/wss_server/README.md
Normal file
28
examples/protocols/https_server/wss_server/README.md
Normal file
@ -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.
|
@ -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")
|
@ -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-----
|
@ -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-----
|
@ -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
examples/protocols/https_server/wss_server/main/keep_alive.c
Normal file
228
examples/protocols/https_server/wss_server/main/keep_alive.c
Normal file
@ -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
examples/protocols/https_server/wss_server/main/keep_alive.h
Normal file
96
examples/protocols/https_server/wss_server/main/keep_alive.h
Normal file
@ -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);
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_ESP_NETIF_TCPIP_ADAPTER_COMPATIBLE_LAYER=n
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
Loading…
Reference in New Issue
Block a user