kopia lustrzana https://github.com/espressif/esp-idf
304 wiersze
10 KiB
C
304 wiersze
10 KiB
C
/* 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)
|
|
{
|
|
if (req->method == HTTP_GET) {
|
|
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
|
|
return ESP_OK;
|
|
}
|
|
httpd_ws_frame_t ws_pkt;
|
|
uint8_t *buf = NULL;
|
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
|
|
|
// First receive the full ws message
|
|
/* Set max_len = 0 to get the frame len */
|
|
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
|
|
return ret;
|
|
}
|
|
ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
|
|
if (ws_pkt.len) {
|
|
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
|
|
buf = calloc(1, ws_pkt.len + 1);
|
|
if (buf == NULL) {
|
|
ESP_LOGE(TAG, "Failed to calloc memory for buf");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
ws_pkt.payload = buf;
|
|
/* Set max_len = ws_pkt.len to get the frame payload */
|
|
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
|
|
free(buf);
|
|
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");
|
|
free(buf);
|
|
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)));
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
free(buf);
|
|
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);
|
|
}
|