Merge remote-tracking branch 'origin/web_server_sse_log_len' into integration

This commit is contained in:
J. Nick Koston
2026-07-05 23:01:19 -05:00
4 changed files with 35 additions and 30 deletions
+12 -11
View File
@@ -257,8 +257,10 @@ void DeferredUpdateEventSource::deferrable_send_state(void *source, const char *
}
// used for logs plus the initial ping/config
void DeferredUpdateEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id,
uint32_t reconnect) {
void DeferredUpdateEventSource::try_send_nodefer(const char *message, size_t message_len, const char *event,
uint32_t id, uint32_t reconnect) {
// ESPAsyncWebServer's send() only accepts null-terminated strings
(void) message_len;
this->send(message, event, id, reconnect);
}
@@ -279,10 +281,10 @@ void DeferredUpdateEventSourceList::deferrable_send_state(void *source, const ch
}
}
void DeferredUpdateEventSourceList::try_send_nodefer(const char *message, const char *event, uint32_t id,
uint32_t reconnect) {
void DeferredUpdateEventSourceList::try_send_nodefer(const char *message, size_t message_len, const char *event,
uint32_t id, uint32_t reconnect) {
for (DeferredUpdateEventSource *dues : *this) {
dues->try_send_nodefer(message, event, id, reconnect);
dues->try_send_nodefer(message, message_len, event, id, reconnect);
}
}
@@ -304,7 +306,7 @@ void DeferredUpdateEventSourceList::on_client_connect_(DeferredUpdateEventSource
// Configure reconnect timeout and send config
// this should always go through since the AsyncEventSourceClient event queue is empty on connect
auto message = ws->get_config_json();
source->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
source->try_send_nodefer(message.c_str(), message.size(), "ping", millis(), 30000);
#ifdef USE_WEBSERVER_SORTING
for (auto &group : ws->sorting_groups_) {
@@ -315,7 +317,7 @@ void DeferredUpdateEventSourceList::on_client_connect_(DeferredUpdateEventSource
auto group_msg = builder.serialize();
// up to 31 groups should be able to be queued initially without defer
source->try_send_nodefer(group_msg.c_str(), "sorting_group");
source->try_send_nodefer(group_msg.c_str(), group_msg.size(), "sorting_group");
}
#endif
@@ -395,8 +397,8 @@ void WebServer::setup() {
return;
char buf[32];
auto uptime = static_cast<uint32_t>(millis_64() / 1000);
buf_append_printf(buf, sizeof(buf), 0, "{\"uptime\":%" PRIu32 "}", uptime);
this->events_.try_send_nodefer(buf, "ping", millis(), 30000);
size_t len = buf_append_printf(buf, sizeof(buf), 0, "{\"uptime\":%" PRIu32 "}", uptime);
this->events_.try_send_nodefer(buf, len, "ping", millis(), 30000);
});
}
void WebServer::loop() {
@@ -414,8 +416,7 @@ void WebServer::loop() {
void WebServer::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
(void) level;
(void) tag;
(void) message_len;
this->events_.try_send_nodefer(message, "log", millis());
this->events_.try_send_nodefer(message, message_len, "log", millis());
}
#endif
+4 -2
View File
@@ -160,7 +160,8 @@ class DeferredUpdateEventSource final : public AsyncEventSource {
void loop();
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
void try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
uint32_t reconnect = 0);
};
class DeferredUpdateEventSourceList final : public std::list<DeferredUpdateEventSource *> {
@@ -173,7 +174,8 @@ class DeferredUpdateEventSourceList final : public std::list<DeferredUpdateEvent
bool loop();
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
void try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
uint32_t reconnect = 0);
void add_new_client(WebServer *ws, AsyncWebServerRequest *request);
};
@@ -546,10 +546,11 @@ void AsyncEventSource::adopt_pending_sessions_main_loop_() {
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
void AsyncEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
void AsyncEventSource::try_send_nodefer(const char *message, size_t message_len, const char *event, uint32_t id,
uint32_t reconnect) {
for (auto *ses : this->sessions_) {
if (ses->fd_.load() != 0) { // Skip dead sessions
ses->try_send_nodefer(message, event, id, reconnect);
ses->try_send_nodefer(message, message_len, event, id, reconnect);
}
}
}
@@ -600,7 +601,7 @@ void AsyncEventSourceResponse::start_session_main_loop_() {
// tcp send buffer is empty on connect, so these should always go through
auto message = ws->get_config_json();
this->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
this->try_send_nodefer(message.c_str(), message.size(), "ping", millis(), 30000);
#ifdef USE_WEBSERVER_SORTING
for (auto &group : ws->sorting_groups_) {
@@ -612,7 +613,7 @@ void AsyncEventSourceResponse::start_session_main_loop_() {
// a (very) large number of these should be able to be queued initially without defer
// since the only thing in the send buffer at this point is the initial ping/config
this->try_send_nodefer(message.c_str(), "sorting_group");
this->try_send_nodefer(message.c_str(), message.size(), "sorting_group");
}
#endif
@@ -647,7 +648,7 @@ void AsyncEventSourceResponse::process_deferred_queue_() {
while (!deferred_queue_.empty()) {
DeferredEvent &de = deferred_queue_.front();
auto message = de.message_generator_(web_server_, de.source_);
if (this->try_send_nodefer(message.c_str(), "state")) {
if (this->try_send_nodefer(message.c_str(), message.size(), "state")) {
// O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
deferred_queue_.erase(deferred_queue_.begin());
} else {
@@ -718,7 +719,7 @@ void AsyncEventSourceResponse::loop() {
this->entities_iterator_.advance();
}
bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char *event, uint32_t id,
bool AsyncEventSourceResponse::try_send_nodefer(const char *message, size_t message_len, const char *event, uint32_t id,
uint32_t reconnect) {
if (this->fd_.load() == 0) {
return false;
@@ -764,19 +765,18 @@ bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char
// Fast path: check if message contains any newlines at all
// Most SSE messages (JSON state updates) have no newlines
const char *first_n = strchr(message, '\n');
const char *first_r = strchr(message, '\r');
const char *first_n = static_cast<const char *>(memchr(message, '\n', message_len));
const char *first_r = static_cast<const char *>(memchr(message, '\r', message_len));
if (first_n == nullptr && first_r == nullptr) {
// No newlines - fast path (most common case)
event_buffer_.append("data: ", sizeof("data: ") - 1);
event_buffer_.append(message);
event_buffer_.append(message, message_len);
event_buffer_.append(CRLF_STR CRLF_STR, CRLF_LEN * 2); // data line + blank line terminator
} else {
// Has newlines - handle multi-line message
const char *line_start = message;
size_t msg_len = strlen(message);
const char *msg_end = message + msg_len;
const char *msg_end = message + message_len;
// Reuse the first search results
const char *next_n = first_n;
@@ -789,7 +789,7 @@ bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char
if (next_n == nullptr && next_r == nullptr) {
// No more line breaks - output remaining text as final line
event_buffer_.append("data: ", sizeof("data: ") - 1);
event_buffer_.append(line_start);
event_buffer_.append(line_start, msg_end - line_start);
event_buffer_.append(CRLF_STR, CRLF_LEN);
break;
}
@@ -828,8 +828,8 @@ bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char
}
// Search for next newlines only in remaining string
next_n = strchr(line_start, '\n');
next_r = strchr(line_start, '\r');
next_n = static_cast<const char *>(memchr(line_start, '\n', msg_end - line_start));
next_r = static_cast<const char *>(memchr(line_start, '\r', msg_end - line_start));
}
// Terminate message with blank line
@@ -884,7 +884,7 @@ void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *e
deq_push_back_with_dedup_(source, message_generator);
} else {
auto message = message_generator(web_server_, source);
if (!this->try_send_nodefer(message.c_str(), "state")) {
if (!this->try_send_nodefer(message.c_str(), message.size(), "state")) {
deq_push_back_with_dedup_(source, message_generator);
}
}
@@ -291,7 +291,8 @@ class AsyncEventSourceResponse {
friend class AsyncEventSource;
public:
bool try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
bool try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
uint32_t reconnect = 0);
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
void loop();
@@ -343,7 +344,8 @@ class AsyncEventSource : public AsyncWebHandler {
// NOLINTNEXTLINE(readability-identifier-naming)
void onConnect(connect_handler_t &&cb) { this->on_connect_ = std::move(cb); }
void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
void try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
uint32_t reconnect = 0);
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
/// Returns true if there are sessions remaining (including pending cleanup).
bool loop();