mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
Merge branch 'fix/esp_http_server-ws-close-and-utf8-strictness' into 'master'
Fix(esp_http_server): ws close and utf8 strictness See merge request espressif/esp-idf!48908
This commit is contained in:
@@ -69,9 +69,21 @@ httpd_ws_recv_frame(req, &ws_pkt, MAX_PAYLOAD_LEN);
|
||||
2) Allocate the size based on the received packet length
|
||||
3) Call `httpd_ws_recv_frame()` with the allocated buffer
|
||||
|
||||
#### Fragmented messages
|
||||
|
||||
The WebSocket HTTP server does not support fragmented messages, as [RFC6455, section 5.4](https://tools.ietf.org/html/rfc6455#section-5.4) defines them.
|
||||
|
||||
On receive, the server passes each frame to the handler on its own. It does not join the fragments of one message. A handler that gets a message in three fragments sees three separate frames. Use the `final` and `fragmented` fields of `httpd_ws_frame_t` to detect a fragment, and join the payloads in the application.
|
||||
|
||||
The server also does not validate the fragment sequence. It accepts a CONTINUE frame that continues no message. It also accepts a new TEXT or BINARY frame while a fragmented message is still open. RFC 6455 requires a close with status code 1002 in both cases.
|
||||
|
||||
`CONFIG_HTTPD_WS_STRICTER_RFC6455` validates the UTF-8 of a complete, unfragmented TEXT frame only. It does not validate a TEXT message that arrives in fragments. Join the fragments and call `httpd_ws_validate_utf8()` on the result.
|
||||
|
||||
This example echoes each frame as a complete message, so it does not handle fragments either.
|
||||
|
||||
#### Handling outgoing data
|
||||
|
||||
Please note that the WebSocket HTTP server does not automatically fragment messages.
|
||||
On transmit, the server does not automatically fragment messages.
|
||||
Each outgoing frame has the FIN flag set by default.
|
||||
In case an application wants to send fragmented data, it must be done manually by setting the
|
||||
`fragmented` option and using the `final` flag as described in [RFC6455, section 5.4](https://tools.ietf.org/html/rfc6455#section-5.4).
|
||||
@@ -84,10 +96,10 @@ This example registers a dedicated control-frame handler on the `/ws` endpoint (
|
||||
|
||||
```c
|
||||
.handle_ws_control_frames = true,
|
||||
.ws_control_handler = ws_control_frame_handler, // observes PING/PONG/CLOSE
|
||||
.ws_control_handler = ws_control_frame_handler, // handles PING/PONG/CLOSE
|
||||
```
|
||||
|
||||
The handler only observes the frames (this example logs them); the server still sends the protocol replies (PONG for PING, CLOSE for CLOSE) itself. Send the text message `Ping` to the server to watch the full heartbeat round trip: the server sends a PING and the client's PONG response is logged by the control-frame handler.
|
||||
Control frames then go to `ws_control_frame_handler()` instead of the data handler, so `echo_handler()` only deals with TEXT/BINARY frames. The server receives the control frame body for the handler but does not reply, so the handler sends the protocol reply itself (a PONG echoing the payload for a PING, an empty CLOSE for a CLOSE). Send the text message `Ping` to the server to watch the full heartbeat round trip: the server sends a PING and the client's PONG response is logged by the control-frame handler.
|
||||
|
||||
|
||||
### Hardware Required
|
||||
|
||||
@@ -25,9 +25,9 @@ menu "Example Configuration"
|
||||
default y
|
||||
help
|
||||
Enable this option to register a dedicated handler for WebSocket
|
||||
control frames (PING, PONG, CLOSE) on the /ws endpoint. The handler
|
||||
only observes the frames (e.g. for heartbeat tracking or logging);
|
||||
the server still sends the protocol replies (PONG for PING, CLOSE
|
||||
for CLOSE) itself.
|
||||
control frames (PING, PONG, CLOSE) on the /ws endpoint, keeping them
|
||||
out of the data handler. The server receives the frame body for the
|
||||
handler, which then sends the protocol replies itself (PONG for
|
||||
PING, CLOSE for CLOSE).
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -141,31 +141,39 @@ static esp_err_t ws_post_handshake_cb(httpd_req_t *req)
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ENABLE_WS_CONTROL_FRAME_HANDLER
|
||||
/*
|
||||
* Dedicated control-frame handler: observes PING/PONG/CLOSE frames without
|
||||
* receiving them in the data handler. The frame is read-only and owned by the
|
||||
* server, which sends the protocol reply (PONG for PING, CLOSE for CLOSE)
|
||||
* itself after this handler returns.
|
||||
* Dedicated control-frame handler: keeps PING/PONG/CLOSE out of the data handler
|
||||
* below, which therefore only deals with TEXT/BINARY frames.
|
||||
*
|
||||
* The server has already received the frame body (no allocation needed here) but
|
||||
* does not reply, so this handler owns the protocol reply: a PONG echoing the
|
||||
* payload for a PING, an empty CLOSE for a CLOSE, nothing for a PONG. The frame
|
||||
* is owned by the server and is reused in place for the reply; it must not be
|
||||
* freed, retained, or grown past the received length.
|
||||
*
|
||||
* Type "Ping" in the client to see the full heartbeat round trip: the server
|
||||
* sends a PING and the client's PONG response lands here.
|
||||
*/
|
||||
static esp_err_t ws_control_frame_handler(httpd_req_t *req, const httpd_ws_frame_t *frame)
|
||||
static esp_err_t ws_control_frame_handler(httpd_req_t *req, httpd_ws_frame_t *frame)
|
||||
{
|
||||
switch (frame->type) {
|
||||
case HTTPD_WS_TYPE_PING:
|
||||
ESP_LOGI(TAG, "Control frame: PING (len %d), server replies PONG", frame->len);
|
||||
break;
|
||||
ESP_LOGI(TAG, "Control frame: PING (len %d), replying PONG", frame->len);
|
||||
frame->type = HTTPD_WS_TYPE_PONG;
|
||||
return httpd_ws_send_frame(req, frame);
|
||||
case HTTPD_WS_TYPE_PONG:
|
||||
/* Reply to our own PING; nothing to send back (RFC 6455, section 5.5.3) */
|
||||
ESP_LOGI(TAG, "Control frame: PONG, heartbeat alive");
|
||||
break;
|
||||
return ESP_OK;
|
||||
case HTTPD_WS_TYPE_CLOSE:
|
||||
ESP_LOGI(TAG, "Control frame: CLOSE (len %d), server replies CLOSE", frame->len);
|
||||
break;
|
||||
ESP_LOGI(TAG, "Control frame: CLOSE (len %d), replying CLOSE", frame->len);
|
||||
frame->len = 0;
|
||||
frame->payload = NULL;
|
||||
return httpd_ws_send_frame(req, frame);
|
||||
default:
|
||||
ESP_LOGI(TAG, "Control frame: type %d", frame->type);
|
||||
break;
|
||||
/* Reserved control opcode: fail the connection (RFC 6455, section 5.2) */
|
||||
ESP_LOGW(TAG, "Control frame: reserved opcode %d, closing connection", frame->type);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif /* CONFIG_EXAMPLE_ENABLE_WS_CONTROL_FRAME_HANDLER */
|
||||
|
||||
@@ -215,9 +223,11 @@ static esp_err_t echo_handler(httpd_req_t *req)
|
||||
}
|
||||
}
|
||||
|
||||
ret = httpd_ws_send_frame(req, &ws_pkt);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY) {
|
||||
ret = httpd_ws_send_frame(req, &ws_pkt);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
return ret;
|
||||
@@ -310,8 +320,8 @@ static const httpd_uri_t ws = {
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = true,
|
||||
#ifdef CONFIG_EXAMPLE_ENABLE_WS_CONTROL_FRAME_HANDLER
|
||||
/* Route control frames to the dedicated handler; the server still
|
||||
* sends the protocol replies itself. */
|
||||
/* Route control frames to the dedicated handler, which owns the
|
||||
* protocol replies; echo_handler() then only sees data frames. */
|
||||
.handle_ws_control_frames = true,
|
||||
.ws_control_handler = ws_control_frame_handler,
|
||||
#endif /* CONFIG_EXAMPLE_ENABLE_WS_CONTROL_FRAME_HANDLER */
|
||||
|
||||
Reference in New Issue
Block a user