Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Web_socket_patch.zip
Binary file not shown.
16 changes: 13 additions & 3 deletions components/esp_websocket_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@

[![Component Registry](https://components.espressif.com/components/espressif/esp_websocket_client/badge.svg)](https://components.espressif.com/components/espressif/esp_websocket_client)

The `esp-websocket_client` component is a managed component for `esp-idf` that contains implementation of [WebSocket protocol client](https://datatracker.ietf.org/doc/html/rfc6455) for ESP32
The `esp-websocket_client` component is a managed component for `esp-idf` that contains an implementation of the [WebSocket protocol client](https://datatracker.ietf.org/doc/html/rfc6455) for ESP targets.

## Highlights

- WebSocket over TCP (`ws`) and TLS (`wss`)
- Optional header callback event (`WEBSOCKET_EVENT_HEADER_RECEIVED`, IDF 6+)
- Fragmented message send helpers for text and binary payloads
- Runtime ping/reconnect tuning APIs
- Pause/resume support without destroying the websocket task
- Automatic HTTP redirect handling during websocket upgrade

## Examples

Get started with example test [example](https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client/examples):
- Component examples: <https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client/examples>
- Feature showcase example (comprehensive walkthrough): <https://github.com/espressif/esp-protocols/tree/master/examples/websocket_features>

## Documentation

* View the full [html documentation](https://docs.espressif.com/projects/esp-protocols/esp_websocket_client/docs/latest/index.html)
- Full HTML documentation: <https://docs.espressif.com/projects/esp-protocols/esp_websocket_client/docs/latest/index.html>
142 changes: 138 additions & 4 deletions components/esp_websocket_client/esp_websocket_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@
#include <errno.h>
#include <arpa/inet.h>

/*
* ESP-IDF 4.4.x compatibility shims.
* These functions / struct fields were added in later esp-protocols releases
* and are not present in the bundled IDF 4.4.7 tcp_transport library.
*/
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)

/* esp_transport_ws_get_fin_flag() is unavailable in IDF 4.4.
* The old transport always delivers complete frames, so default to true. */
static inline bool esp_transport_ws_get_fin_flag(esp_transport_handle_t t)
{
(void)t;
return true;
}

/* esp_transport_ws_get_upgrade_request_status() is unavailable in IDF 4.4.
* Return 0 (unknown) when the API does not exist. */
static inline int esp_transport_ws_get_upgrade_request_status(esp_transport_handle_t t)
{
(void)t;
return 0;
}

#endif /* ESP_IDF_VERSION < 5.0.0 */

static const char *TAG = "websocket_client";

#define WEBSOCKET_TCP_DEFAULT_PORT (80)
Expand Down Expand Up @@ -75,6 +100,8 @@ const static int STOPPED_BIT = BIT0;
const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client
// and we are waiting for the server to continue with clean close
const static int REQUESTED_STOP_BIT = BIT2; // Indicates that a client stop has been requested
const static int RESUME_BIT = BIT3; // Signal to resume from PAUSED state
const static int WAKE_BIT = BIT4; // Generic: wake task from any blocking event wait to re-check state

ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);

Expand Down Expand Up @@ -122,6 +149,7 @@ typedef enum {
WEBSOCKET_STATE_CONNECTED,
WEBSOCKET_STATE_WAIT_TIMEOUT,
WEBSOCKET_STATE_CLOSING,
WEBSOCKET_STATE_PAUSED, // Task alive but blocked; waiting for RESUME_BIT
} websocket_client_state_t;

struct esp_websocket_client {
Expand Down Expand Up @@ -259,8 +287,8 @@ static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_hand


if (client->state == WEBSOCKET_STATE_CLOSING || client->state == WEBSOCKET_STATE_UNKNOW ||
client->state == WEBSOCKET_STATE_WAIT_TIMEOUT) {
ESP_LOGW(TAG, "Connection already closing/closed, skipping abort");
client->state == WEBSOCKET_STATE_WAIT_TIMEOUT || client->state == WEBSOCKET_STATE_PAUSED) {
ESP_LOGW(TAG, "Connection already closing/closed/paused, skipping abort");
goto cleanup;
}

Expand Down Expand Up @@ -536,6 +564,82 @@ static esp_err_t stop_wait_task(esp_websocket_client_handle_t client)
return ESP_OK;
}

esp_err_t esp_websocket_client_pause(esp_websocket_client_handle_t client)
{
if (client == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!client->run) {
ESP_LOGW(TAG, "Client was not started");
return ESP_FAIL;
}

/* Cannot pause from within the websocket task */
TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
if (running_task == client->task_handle) {
ESP_LOGE(TAG, "Client cannot be paused from websocket task");
return ESP_FAIL;
}

xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);

if (client->state == WEBSOCKET_STATE_PAUSED) {
xSemaphoreGiveRecursive(client->lock);
return ESP_OK; /* already paused */
}

bool was_connected = (client->state == WEBSOCKET_STATE_CONNECTED);

/* Close the TCP/TLS transport (does NOT destroy the transport object) */
if (client->transport) {
esp_transport_close(client->transport);
}

client->state = WEBSOCKET_STATE_PAUSED;
xEventGroupClearBits(client->status_bits, CLOSE_FRAME_SENT_BIT | RESUME_BIT);

xSemaphoreGiveRecursive(client->lock);

/* Wake the task if it's blocked in any event-group wait (e.g. WAIT_TIMEOUT) */
xEventGroupSetBits(client->status_bits, WAKE_BIT);

if (was_connected) {
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0);
}

ESP_LOGI(TAG, "Client paused (task kept alive)");
return ESP_OK;
}

esp_err_t esp_websocket_client_resume(esp_websocket_client_handle_t client, const char *headers)
{
if (client == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!client->run) {
ESP_LOGW(TAG, "Client was not started");
return ESP_FAIL;
}
if (client->state != WEBSOCKET_STATE_PAUSED) {
ESP_LOGW(TAG, "Client is not paused (state=%d)", (int)client->state);
return ESP_FAIL;
}

/* Update config headers while the task is blocked (safe, no lock needed).
* The task will call set_websocket_transport_optional_settings() when it
* picks up RESUME_BIT to push these into the ws transport layer. */
if (headers) {
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
free(client->config->headers);
client->config->headers = strdup(headers);
xSemaphoreGiveRecursive(client->lock);
}

xEventGroupSetBits(client->status_bits, RESUME_BIT);
ESP_LOGI(TAG, "Resume requested");
return ESP_OK;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed .auth field breaks HTTP Basic Authentication

High Severity

The .auth = client->config->auth line was removed from the esp_transport_ws_config_t initialization in set_websocket_transport_optional_settings(). The auth field is computed from username/password via http_auth_basic() and stored in client->config->auth, but it's no longer passed to the transport layer. This silently breaks HTTP Basic Authentication for all websocket connections that use username/password credentials.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugbot Autofix determined this is a false positive.

The websocket transport configuration already includes .auth = client->config->auth, so Basic Auth credentials are still propagated correctly.

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
static void websocket_header_hook(void * client, const char * line, int line_len)
{
Expand All @@ -557,7 +661,6 @@ static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_
.header_hook = websocket_header_hook,
.header_user_context = client,
#endif
.auth = client->config->auth,
.propagate_control_frames = true
};
return esp_transport_ws_set_config(trans, &config);
Expand Down Expand Up @@ -1333,6 +1436,9 @@ static void esp_websocket_client_task(void *pv)
xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT);
}
break;
case WEBSOCKET_STATE_PAUSED:
// Nothing to do — task will block on event bits below
break;
default:
ESP_LOGD(TAG, "Client run iteration in a default state: %d", client->state);
break;
Expand Down Expand Up @@ -1365,7 +1471,27 @@ static void esp_websocket_client_task(void *pv)
}
} else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) {
// waiting for reconnection or a request to stop the client...
xEventGroupWaitBits(client->status_bits, REQUESTED_STOP_BIT, false, true, client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
xEventGroupWaitBits(client->status_bits, REQUESTED_STOP_BIT | WAKE_BIT, false, false, client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
xEventGroupClearBits(client->status_bits, WAKE_BIT); // consume wake signal
} else if (WEBSOCKET_STATE_PAUSED == client->state) {
// Block until resume or stop requested — zero CPU while parked
EventBits_t bits = xEventGroupWaitBits(client->status_bits,
RESUME_BIT | REQUESTED_STOP_BIT,
true, /* clear on exit */
false, /* any bit */
portMAX_DELAY);
if (bits & REQUESTED_STOP_BIT) {
client->run = false;
}
if (bits & RESUME_BIT) {
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
// Refresh transport WS settings (path, headers) from config
set_websocket_transport_optional_settings(client, client->config->scheme);
client->state = WEBSOCKET_STATE_INIT;
xEventGroupClearBits(client->status_bits, CLOSE_FRAME_SENT_BIT | STOPPED_BIT);
xSemaphoreGiveRecursive(client->lock);
ESP_LOGI(TAG, "Resumed from paused state");
}
} else if (WEBSOCKET_STATE_CLOSING == client->state &&
(CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) {
ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server");
Expand Down Expand Up @@ -1402,6 +1528,7 @@ static void esp_websocket_client_task(void *pv)
} else {
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
}
ESP_LOGI(TAG, "[DIAG] websocket_task calling vTaskDelete(NULL) tick=%lu", (unsigned long)xTaskGetTickCount());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diagnostic debug log accidentally left in production code

Low Severity

The [DIAG] prefixed ESP_LOGI log line printing tick count before vTaskDelete appears to be development-time diagnostic code that was not removed before submission. It adds noise to production logs and uses a non-standard [DIAG] prefix not found elsewhere in the codebase.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugbot Autofix determined this is a false positive.

No [DIAG] ESP_LOGI line before vTaskDelete(NULL) is present in the current source, so there is no diagnostic log to remove.

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

vTaskDelete(NULL);
}

Expand Down Expand Up @@ -1533,6 +1660,13 @@ int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const ch
return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout);
}

