Send binary WebSocket frames in one write
This commit is contained in:
+49
-1
@@ -11,7 +11,7 @@
|
||||
#include "secure_random.h"
|
||||
|
||||
#if ESP_IDF_VERSION != ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
#error "Reaudit HTTPD headers, upgrade, request completion and idle cleanup for this IDF"
|
||||
#error "Reaudit HTTPD headers, upgrade, idle cleanup and WS/TLS send contracts for this IDF"
|
||||
#endif
|
||||
|
||||
/* IDF 5.5.0 httpd_sess_process increments lru_counter only AFTER successful
|
||||
@@ -60,6 +60,54 @@ void web_httpd_idle_sweep(httpd_handle_t server,
|
||||
}
|
||||
}
|
||||
|
||||
static int web_httpd_aborted_send(httpd_handle_t server, int fd,
|
||||
const char *buffer, size_t length, int flags)
|
||||
{
|
||||
(void)server; (void)fd; (void)buffer; (void)length; (void)flags;
|
||||
return HTTPD_SOCK_ERR_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t web_httpd_ws_send_binary(httpd_handle_t server, int fd,
|
||||
const void *expected_context,
|
||||
const uint8_t *payload, size_t length)
|
||||
{
|
||||
struct httpd_data *hd = server;
|
||||
if (!hd || fd < 0 || !expected_context || (!payload && length) ||
|
||||
length > WEB_HTTPD_WS_BINARY_MAX_PAYLOAD) return ESP_ERR_INVALID_ARG;
|
||||
if (httpd_os_thread_handle() != hd->hd_td.handle || hd->hd_req_aux.sd)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
struct sock_db *sd = httpd_sess_get(hd, fd);
|
||||
if (!sd || sd->ctx != expected_context || !sd->ws_handshake_done ||
|
||||
sd->ws_close || sd->for_async_req || !sd->send_fn)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
/* IDF 5.5.0 httpd_ws_send_frame_async emits header/payload separately.
|
||||
* Keep the existing send override (HTTPS -> esp_tls_conn_write), not raw
|
||||
* socket IO. Owner-local scratch lives through the synchronous call only. */
|
||||
uint8_t wire[WEB_HTTPD_WS_BINARY_MAX_PAYLOAD + 4U];
|
||||
size_t header = length <= 125U ? 2U : 4U;
|
||||
wire[0] = 0x82; /* FIN, binary; server frames are never masked. */
|
||||
wire[1] = header == 2U ? (uint8_t)length : 126U;
|
||||
if (header == 4U) {
|
||||
wire[2] = (uint8_t)(length >> 8U);
|
||||
wire[3] = (uint8_t)length;
|
||||
}
|
||||
if (length) memcpy(wire + header, payload, length);
|
||||
size_t total = header + length;
|
||||
int sent = sd->send_fn(hd, fd, (const char *)wire, total, 0);
|
||||
if (sent == (int)total) return ESP_OK;
|
||||
/* Owner retains this validated session across the synchronous send. TLS
|
||||
* may hold pending output after short/zero/WANT/error: never retry it with
|
||||
* different arguments. Deferred close alone permits SDK automatic PONG or
|
||||
* CLOSE first. Shutdown alone cannot block buffered-input TLS calls either.
|
||||
* Reject all sends before shutdown, even if shutdown fails. ws_close also
|
||||
* skips SDK request processing; normal HTTPD still owns TLS destruction. */
|
||||
sd->send_fn = web_httpd_aborted_send;
|
||||
sd->ws_close = true;
|
||||
(void)shutdown(fd, SHUT_RDWR);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
bool web_httpd_headers_valid(httpd_req_t *request)
|
||||
{
|
||||
if (!request || !request->aux) return false;
|
||||
|
||||
@@ -26,6 +26,15 @@ void web_httpd_wipe_request(httpd_req_t *request, bool closing);
|
||||
esp_err_t web_httpd_upgrade(httpd_req_t *request,
|
||||
esp_err_t (*handler)(httpd_req_t *));
|
||||
|
||||
#define WEB_HTTPD_WS_BINARY_MAX_PAYLOAD 512U
|
||||
/* HTTPD-owner work only; caller retains generation/lifecycle and one-work-slot
|
||||
* ownership. Synchronous FIN/binary/unmasked send through the session override.
|
||||
* Non-full writes synchronously block further session sends and abort the socket;
|
||||
* caller must retain normal failure/cleanup accounting, never replay the frame. */
|
||||
esp_err_t web_httpd_ws_send_binary(httpd_handle_t server, int fd,
|
||||
const void *expected_context,
|
||||
const uint8_t *payload, size_t length);
|
||||
|
||||
/* Serialized server startup only, exact-match ordinary GET, URI <= 127 bytes.
|
||||
* Stage both allocations before publication; HTTPD owns/frees them on success. */
|
||||
esp_err_t web_httpd_register_optional_get(httpd_handle_t server, const httpd_uri_t *uri);
|
||||
|
||||
@@ -65,6 +65,9 @@ typedef struct {
|
||||
uint32_t generation;
|
||||
} web_serial_work_t;
|
||||
|
||||
_Static_assert(WEB_SERIAL_TRANSPORT_TX_PAYLOAD_SIZE == WEB_HTTPD_WS_BINARY_MAX_PAYLOAD,
|
||||
"Reaudit bounded binary WS send when changing serial payload size");
|
||||
|
||||
typedef struct web_serial_slot {
|
||||
web_serial_slot_state_t state;
|
||||
httpd_handle_t server;
|
||||
@@ -1057,7 +1060,9 @@ static void web_serial_send_work(void *argument)
|
||||
};
|
||||
send_called = true;
|
||||
if (sample_epoch && performance_gate() == sample_epoch) send_start = esp_timer_get_time();
|
||||
result = httpd_ws_send_frame_async(server, socket_fd, &frame);
|
||||
result = type == HTTPD_WS_TYPE_BINARY
|
||||
? web_httpd_ws_send_binary(server, socket_fd, slot, slot->tx_data, length)
|
||||
: httpd_ws_send_frame_async(server, socket_fd, &frame);
|
||||
if (sample_epoch && performance_gate() == sample_epoch) send_end = esp_timer_get_time();
|
||||
if (result == ESP_OK) {
|
||||
(void)httpd_sess_update_lru_counter(server, socket_fd);
|
||||
|
||||
Reference in New Issue
Block a user