wss_server_example.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /* Simple HTTP + SSL + WS Server Example
  2. This example code is in the Public Domain (or CC0 licensed, at your option.)
  3. Unless required by applicable law or agreed to in writing, this
  4. software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  5. CONDITIONS OF ANY KIND, either express or implied.
  6. */
  7. #include <esp_event.h>
  8. #include <esp_log.h>
  9. #include <esp_system.h>
  10. #include <nvs_flash.h>
  11. #include <sys/param.h>
  12. #include "esp_netif.h"
  13. #include "esp_eth.h"
  14. #include "protocol_examples_common.h"
  15. #include <esp_https_server.h>
  16. #include "keep_alive.h"
  17. #include "sdkconfig.h"
  18. #if !CONFIG_HTTPD_WS_SUPPORT
  19. #error This example cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
  20. #endif
  21. struct async_resp_arg {
  22. httpd_handle_t hd;
  23. int fd;
  24. };
  25. static const char *TAG = "wss_echo_server";
  26. static const size_t max_clients = 4;
  27. static esp_err_t ws_handler(httpd_req_t *req)
  28. {
  29. if (req->method == HTTP_GET) {
  30. ESP_LOGI(TAG, "Handshake done, the new connection was opened");
  31. return ESP_OK;
  32. }
  33. httpd_ws_frame_t ws_pkt;
  34. uint8_t *buf = NULL;
  35. memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
  36. // First receive the full ws message
  37. /* Set max_len = 0 to get the frame len */
  38. esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
  39. if (ret != ESP_OK) {
  40. ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
  41. return ret;
  42. }
  43. ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
  44. if (ws_pkt.len) {
  45. /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
  46. buf = calloc(1, ws_pkt.len + 1);
  47. if (buf == NULL) {
  48. ESP_LOGE(TAG, "Failed to calloc memory for buf");
  49. return ESP_ERR_NO_MEM;
  50. }
  51. ws_pkt.payload = buf;
  52. /* Set max_len = ws_pkt.len to get the frame payload */
  53. ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
  54. if (ret != ESP_OK) {
  55. ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
  56. free(buf);
  57. return ret;
  58. }
  59. }
  60. // If it was a PONG, update the keep-alive
  61. if (ws_pkt.type == HTTPD_WS_TYPE_PONG) {
  62. ESP_LOGD(TAG, "Received PONG message");
  63. free(buf);
  64. return wss_keep_alive_client_is_active(httpd_get_global_user_ctx(req->handle),
  65. httpd_req_to_sockfd(req));
  66. // If it was a TEXT message, just echo it back
  67. } else if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
  68. ESP_LOGI(TAG, "Received packet with message: %s", ws_pkt.payload);
  69. ret = httpd_ws_send_frame(req, &ws_pkt);
  70. if (ret != ESP_OK) {
  71. ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
  72. }
  73. ESP_LOGI(TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", req->handle,
  74. httpd_req_to_sockfd(req), httpd_ws_get_fd_info(req->handle, httpd_req_to_sockfd(req)));
  75. free(buf);
  76. return ret;
  77. }
  78. free(buf);
  79. return ESP_OK;
  80. }
  81. esp_err_t wss_open_fd(httpd_handle_t hd, int sockfd)
  82. {
  83. ESP_LOGI(TAG, "New client connected %d", sockfd);
  84. wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
  85. return wss_keep_alive_add_client(h, sockfd);
  86. }
  87. void wss_close_fd(httpd_handle_t hd, int sockfd)
  88. {
  89. ESP_LOGI(TAG, "Client disconnected %d", sockfd);
  90. wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
  91. wss_keep_alive_remove_client(h, sockfd);
  92. }
  93. static const httpd_uri_t ws = {
  94. .uri = "/ws",
  95. .method = HTTP_GET,
  96. .handler = ws_handler,
  97. .user_ctx = NULL,
  98. .is_websocket = true,
  99. .handle_ws_control_frames = true
  100. };
  101. static void send_hello(void *arg)
  102. {
  103. static const char * data = "Hello client";
  104. struct async_resp_arg *resp_arg = arg;
  105. httpd_handle_t hd = resp_arg->hd;
  106. int fd = resp_arg->fd;
  107. httpd_ws_frame_t ws_pkt;
  108. memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
  109. ws_pkt.payload = (uint8_t*)data;
  110. ws_pkt.len = strlen(data);
  111. ws_pkt.type = HTTPD_WS_TYPE_TEXT;
  112. httpd_ws_send_frame_async(hd, fd, &ws_pkt);
  113. free(resp_arg);
  114. }
  115. static void send_ping(void *arg)
  116. {
  117. struct async_resp_arg *resp_arg = arg;
  118. httpd_handle_t hd = resp_arg->hd;
  119. int fd = resp_arg->fd;
  120. httpd_ws_frame_t ws_pkt;
  121. memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
  122. ws_pkt.payload = NULL;
  123. ws_pkt.len = 0;
  124. ws_pkt.type = HTTPD_WS_TYPE_PING;
  125. httpd_ws_send_frame_async(hd, fd, &ws_pkt);
  126. free(resp_arg);
  127. }
  128. bool client_not_alive_cb(wss_keep_alive_t h, int fd)
  129. {
  130. ESP_LOGE(TAG, "Client not alive, closing fd %d", fd);
  131. httpd_sess_trigger_close(wss_keep_alive_get_user_ctx(h), fd);
  132. return true;
  133. }
  134. bool check_client_alive_cb(wss_keep_alive_t h, int fd)
  135. {
  136. ESP_LOGD(TAG, "Checking if client (fd=%d) is alive", fd);
  137. struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
  138. resp_arg->hd = wss_keep_alive_get_user_ctx(h);
  139. resp_arg->fd = fd;
  140. if (httpd_queue_work(resp_arg->hd, send_ping, resp_arg) == ESP_OK) {
  141. return true;
  142. }
  143. return false;
  144. }
  145. static httpd_handle_t start_wss_echo_server(void)
  146. {
  147. // Prepare keep-alive engine
  148. wss_keep_alive_config_t keep_alive_config = KEEP_ALIVE_CONFIG_DEFAULT();
  149. keep_alive_config.max_clients = max_clients;
  150. keep_alive_config.client_not_alive_cb = client_not_alive_cb;
  151. keep_alive_config.check_client_alive_cb = check_client_alive_cb;
  152. wss_keep_alive_t keep_alive = wss_keep_alive_start(&keep_alive_config);
  153. // Start the httpd server
  154. httpd_handle_t server = NULL;
  155. ESP_LOGI(TAG, "Starting server");
  156. httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT();
  157. conf.httpd.max_open_sockets = max_clients;
  158. conf.httpd.global_user_ctx = keep_alive;
  159. conf.httpd.open_fn = wss_open_fd;
  160. conf.httpd.close_fn = wss_close_fd;
  161. extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
  162. extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
  163. conf.servercert = servercert_start;
  164. conf.servercert_len = servercert_end - servercert_start;
  165. extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
  166. extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end");
  167. conf.prvtkey_pem = prvtkey_pem_start;
  168. conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;
  169. esp_err_t ret = httpd_ssl_start(&server, &conf);
  170. if (ESP_OK != ret) {
  171. ESP_LOGI(TAG, "Error starting server!");
  172. return NULL;
  173. }
  174. // Set URI handlers
  175. ESP_LOGI(TAG, "Registering URI handlers");
  176. httpd_register_uri_handler(server, &ws);
  177. wss_keep_alive_set_user_ctx(keep_alive, server);
  178. return server;
  179. }
  180. static esp_err_t stop_wss_echo_server(httpd_handle_t server)
  181. {
  182. // Stop keep alive thread
  183. wss_keep_alive_stop(httpd_get_global_user_ctx(server));
  184. // Stop the httpd server
  185. return httpd_ssl_stop(server);
  186. }
  187. static void disconnect_handler(void* arg, esp_event_base_t event_base,
  188. int32_t event_id, void* event_data)
  189. {
  190. httpd_handle_t* server = (httpd_handle_t*) arg;
  191. if (*server) {
  192. if (stop_wss_echo_server(*server) == ESP_OK) {
  193. *server = NULL;
  194. } else {
  195. ESP_LOGE(TAG, "Failed to stop https server");
  196. }
  197. }
  198. }
  199. static void connect_handler(void* arg, esp_event_base_t event_base,
  200. int32_t event_id, void* event_data)
  201. {
  202. httpd_handle_t* server = (httpd_handle_t*) arg;
  203. if (*server == NULL) {
  204. *server = start_wss_echo_server();
  205. }
  206. }
  207. // Get all clients and send async message
  208. static void wss_server_send_messages(httpd_handle_t* server)
  209. {
  210. bool send_messages = true;
  211. // Send async message to all connected clients that use websocket protocol every 10 seconds
  212. while (send_messages) {
  213. vTaskDelay(10000 / portTICK_PERIOD_MS);
  214. if (!*server) { // httpd might not have been created by now
  215. continue;
  216. }
  217. size_t clients = max_clients;
  218. int client_fds[max_clients];
  219. if (httpd_get_client_list(*server, &clients, client_fds) == ESP_OK) {
  220. for (size_t i=0; i < clients; ++i) {
  221. int sock = client_fds[i];
  222. if (httpd_ws_get_fd_info(*server, sock) == HTTPD_WS_CLIENT_WEBSOCKET) {
  223. ESP_LOGI(TAG, "Active client (fd=%d) -> sending async message", sock);
  224. struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
  225. resp_arg->hd = *server;
  226. resp_arg->fd = sock;
  227. if (httpd_queue_work(resp_arg->hd, send_hello, resp_arg) != ESP_OK) {
  228. ESP_LOGE(TAG, "httpd_queue_work failed!");
  229. send_messages = false;
  230. break;
  231. }
  232. }
  233. }
  234. } else {
  235. ESP_LOGE(TAG, "httpd_get_client_list failed!");
  236. return;
  237. }
  238. }
  239. }
  240. void app_main(void)
  241. {
  242. static httpd_handle_t server = NULL;
  243. ESP_ERROR_CHECK(nvs_flash_init());
  244. ESP_ERROR_CHECK(esp_netif_init());
  245. ESP_ERROR_CHECK(esp_event_loop_create_default());
  246. /* Register event handlers to start server when Wi-Fi or Ethernet is connected,
  247. * and stop server when disconnection happens.
  248. */
  249. #ifdef CONFIG_EXAMPLE_CONNECT_WIFI
  250. ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
  251. ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
  252. #endif // CONFIG_EXAMPLE_CONNECT_WIFI
  253. #ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
  254. ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
  255. ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
  256. #endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
  257. /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
  258. * Read "Establishing Wi-Fi or Ethernet Connection" section in
  259. * examples/protocols/README.md for more information about this function.
  260. */
  261. ESP_ERROR_CHECK(example_connect());
  262. /* This function demonstrates periodic sending Websocket messages
  263. * to all connected clients to this server
  264. */
  265. wss_server_send_messages(&server);
  266. }