mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
Merge remote-tracking branch 'origin/web_server_sse_log_len' into integration
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user