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 :
1. URI \hello for GET command returns "Hello World!" 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

View File

@@ -26,4 +26,11 @@ menu "Example Configuration"
help
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

View File

@@ -22,7 +22,8 @@
#include "esp_netif.h"
#include "esp_tls.h"
#include "esp_check.h"
#include <time.h>
#include <sys/time.h>
#if !CONFIG_IDF_TARGET_LINUX
#include <esp_wifi.h>
#include <esp_system.h>
@@ -392,6 +393,40 @@ static const httpd_uri_t ctrl = {
.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)
{
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, &ctrl);
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);
#endif
#endif
return server;
}

View File

@@ -1,6 +1,6 @@
#!/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
import logging
import os
@@ -14,6 +14,7 @@ import time
import pytest
try:
import http.client
from idf_http_server_test import client
except ModuleNotFoundError:
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:
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