forked from espressif/esp-idf
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:
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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')
|
||||
|
1
examples/protocols/http_server/simple/sdkconfig.ci.sse
Normal file
1
examples/protocols/http_server/simple/sdkconfig.ci.sse
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_EXAMPLE_ENABLE_SSE_HANDLER=y
|
Reference in New Issue
Block a user