/* Backward-compat: generic send (defaults to binary). Removed upstream but
* still present in the IDF 4.4 pre-compiled archive API. */
int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
{
return esp_websocket_client_send_bin(client, data, len, timeout);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-adds previously removed deprecated API function

Medium Severity

esp_websocket_client_send() was intentionally deprecated and removed in an earlier release (documented in CHANGELOG.md as "removed deprecated API"). This PR re-introduces it as a backward-compat shim. Re-adding a function that was deliberately removed creates confusion about the API surface and contradicts the project's prior deprecation decision.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugbot Autofix determined this is a false positive.

The deprecated esp_websocket_client_send() API is not declared or defined in the current codebase, so it has not been reintroduced.

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.


int esp_websocket_client_send_bin_partial(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
{
return esp_websocket_client_send_with_exact_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout);
Expand Down
45 changes: 45 additions & 0 deletions components/esp_websocket_client/include/esp_websocket_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,36 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client);
*/
esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client);

/**
* @brief Pause the WebSocket client without destroying the task.
*
* Closes the TCP/TLS transport but keeps the internal FreeRTOS task alive
* (blocked at zero CPU cost). Call esp_websocket_client_resume() to reconnect
* using the same task, avoiding vTaskDelete/xTaskCreate overhead.
*
* Notes:
* - Cannot be called from the websocket event handler / websocket task
* - After pause, call set_uri() if the endpoint changed, then resume()
*
* @param[in] client The client
*
* @return esp_err_t
*/
esp_err_t esp_websocket_client_pause(esp_websocket_client_handle_t client);

