diff --git a/components/esp_http_server/Kconfig b/components/esp_http_server/Kconfig index bcf95f28b9..38b1dad20d 100644 --- a/components/esp_http_server/Kconfig +++ b/components/esp_http_server/Kconfig @@ -81,6 +81,15 @@ menu "HTTP Server" 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. + config HTTPD_SERVER_PSA_CRYPTO_MIGRATE depends on MBEDTLS_VER_4_X_SUPPORT bool "Migrate ESP HTTP Server to use PSA Crypto" diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index 38b5b1926b..61649f855a 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 */ @@ -467,8 +467,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 ac809a4034..916807eaaa 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)); @@ -148,10 +168,6 @@ static esp_err_t echo_handler(httpd_req_t *req) */ static esp_err_t echo_partial_handler(httpd_req_t *req) { - if (req->method == HTTP_GET) { - ESP_LOGI(TAG, "Handshake done, the new connection was opened (partial)"); - return ESP_OK; - } httpd_ws_frame_t ws_pkt; uint8_t *chunk_buf = NULL; memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); @@ -248,8 +264,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 86c627e080..91d13980e8 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 @@ -228,11 +228,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')