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
This commit is contained in:
Ashish Sharma
2026-02-11 17:07:08 +08:00
parent b80c6197ea
commit 5028b6230f
7 changed files with 104 additions and 19 deletions
+9
View File
@@ -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"
@@ -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;
/**
+15 -4
View File
@@ -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 */
@@ -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)
@@ -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
@@ -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 */
};
@@ -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')