feat(https_server): Added Server Sent Events demo

This commit adds the demo of Server Sent Events
functionality in the https_server/simple example
Closes https://github.com/espressif/esp-idf/issues/13603

Co-authored-by: default avatarAditya Patwardhan <aditya.patwardhan@espressif.com>
This commit is contained in:
hrushikesh.bhosale
2024-12-04 14:17:31 +05:30
parent 84631fe972
commit 696b32c942
5 changed files with 115 additions and 4 deletions

View File

@@ -6,6 +6,15 @@
The Example consists of HTTPD server demo with demonstration of URI handling : The Example consists of HTTPD server demo with demonstration of URI handling :
1. URI \hello for GET command returns "Hello World!" message 1. URI \hello for GET command returns "Hello World!" message
2. URI \echo for POST command echoes back the POSTed message 2. URI \echo for POST command echoes back the POSTed message
3. URI \sse for GET command sends a message to client every second
## User Callback
The example includes a simple user callback that can be used to get the SSL context (connection information) when the server is being initialized. To enable the user callback, set `CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK` to `y` in the project configuration menu.
## Server-Sent Events (SSE)
The example also includes a simple SSE handler (having endpoint \sse), which sends a message to the client every second. To enable SSE, set `CONFIG_EXAMPLE_ENABLE_SSE_HANDLER` to `y` in the project configuration menu.
## How to use example ## How to use example

View File

@@ -26,4 +26,11 @@ menu "Example Configuration"
help help
The client's password which used for basic authenticate. The client's password which used for basic authenticate.
config EXAMPLE_ENABLE_SSE_HANDLER
bool "Enable Server-Sent Events (SSE) handler"
default n
help
Enable this option to use Server-Sent Events (SSE) functionality.
This will allow the server to push real-time updates to the client over an HTTP connection.
endmenu endmenu

View File

@@ -22,7 +22,8 @@
#include "esp_netif.h" #include "esp_netif.h"
#include "esp_tls.h" #include "esp_tls.h"
#include "esp_check.h" #include "esp_check.h"
#include <time.h>
#include <sys/time.h>
#if !CONFIG_IDF_TARGET_LINUX #if !CONFIG_IDF_TARGET_LINUX
#include <esp_wifi.h> #include <esp_wifi.h>
#include <esp_system.h> #include <esp_system.h>
@@ -392,6 +393,40 @@ static const httpd_uri_t ctrl = {
.user_ctx = NULL .user_ctx = NULL
}; };
#if CONFIG_EXAMPLE_ENABLE_SSE_HANDLER
/* An HTTP GET handler for SSE */
static esp_err_t sse_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "text/event-stream");
httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
httpd_resp_set_hdr(req, "Connection", "keep-alive");
char sse_data[64];
while (1) {
struct timeval tv;
gettimeofday(&tv, NULL); // Get the current time
int64_t time_since_boot = tv.tv_sec; // Time since boot in seconds
esp_err_t err;
int len = snprintf(sse_data, sizeof(sse_data), "data: Time since boot: %lld seconds\n\n", time_since_boot);
if ((err = httpd_resp_send_chunk(req, sse_data, len)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to send sse data (returned %02X)", err);
break;
}
vTaskDelay(pdMS_TO_TICKS(1000)); // Send data every second
}
httpd_resp_send_chunk(req, NULL, 0); // End response
return ESP_OK;
}
static const httpd_uri_t sse = {
.uri = "/sse",
.method = HTTP_GET,
.handler = sse_handler,
.user_ctx = NULL
};
#endif // CONFIG_EXAMPLE_ENABLE_SSE_HANDLER
static httpd_handle_t start_webserver(void) static httpd_handle_t start_webserver(void)
{ {
httpd_handle_t server = NULL; httpd_handle_t server = NULL;
@@ -414,9 +449,12 @@ static httpd_handle_t start_webserver(void)
httpd_register_uri_handler(server, &echo); httpd_register_uri_handler(server, &echo);
httpd_register_uri_handler(server, &ctrl); httpd_register_uri_handler(server, &ctrl);
httpd_register_uri_handler(server, &any); httpd_register_uri_handler(server, &any);
#if CONFIG_EXAMPLE_BASIC_AUTH #if CONFIG_EXAMPLE_ENABLE_SSE_HANDLER
httpd_register_uri_handler(server, &sse); // Register SSE handler
#endif
#if CONFIG_EXAMPLE_BASIC_AUTH
httpd_register_basic_auth(server); httpd_register_basic_auth(server);
#endif #endif
return server; return server;
} }

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import logging import logging
import os import os
@@ -14,6 +14,7 @@ import time
import pytest import pytest
try: try:
import http.client
from idf_http_server_test import client from idf_http_server_test import client
except ModuleNotFoundError: except ModuleNotFoundError:
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', 'tools', 'ci', 'python_packages')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', 'tools', 'ci', 'python_packages'))
@@ -171,3 +172,58 @@ def test_examples_protocol_http_server_lru_purge_enable(dut: Dut) -> None:
for t in threads: for t in threads:
t.join() t.join()
@pytest.mark.esp32
@pytest.mark.wifi_router
@pytest.mark.parametrize('config', ['sse',], indirect=True)
def test_examples_protocol_http_server_sse(dut: Dut) -> None:
# Get binary file
binary_file = os.path.join(dut.app.binary_path, 'simple.bin')
bin_size = os.path.getsize(binary_file)
logging.info('http_server_bin_size : {}KB'.format(bin_size // 1024))
# Upload binary and start testing
logging.info('Starting http_server simple test app')
# Parse IP address of STA
logging.info('Waiting to connect with AP')
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
dut.expect('Please input ssid password:')
env_name = 'wifi_router'
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
ap_password = get_env_config_variable(env_name, 'ap_password')
dut.write(f'{ap_ssid} {ap_password}')
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode()
got_port = int(dut.expect(r"(?:[\s\S]*)Starting server on port: '(\d+)'", timeout=30)[1].decode())
logging.info('Got IP : {}'.format(got_ip))
logging.info('Got Port : {}'.format(got_port))
# Expected Logs
dut.expect('Registering URI handlers', timeout=30)
logging.info('Test /sse GET handler')
try:
logging.info(f'Connecting to {got_ip}:{got_port}')
conn = http.client.HTTPConnection(got_ip, got_port, timeout=15)
conn.request('GET', url='/sse') # Ensure the URL path is correct
response = conn.getresponse()
# Process and verify only the first 5 lines of the response as the response is continuous
response_data = ''
for i, line in enumerate(response):
if i >= 5:
break
decoded_line = line.decode('utf-8').strip()
response_data += decoded_line
conn.close()
# Verify the format of the line
if 'data: Time since boot:' not in response_data:
raise RuntimeError(f'Unexpected line format: {response_data}')
except Exception as e:
logging.error(f'Error during SSE GET request: {e}')
raise RuntimeError('SSE handler test failed due to connection error')

View File

@@ -0,0 +1 @@
CONFIG_EXAMPLE_ENABLE_SSE_HANDLER=y