/**
* @brief Resume a paused WebSocket client.
*
* Wakes the internal task which will re-apply transport settings from config
* (updated path, headers) and establish a fresh connection.
*
* @param[in] client The client (must be in paused state)
* @param[in] headers Optional new headers to set before reconnecting (NULL to keep current)
*
* @return esp_err_t
*/
esp_err_t esp_websocket_client_resume(esp_websocket_client_handle_t client, const char *headers);

/**
* @brief Destroy the WebSocket connection and free all resources.
* This function must be the last function to call for an session.
Expand Down Expand Up @@ -271,6 +301,21 @@ esp_err_t esp_websocket_client_destroy_on_exit(esp_websocket_client_handle_t cli
*/
int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout);

/**
* @brief Generic write data to the WebSocket connection; defaults to binary send
* (backward-compat shim kept for IDF 4.4 API parity)
*
* @param[in] client The client
* @param[in] data The data
* @param[in] len The length
* @param[in] timeout Write data timeout in RTOS ticks
*
* @return
* - Number of data was sent
* - (-1) if any errors
*/
int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout);

/**
* @brief Write binary data to the WebSocket connection and sends it without setting the FIN flag(data send with WS OPCODE=02, i.e. binary)
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,34 @@ TEST(websocket, websocket_set_invalid_url)
esp_websocket_client_destroy(client);
}

TEST(websocket, websocket_pause_resume_argument_validation)
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_websocket_client_pause(NULL));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_websocket_client_resume(NULL, NULL));
}

TEST(websocket, websocket_pause_resume_when_not_started)
{
const esp_websocket_client_config_t websocket_cfg = {
.uri = "ws://echo.websocket.org",
};

esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
TEST_ASSERT_NOT_NULL(client);

TEST_ASSERT_EQUAL(ESP_FAIL, esp_websocket_client_pause(client));
TEST_ASSERT_EQUAL(ESP_FAIL, esp_websocket_client_resume(client, NULL));

esp_websocket_client_destroy(client);
}

TEST_GROUP_RUNNER(websocket)
{
RUN_TEST_CASE(websocket, websocket_init_deinit)
RUN_TEST_CASE(websocket, websocket_init_invalid_url)
RUN_TEST_CASE(websocket, websocket_set_invalid_url)
RUN_TEST_CASE(websocket, websocket_pause_resume_argument_validation)
RUN_TEST_CASE(websocket, websocket_pause_resume_when_not_started)
}

void app_main(void)
Expand Down
13 changes: 10 additions & 3 deletions docs/esp_websocket_client/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ The ESP WebSocket client is an implementation of `WebSocket protocol client <htt

Features
--------
* Supports WebSocket over TCP, TLS with mbedtls
* Supports WebSocket over TCP and TLS with mbedtls
* Supports HTTP handshake header callback via ``WEBSOCKET_EVENT_HEADER_RECEIVED``
* Supports automatic HTTP redirect handling during websocket upgrade
* Supports pause/resume without destroying the websocket task
* Supports fragmented text/binary transmission APIs
* Easy to setup with URI
* Multiple instances (Multiple clients in one application)
* Multiple instances (multiple clients in one application)

Configuration
-------------
Expand Down Expand Up @@ -124,6 +128,7 @@ Events
* `WEBSOCKET_EVENT_BEGIN`: The client thread is running.
* `WEBSOCKET_EVENT_BEFORE_CONNECT`: The client is about to connect.
* `WEBSOCKET_EVENT_CONNECTED`: The client has successfully established a connection to the server. The client is now ready to send and receive data. Contains no event data.
* `WEBSOCKET_EVENT_HEADER_RECEIVED`: Posted for each pre-upgrade HTTP response header (IDF 6.0+ builds).
* `WEBSOCKET_EVENT_DATA`: The client has successfully received and parsed a WebSocket frame. The event data contains a pointer to the payload data, the length of the payload data as well as the opcode of the received frame. A message may be fragmented into multiple events if the length exceeds the buffer size. This event will also be posted for non-payload frames, e.g. pong or connection close frames.
* `WEBSOCKET_EVENT_ERROR`: The client has experienced an error. Examples include transport write or read failures.
* `WEBSOCKET_EVENT_DISCONNECTED`: The client has aborted the connection due to the transport layer failing to read data, e.g. because the server is unavailable. Contains no event data.
Expand All @@ -143,7 +148,9 @@ Limitations and Known Issues

Application Example
-------------------
A simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org <https://websocket.org>`_ server can be found here: `example <https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client/examples>`_
A simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data can be found in the component example directory: `examples <https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client/examples>`_.

For a broader feature walkthrough (headers callback, fragmented frames, pause/resume, runtime ping/reconnect tuning), see: `websocket_features example <https://github.com/espressif/esp-protocols/tree/master/examples/websocket_features>`_.

Sending Text Data
^^^^^^^^^^^^^^^^^
Expand Down
3 changes: 3 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ This directory showcases a variety of examples, illustrating the use of differen
3. **MQTT Demo on Linux**: A comprehensive demonstration of an MQTT (Message Queuing Telemetry Transport) application designed to run on a Linux environment.
Location: [mqtt](mqtt)

4. **WebSocket Feature Showcase**: Demonstrates modern `esp_websocket_client` features such as header callbacks, custom headers, fragmented frames, pause/resume, and runtime ping/reconnect tuning.
Location: [websocket_features](websocket_features)

## Additional Resources and Examples

For an extensive collection of additional examples, especially those related to specific components, please visit the upper layer directory and navigate to the respective component's example directory.
Expand Down
3 changes: 3 additions & 0 deletions examples/websocket_features/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(websocket_features)
Loading