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