From 222d470312ea224635d0951117257edc74bf21fc Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 11 Feb 2026 17:07:08 +0800 Subject: [PATCH 1/2] feat(http_server): improve websocket server handling 1. Adds post handshake callback 2. Removes requirement to handle HTTP_GET message in websocket handler Closes https://github.com/espressif/esp-idf/issues/18215 --- components/esp_http_server/Kconfig | 9 ++++ .../esp_http_server/include/esp_http_server.h | 13 ++++-- components/esp_http_server/src/httpd_uri.c | 19 ++++++-- .../protocols/esp_http_server.rst | 9 ++++ .../ws_echo_server/main/Kconfig.projbuild | 11 +++++ .../ws_echo_server/main/ws_echo_server.c | 35 ++++++++++++--- .../pytest_ws_server_example.py | 43 +++++++++++++------ 7 files changed, 114 insertions(+), 25 deletions(-) diff --git a/components/esp_http_server/Kconfig b/components/esp_http_server/Kconfig index 5078d70da6..e5532104fd 100644 --- a/components/esp_http_server/Kconfig +++ b/components/esp_http_server/Kconfig @@ -72,4 +72,13 @@ menu "HTTP Server" Enable this option to use WebSocket pre-handshake callback. This will allow the server to register a callback function that will be called before the WebSocket handshake is processed i.e. before switching to the WebSocket protocol. + + config HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT + bool "WebSocket post-handshake callback support" + default n + depends on HTTPD_WS_SUPPORT + help + Enable this option to use WebSocket post-handshake callback. This will allow the server to register + a callback function that will be called after the WebSocket handshake is processed i.e. after switching + to the WebSocket protocol. endmenu diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index a531f4ccb8..ecd9b0d363 100644 --- a/components/esp_http_server/include/esp_http_server.h +++ b/components/esp_http_server/include/esp_http_server.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -465,8 +465,15 @@ typedef struct httpd_uri { * i.e. before the server responds with the WebSocket handshake response or before switching to the WebSocket handler. */ esp_err_t (*ws_pre_handshake_cb)(httpd_req_t *req); -#endif -#endif +#endif /* CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT */ +#if CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT || __DOXYGEN__ + /** + * Pointer to WebSocket post-handshake callback. This will be called after the WebSocket handshake is processed, + * i.e. after the server responds with the WebSocket handshake response or after switching to the WebSocket handler. + */ + esp_err_t (*ws_post_handshake_cb)(httpd_req_t *req); +#endif /* CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT */ +#endif /* CONFIG_HTTPD_WS_SUPPORT */ } httpd_uri_t; /** diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c index e16b7756e4..1710253cee 100644 --- a/components/esp_http_server/src/httpd_uri.c +++ b/components/esp_http_server/src/httpd_uri.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -169,6 +169,9 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle, #ifdef CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT hd->hd_calls[i]->ws_pre_handshake_cb = uri_handler->ws_pre_handshake_cb; #endif +#ifdef CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT + hd->hd_calls[i]->ws_post_handshake_cb = uri_handler->ws_post_handshake_cb; +#endif #ifdef CONFIG_HTTPD_WS_SUPPORT hd->hd_calls[i]->is_websocket = uri_handler->is_websocket; hd->hd_calls[i]->handle_ws_control_frames = uri_handler->handle_ws_control_frames; @@ -328,7 +331,7 @@ esp_err_t httpd_uri(struct httpd_data *hd) ESP_LOGW(TAG, LOG_FMT("ws_pre_handshake_cb failed")); return ESP_FAIL; } -#endif +#endif /* CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT */ ESP_LOGD(TAG, LOG_FMT("Responding WS handshake to sock %d"), aux->sd->fd); esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req, uri->supported_subprotocol); @@ -340,9 +343,17 @@ esp_err_t httpd_uri(struct httpd_data *hd) aux->sd->ws_handler = uri->handler; aux->sd->ws_control_frames = uri->handle_ws_control_frames; aux->sd->ws_user_ctx = uri->user_ctx; - } -#endif +#ifdef CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT + if (uri->ws_post_handshake_cb && uri->ws_post_handshake_cb(req) != ESP_OK) { + ESP_LOGW(TAG, LOG_FMT("ws_post_handshake_cb failed")); + return ESP_FAIL; + } +#endif /* CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT */ + /* If the request is websocket handshake, then do not call the uri->handler */ + return ESP_OK; + } +#endif /* CONFIG_HTTPD_WS_SUPPORT */ /* Invoke handler */ if (uri->handler(req) != ESP_OK) { /* Handler returns error, this socket should be closed */ diff --git a/docs/en/api-reference/protocols/esp_http_server.rst b/docs/en/api-reference/protocols/esp_http_server.rst index 167fac54a1..f033c18ab8 100644 --- a/docs/en/api-reference/protocols/esp_http_server.rst +++ b/docs/en/api-reference/protocols/esp_http_server.rst @@ -78,6 +78,15 @@ The pre-handshake callback can be used for authentication, authorization, or oth To use the WebSocket pre-handshake callback, you must enable :ref:`CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT` in your project configuration. +WebSocket Post-Handshake Callback +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Similar to the pre-handshake callback, the HTTP server component also provides a post-handshake callback for WebSocket endpoints. This callback is invoked after the WebSocket handshake is processed. + +At this point the connection has been upgraded to WebSocket, and the server has responded with the WebSocket handshake response. This post handshake callback can be used for logging, sending initial messages, or other setup tasks. + +To use the WebSocket post-handshake callback, you must enable :ref:`CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT` in your project configuration. + .. code-block:: c static esp_err_t ws_auth_handler(httpd_req_t *req) diff --git a/examples/protocols/http_server/ws_echo_server/main/Kconfig.projbuild b/examples/protocols/http_server/ws_echo_server/main/Kconfig.projbuild index 2883b67ed2..a11c9edf48 100644 --- a/examples/protocols/http_server/ws_echo_server/main/Kconfig.projbuild +++ b/examples/protocols/http_server/ws_echo_server/main/Kconfig.projbuild @@ -9,4 +9,15 @@ menu "Example Configuration" This will allow the server to register a callback function that will be called before the WebSocket handshake is processed. + config EXAMPLE_ENABLE_WS_POST_HANDSHAKE_CB + bool "Enable WebSocket post-handshake callback" + select HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT + default y + help + Enable this option to use WebSocket post-handshake callback. + This will allow the server to register a callback function that will be + called after the WebSocket handshake is processed. + In this example, the post-handshake callback is used to send a welcome message + to the client after the handshake is complete. + endmenu diff --git a/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c b/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c index f79cfcceed..d4c2433a56 100644 --- a/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c +++ b/examples/protocols/http_server/ws_echo_server/main/ws_echo_server.c @@ -87,16 +87,36 @@ static esp_err_t ws_pre_handshake_cb(httpd_req_t *req) } #endif +#ifdef CONFIG_EXAMPLE_ENABLE_WS_POST_HANDSHAKE_CB +static esp_err_t ws_post_handshake_cb(httpd_req_t *req) +{ + ESP_LOGI(TAG, "=== ws_post_handshake_cb called ==="); + + // Get the URI with query string + const char *uri = req->uri; + ESP_LOGI(TAG, "WebSocket connection established for URI: %s", uri ? uri : "NULL"); + + // Send a welcome message to the client + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + ws_pkt.payload = (uint8_t *)"Welcome to the WebSocket Echo Server (post-handshake)!"; + ws_pkt.len = strlen((char *)ws_pkt.payload); + esp_err_t ret = httpd_ws_send_frame(req, &ws_pkt); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret); + return ret; + } + return ESP_OK; +} +#endif /* CONFIG_EXAMPLE_ENABLE_WS_POST_HANDSHAKE_CB */ + /* * This handler echos back the received ws data * and triggers an async send if certain message received */ static esp_err_t echo_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)); @@ -156,8 +176,11 @@ static const httpd_uri_t ws_auth = { .user_ctx = NULL, .is_websocket = true, #ifdef CONFIG_EXAMPLE_ENABLE_WS_PRE_HANDSHAKE_CB - .ws_pre_handshake_cb = ws_pre_handshake_cb -#endif + .ws_pre_handshake_cb = ws_pre_handshake_cb, +#endif /* CONFIG_EXAMPLE_ENABLE_WS_PRE_HANDSHAKE_CB */ +#ifdef CONFIG_EXAMPLE_ENABLE_WS_POST_HANDSHAKE_CB + .ws_post_handshake_cb = ws_post_handshake_cb, +#endif /* CONFIG_EXAMPLE_ENABLE_WS_POST_HANDSHAKE_CB */ }; diff --git a/examples/protocols/http_server/ws_echo_server/pytest_ws_server_example.py b/examples/protocols/http_server/ws_echo_server/pytest_ws_server_example.py index 000d61987b..6393c19c3b 100644 --- a/examples/protocols/http_server/ws_echo_server/pytest_ws_server_example.py +++ b/examples/protocols/http_server/ws_echo_server/pytest_ws_server_example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import logging import os @@ -30,7 +30,7 @@ class WsClient: self.uri = uri def __enter__(self): # type: ignore - self.ws.connect('ws://{}:{}/{}'.format(self.ip, self.port, self.uri)) + self.ws.connect(f'ws://{self.ip}:{self.port}/{self.uri}') return self def __exit__(self, exc_type, exc_value, traceback): # type: ignore @@ -53,7 +53,7 @@ def test_examples_protocol_http_ws_echo_server(dut: Dut) -> None: # Get binary file binary_file = os.path.join(dut.app.binary_path, 'ws_echo_server.bin') bin_size = os.path.getsize(binary_file) - logging.info('http_ws_server_bin_size : {}KB'.format(bin_size // 1024)) + logging.info(f'http_ws_server_bin_size : {bin_size // 1024}KB') logging.info('Starting ws-echo-server test app based on http_server') @@ -68,8 +68,8 @@ def test_examples_protocol_http_ws_echo_server(dut: Dut) -> None: got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() got_port = dut.expect(r"Starting server on port: '(\d+)'", timeout=30)[1].decode() - logging.info('Got IP : {}'.format(got_ip)) - logging.info('Got Port : {}'.format(got_port)) + logging.info(f'Got IP : {got_ip}') + logging.info(f'Got Port : {got_port}') # Start ws server test with WsClient(got_ip, int(got_port), uri='ws') as ws: @@ -77,24 +77,24 @@ def test_examples_protocol_http_ws_echo_server(dut: Dut) -> None: for expected_opcode in [OPCODE_TEXT, OPCODE_BIN, OPCODE_PING]: ws.write(data=DATA, opcode=expected_opcode) opcode, data = ws.read() - logging.info('Testing opcode {}: Received opcode:{}, data:{}'.format(expected_opcode, opcode, data)) + logging.info(f'Testing opcode {expected_opcode}: Received opcode:{opcode}, data:{data}') data = data.decode() if expected_opcode == OPCODE_PING: dut.expect('Got a WS PING frame, Replying PONG') if opcode != OPCODE_PONG or data != DATA: - raise RuntimeError('Failed to receive correct opcode:{} or data:{}'.format(opcode, data)) + raise RuntimeError(f'Failed to receive correct opcode:{opcode} or data:{data}') continue dut_data = dut.expect(r'Got packet with message: ([A-Za-z0-9_]*)')[1] dut_opcode = dut.expect(r'Packet type: ([0-9]*)')[1].decode() if opcode != expected_opcode or data != DATA or opcode != int(dut_opcode) or (data not in str(dut_data)): - raise RuntimeError('Failed to receive correct opcode:{} or data:{}'.format(opcode, data)) + raise RuntimeError(f'Failed to receive correct opcode:{opcode} or data:{data}') ws.write(data='Trigger async', opcode=OPCODE_TEXT) opcode, data = ws.read() - logging.info('Testing async send: Received opcode:{}, data:{}'.format(opcode, data)) + logging.info(f'Testing async send: Received opcode:{opcode}, data:{data}') data = data.decode() if opcode != OPCODE_TEXT or data != 'Async data': - raise RuntimeError('Failed to receive correct opcode:{} or data:{}'.format(opcode, data)) + raise RuntimeError(f'Failed to receive correct opcode:{opcode} or data:{data}') @pytest.mark.wifi_router @@ -119,11 +119,30 @@ def test_ws_auth_handshake(dut: Dut) -> None: handshake_success = False try: # Attempt to use WSClient, expecting it to fail handshake - with WsClient(got_ip, int(got_port), uri='auth?token=valid') as ws: # type: ignore # noqa: F841 + with WsClient(got_ip, int(got_port), uri='auth?token=invalid') as ws: # type: ignore # noqa: F841 handshake_success = True except Exception as e: logging.info(f'WebSocket handshake failed: {e}') handshake_success = False - if handshake_success is False: + if handshake_success is True: raise RuntimeError('WebSocket handshake succeeded, but it should have been rejected by ws_pre_handshake_cb') + + try: + # Attempt to use WSClient, expecting it to succeed handshake + with WsClient(got_ip, int(got_port), uri='auth?token=valid') as ws: # type: ignore # noqa: F841 + handshake_success = True + dut.expect(r'ws_pre_handshake_cb called', timeout=10) + dut.expect(r'Valid token found, accepting handshake', timeout=10) + opcode, data = ws.read() + logging.info(f'Received opcode:{opcode}, data:{data}') + if opcode != OPCODE_TEXT or data.decode() != 'Welcome to the WebSocket Echo Server (post-handshake)!': + raise RuntimeError( + f'Failed to receive correct welcome message after handshake. Opcode:{opcode}, data:{data}' + ) + except Exception as e: + logging.info(f'WebSocket handshake failed: {e}') + handshake_success = False + + if handshake_success is False: + raise RuntimeError('WebSocket handshake failed, but it should have succeeded with valid token') From fca39df7dac5605fca2a5e1fcf46cc1812a5872a Mon Sep 17 00:00:00 2001 From: Zhang Shuxian Date: Wed, 25 Feb 2026 10:45:44 +0800 Subject: [PATCH 2/2] docs: Update CN translation for esp_http_server.rst --- docs/zh_CN/api-reference/protocols/esp_http_server.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/zh_CN/api-reference/protocols/esp_http_server.rst b/docs/zh_CN/api-reference/protocols/esp_http_server.rst index dcbfff1c29..6fd0a0b626 100644 --- a/docs/zh_CN/api-reference/protocols/esp_http_server.rst +++ b/docs/zh_CN/api-reference/protocols/esp_http_server.rst @@ -78,6 +78,15 @@ HTTP 服务器组件为 WebSocket 端点提供了握手前回调 (pre-handshake 要使用 WebSocket 握手前回调,需在项目配置中启用 :ref:`CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT` 选项。 +WebSocket 握手后回调 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +与握手前回调类似,HTTP 服务器组件也为 WebSocket 端点提供了握手后回调。该回调会在处理完 WebSocket 握手后被调用。 + +此时连接已升级为 WebSocket,服务器已返回 WebSocket 握手响应。该握手后回调可用于记录日志、发送初始消息或执行其他初始化任务。 + +要使用 WebSocket 握手后回调功能,需在项目配置中启用 :ref:`CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT` 选项。 + .. code-block:: c static esp_err_t ws_auth_handler(httpd_req_t *req)