fix(esp_http_server): let the WS control handler own the protocol reply

This commit is contained in:
Ashish Sharma
2026-09-04 16:24:38 +08:00
parent 731ef25e37
commit 8a01f77647
9 changed files with 116 additions and 69 deletions
@@ -90,7 +90,7 @@ struct sock_db {
bool ws_close_sent; /*!< Set to true once this endpoint has sent its own WS Close frame */
esp_err_t (*ws_handler)(httpd_req_t *r); /*!< WebSocket handler, leave to null if it's not WebSocket */
bool ws_control_frames; /*!< WebSocket flag indicating that control frames should be passed to user handlers */
esp_err_t (*ws_control_handler)(httpd_req_t *r, const httpd_ws_frame_t *frame); /*!< Dedicated WebSocket control-frame handler, NULL if not used */
esp_err_t (*ws_control_handler)(httpd_req_t *r, httpd_ws_frame_t *frame); /*!< Dedicated WebSocket control-frame handler, NULL if not used */
void *ws_user_ctx; /*!< Pointer to user context data which will be available to handler for websocket*/
#endif
};
@@ -582,8 +582,10 @@ esp_err_t httpd_ws_recv_control_frame(httpd_req_t *req, httpd_ws_frame_t *frame,
/**
* @brief Send the protocol reply for a received WebSocket control frame.
*
* @note PING is answered with a PONG echoing the payload; CLOSE is answered with
* an empty CLOSE; all other control frames (e.g. PONG) require no reply.
* @note PING is answered with a PONG echoing the payload. CLOSE is answered with
* a CLOSE that echoes the received status code and reason when
* CONFIG_HTTPD_WS_STRICTER_RFC6455 is set, and with an empty CLOSE
* otherwise. All other control frames (for example PONG) require no reply.
*
* @param[in] req WebSocket request
* @param[in] frame Control frame previously received (modified in place)
@@ -597,15 +599,18 @@ esp_err_t httpd_ws_reply_to_control_frame(httpd_req_t *req, httpd_ws_frame_t *fr
* @brief Handle an incoming WebSocket control frame via the dedicated control handler.
*
* @note Used only when a ws_control_handler is registered (handle_ws_control_frames
* must be true). The server receives the frame body, passes a read-only view
* to the control handler, then performs the protocol reply itself. If the
* control handler returns an error, the reply is still sent and the error is
* propagated so the caller closes the socket.
* must be true). The server receives the frame body and hands it to the
* control handler, which owns the protocol reply. The server does not reply
* on its own, so a handler that answers a PING cannot produce a duplicate
* PONG on the wire. A handler error is propagated so the caller closes the
* socket.
*
* @param[in] req WebSocket request
* @return
* - ESP_OK : Control frame handled and replied
* - others : Control handler error, or frame could not be received/replied
* - ESP_OK : Control frame received and handled
* - ESP_ERR_INVALID_ARG : Argument is invalid (null request aux or session)
* - ESP_ERR_INVALID_STATE : No control handler registered for this session
* - others : Control handler error, or frame could not be received
*/
esp_err_t httpd_ws_handle_control_frame(httpd_req_t *req);
#endif /* CONFIG_HTTPD_WS_SUPPORT */
+1 -1
View File
@@ -853,7 +853,7 @@ esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
/* Dispatch the frame:
* - Control frames (CLOSE/PING/PONG) go to the dedicated control handler
* when one is registered; the server then sends the protocol reply.
* when one is registered; that handler owns the protocol reply.
* - Otherwise dispatch to the data handler for non-control frames, PONG
* frames, or when the handler opted in to receiving control frames.
* PONG must be dispatched so that user heartbeat code can track it and
+6 -11
View File
@@ -878,6 +878,9 @@ esp_err_t httpd_ws_handle_control_frame(httpd_req_t *req)
return ESP_ERR_INVALID_ARG;
}
struct sock_db *sd = aux->sd;
if (sd->ws_control_handler == NULL) {
return ESP_ERR_INVALID_STATE;
}
/* The server receives the control-frame body itself. Oversized or malformed
* frames are rejected by the max_len cap (zero-trust on client input). */
@@ -888,17 +891,9 @@ esp_err_t httpd_ws_handle_control_frame(httpd_req_t *req)
return ret;
}
/* Notify the user's control handler with a read-only view of the frame. */
esp_err_t handler_ret = ESP_OK;
if (sd->ws_control_handler != NULL) {
handler_ret = sd->ws_control_handler(req, &frame);
}
/* The server always performs the protocol reply (PONG for PING, CLOSE for
* CLOSE). If the handler failed, still reply, then propagate the error so the
* caller closes the socket. */
esp_err_t reply_ret = httpd_ws_reply_to_control_frame(req, &frame);
return (handler_ret != ESP_OK) ? handler_ret : reply_ret;
/* Hand the frame over. The server deliberately does not reply: with
* handle_ws_control_frames true the application owns the protocol reply */
return sd->ws_control_handler(req, &frame);
}
esp_err_t httpd_ws_get_frame_type(httpd_req_t *req)