ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
web_server_idf.h
Go to the documentation of this file.
1#pragma once
2#ifdef USE_ESP32
3
7#include <esp_http_server.h>
8
9#include <atomic>
10#include <functional>
11#include <list>
12#include <map>
13#include <span>
14#include <string>
15#include <utility>
16#include <vector>
17
18#ifdef USE_WEBSERVER
21#include "sse_chunk.h"
22#endif
23
24struct iovec; // NOLINT(readability-identifier-naming) - forward decl of lwip's gather list entry
25
26namespace esphome {
27#ifdef USE_WEBSERVER
28namespace web_server {
29class WebServer;
30}; // namespace web_server
31#endif
32namespace web_server_idf {
33
35 public:
36 AsyncWebParameter(std::string name, std::string value) : name_(std::move(name)), value_(std::move(value)) {}
37 const std::string &name() const { return this->name_; }
38 const std::string &value() const { return this->value_; }
39
40 protected:
41 std::string name_;
42 std::string value_;
43};
44
46
48 public:
51
52 // NOLINTNEXTLINE(readability-identifier-naming)
53 void addHeader(const char *name, const char *value);
54
55 virtual const char *get_content_data() const = 0;
56 virtual size_t get_content_size() const = 0;
57
58 protected:
60};
61
63 public:
65
66 const char *get_content_data() const override { return nullptr; };
67 size_t get_content_size() const override { return 0; };
68};
69
71 public:
73 : AsyncWebServerResponse(req), content_(std::move(content)) {}
74
75 const char *get_content_data() const override { return this->content_.c_str(); };
76 size_t get_content_size() const override { return this->content_.size(); };
77
78 protected:
79 std::string content_;
80};
81
83 public:
85
86 const char *get_content_data() const override { return this->content_.c_str(); };
87 size_t get_content_size() const override { return this->content_.size(); };
88
89 void print(const char *str) { this->content_.append(str); }
90 void print(const std::string &str) { this->content_.append(str); }
91 void print(float value);
92 void printf(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
93 void write(uint8_t c) { this->content_.push_back(static_cast<char>(c)); }
94
95 protected:
96 std::string content_;
97};
98
100 public:
101 AsyncWebServerResponseProgmem(const AsyncWebServerRequest *req, const uint8_t *data, const size_t size)
102 : AsyncWebServerResponse(req), data_(data), size_(size) {}
103
104 const char *get_content_data() const override { return reinterpret_cast<const char *>(this->data_); };
105 size_t get_content_size() const override { return this->size_; };
106
107 protected:
108 const uint8_t *data_;
109 size_t size_;
110};
111
113 friend class AsyncWebServer;
114
115 public:
117
118 http_method method() const { return static_cast<http_method>(this->req_->method); }
119 static constexpr size_t URL_BUF_SIZE = CONFIG_HTTPD_MAX_URI_LEN + 1;
122 StringRef url_to(std::span<char, URL_BUF_SIZE> buffer) const;
123 // NOLINTNEXTLINE(readability-identifier-naming)
124 size_t contentLength() const { return this->req_->content_len; }
125
126#ifdef USE_WEBSERVER_AUTH
127 bool authenticate(const char *username, const char *password) const;
128 // NOLINTNEXTLINE(readability-identifier-naming)
129 void requestAuthentication() const;
130#endif
131
132 void redirect(const char *url);
133 void redirect(const std::string &url) { this->redirect(url.c_str()); }
134
135 inline void ESPHOME_ALWAYS_INLINE send(AsyncWebServerResponse *response) {
136 httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
137 }
138 inline void ESPHOME_ALWAYS_INLINE send(int code, const char *content_type = nullptr, const char *content = nullptr) {
139 this->init_response_(nullptr, code, content_type);
140 if (content) {
141 httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
142 } else {
143 httpd_resp_send(*this, nullptr, 0);
144 }
145 }
146 // NOLINTNEXTLINE(readability-identifier-naming)
147 AsyncWebServerResponse *beginResponse(int code, const char *content_type) {
148 auto *res = new AsyncWebServerResponseEmpty(this); // NOLINT(cppcoreguidelines-owning-memory)
149 this->init_response_(res, code, content_type);
150 return res;
151 }
152 // NOLINTNEXTLINE(readability-identifier-naming)
153 AsyncWebServerResponse *beginResponse(int code, const char *content_type, const std::string &content) {
154 auto *res = new AsyncWebServerResponseContent(this, content); // NOLINT(cppcoreguidelines-owning-memory)
155 this->init_response_(res, code, content_type);
156 return res;
157 }
158 // NOLINTNEXTLINE(readability-identifier-naming)
159 AsyncWebServerResponse *beginResponse(int code, const char *content_type, const uint8_t *data,
160 const size_t data_size) {
161 auto *res = new AsyncWebServerResponseProgmem(this, data, data_size); // NOLINT(cppcoreguidelines-owning-memory)
162 this->init_response_(res, code, content_type);
163 return res;
164 }
165 // NOLINTNEXTLINE(readability-identifier-naming)
166 AsyncResponseStream *beginResponseStream(const char *content_type) {
167 auto *res = new AsyncResponseStream(this); // NOLINT(cppcoreguidelines-owning-memory)
168 this->init_response_(res, 200, content_type);
169 return res;
170 }
171
172 // NOLINTNEXTLINE(readability-identifier-naming)
173 bool hasParam(const char *name) { return this->getParam(name) != nullptr; }
174 // NOLINTNEXTLINE(readability-identifier-naming)
175 bool hasParam(const std::string &name) { return this->getParam(name.c_str()) != nullptr; }
176 // NOLINTNEXTLINE(readability-identifier-naming)
177 AsyncWebParameter *getParam(const char *name);
178 // NOLINTNEXTLINE(readability-identifier-naming)
179 AsyncWebParameter *getParam(const std::string &name) { return this->getParam(name.c_str()); }
180
181 // NOLINTNEXTLINE(readability-identifier-naming)
182 bool hasArg(const char *name);
183 std::string arg(const char *name);
184 std::string arg(const std::string &name) { return this->arg(name.c_str()); }
185
186#ifdef USE_WEBSERVER_OTA
188 // NOLINTNEXTLINE(readability-identifier-naming)
189 void onDisconnect(std::function<void()> fn) { this->on_disconnect_ = std::move(fn); }
190#endif
191
192 operator httpd_req_t *() const { return this->req_; }
193 optional<std::string> get_header(const char *name) const;
194 // NOLINTNEXTLINE(readability-identifier-naming)
195 bool hasHeader(const char *name) const;
196
197 protected:
198 httpd_req_t *req_;
200 // Use vector instead of map/unordered_map: most requests have 0-3 params, so linear search
201 // is faster than tree/hash overhead. AsyncWebParameter stores both name and value to avoid
202 // duplicate storage. Only successful lookups are cached to prevent cache pollution when
203 // handlers check for optional parameters that don't exist.
204 optional<std::string> find_query_value_(const char *name) const;
205 std::vector<AsyncWebParameter *> params_;
206 std::string post_query_;
207#ifdef USE_WEBSERVER_OTA
208 std::function<void()> on_disconnect_;
209#endif
210 AsyncWebServerRequest(httpd_req_t *req) : req_(req) {}
211 AsyncWebServerRequest(httpd_req_t *req, std::string post_query) : req_(req), post_query_(std::move(post_query)) {}
212 void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type);
213};
214
215class AsyncWebHandler;
216
218 public:
219 AsyncWebServer(uint16_t port) : port_(port) {};
220 ~AsyncWebServer() { this->end(); }
221
222 // NOLINTNEXTLINE(readability-identifier-naming)
223 void onNotFound(std::function<void(AsyncWebServerRequest *request)> &&fn) { on_not_found_ = std::move(fn); }
224
225 void begin();
226 void end();
227
228 // NOLINTNEXTLINE(readability-identifier-naming)
230 this->handlers_.push_back(handler);
231 return *handler;
232 }
233
234 httpd_handle_t get_server() { return this->server_; }
235
236 protected:
237 uint16_t port_{};
238 httpd_handle_t server_{};
239 static esp_err_t request_handler(httpd_req_t *r);
240 static esp_err_t request_post_handler(httpd_req_t *r);
241 esp_err_t request_handler_(AsyncWebServerRequest *request) const;
242 static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd);
243 esp_err_t handle_raw_body_(httpd_req_t *r, const char *content_type);
244#ifdef USE_WEBSERVER_OTA
245 esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
246#endif
247 std::vector<AsyncWebHandler *> handlers_;
248 std::function<void(AsyncWebServerRequest *request)> on_not_found_{};
249};
250
252 public:
253 virtual ~AsyncWebHandler() {}
254 // NOLINTNEXTLINE(readability-identifier-naming)
255 virtual bool canHandle(AsyncWebServerRequest *request) const { return false; }
256 // NOLINTNEXTLINE(readability-identifier-naming)
257 virtual void handleRequest(AsyncWebServerRequest *request) {}
258 // NOLINTNEXTLINE(readability-identifier-naming)
259 virtual void handleUpload(AsyncWebServerRequest *request, const std::string &filename, size_t index, uint8_t *data,
260 size_t len, bool final) {}
261 // NOLINTNEXTLINE(readability-identifier-naming)
262 virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {}
263 // NOLINTNEXTLINE(readability-identifier-naming)
264 virtual bool isRequestHandlerTrivial() const { return true; }
265};
266
267#ifdef USE_WEBSERVER
268class AsyncEventSource;
270
272
273/*
274 This class holds a pointer to the source component that wants to publish a state event, and a pointer to a function
275 that will lazily generate that event. The two pointers allow dedup in the deferred queue if multiple publishes for
276 the same component are backed up, and take up only two pointers of memory. The entry in the deferred queue (a
277 std::vector) is the DeferredEvent instance itself (not a pointer to one elsewhere in heap) so still only two pointers
278 per entry (and no heap fragmentation). Even 100 backed up events (you'd have to have at least 100 sensors publishing
279 because of dedup) would take up only 0.8 kB.
280*/
283
284 protected:
285 void *source_;
287
288 public:
289 DeferredEvent(void *source, message_generator_t *message_generator)
290 : source_(source), message_generator_(message_generator) {}
291 bool operator==(const DeferredEvent &test) const {
292 return (source_ == test.source_ && message_generator_ == test.message_generator_);
293 }
294};
295static_assert(sizeof(DeferredEvent) == sizeof(void *) + sizeof(message_generator_t *),
296 "DeferredEvent should have no padding");
297
299 friend class AsyncEventSource;
300
301 public:
302 bool try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
303 uint32_t reconnect = 0);
304 // detail_all events go out while entities are still being listed; state events wait until listing is done
305 void deferrable_send_state(void *source, bool detail_all, message_generator_t *message_generator);
306 // The old event name argument would otherwise convert to true
307 void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator) = delete;
308 void loop();
309
310 protected:
313
314 // Main-loop only: sends initial ping/config/sorting_groups, starts entity iterator.
316
317 void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator);
319 // A new chunk may go out: not re-entered from a log line, session alive, tail empty
320 bool ready_to_send_();
321 // Non-blocking gather write. Returns bytes written, 0 on would-block or a socket that is not
322 // ours, -1 after requesting the close on any other error.
323 ssize_t send_(struct iovec *iov, int iovcnt);
324 // Push what is left of the chunk in tail_ to the socket; owns the stall timer.
325 void drain_tail_();
326 // Grow tail_ to hold len bytes, kept at its high-water mark. False on OOM.
327 bool reserve_tail_(size_t len);
328 // Keep the whole chunk in tail_ and continue from sent; false when the tail cannot be allocated.
329 bool stash_chunk_(const char *prefix, size_t prefix_len, const char *message, size_t message_len, size_t total,
330 size_t sent);
331 // Send a state event; JSON too large for the stack buffer is serialized into tail_ instead
332 bool send_json_(void *source, message_generator_t *generator);
339 // Keep the arena out of the caller's frame so it is gone before the state send.
340 // size_out: BUFFER = JSON length, NO_MEMORY = requested tail capacity, TOO_LARGE = available JSON room;
341 // unused for TAIL.
342 JsonPrepareResult __attribute__((noinline)) prepare_json_(void *source, message_generator_t *generator, char *buffer,
343 size_t *size_out);
344 // Warn once, and close the session once the stall timeout passes with no memory for the tail
345 void tail_alloc_failed_(size_t cap);
346 void request_close_();
347
348 // A log line emitted inside a send re-enters try_send_nodefer on this session; refuse it
354 void process_close_();
355 static void close_session_work(void *arg);
356 void refresh_lru_();
357 static void refresh_lru_work(void *arg);
358 // httpd frees a session before closing its socket, so the fd may already belong to a new client
359 bool owns_session_(int fd) const { return fd != 0 && httpd_sess_get_ctx(this->hd_, fd) == this; }
360
361 // Deletable only after destroy() zeroed fd_ and no queued HTTPD work still references this object.
362 bool safe_to_delete_() const {
363 return this->fd_.load() == 0 && !this->close_work_queued_.load(std::memory_order_acquire) &&
364 !this->lru_work_queued_.load(std::memory_order_acquire);
365 }
366
367 static void destroy(void *p);
369 httpd_handle_t hd_{};
370 std::atomic<int> fd_{};
371 std::vector<DeferredEvent> deferred_queue_;
374 // One chunk the socket did not take whole, allocated on the first stall; the only heap use
375 // on the send path
377 uint32_t send_failure_started_ms_{0}; // Zero means no send stall in progress.
380 uint16_t tail_cap_{0};
381 uint16_t tail_len_{0}; // Zero means nothing pending
382 uint16_t tail_sent_{0};
383 // Set on the main loop before queueing close work, cleared by the HTTPD-task callback when done.
384 std::atomic<bool> close_work_queued_{false};
385 std::atomic<bool> lru_work_queued_{false};
386 // Main-loop only; the HTTPD task never reads or writes these flags.
387 bool close_requested_{false};
389 bool sending_{false};
390 // The longest multi line log message in the tree (a climate dump_config) has 22 lines; a
391 // longer one goes through the tail
392 static constexpr size_t MAX_SEND_LINES = 22;
393 static constexpr size_t MAX_SEND_IOV = 1 + 2 * MAX_SEND_LINES;
394 // Chunk header, retry/id/event lines and the first "data: "
395 static constexpr size_t PREFIX_BUF_SIZE = 128;
396
397 // Stack buffer for a state event's JSON; a larger document is serialized into the tail
398 static constexpr size_t JSON_BUF_SIZE = 1024;
399 // Same ceiling JsonBuilder::serialize() applies (max_heap_size in json_util.cpp); a larger
400 // document is dropped before anything is on the wire
401 static constexpr size_t JSON_MAX_SIZE = 5120;
402 // Most RAM a stalled session keeps: the largest state document plus any accepted framing
404 static constexpr uint32_t SEND_STALL_TIMEOUT_MS = 20000;
405 static constexpr uint32_t CLOSE_RETRY_INTERVAL_MS = 250;
406 static constexpr uint32_t CLOSE_CONFIRM_INTERVAL_MS = 1000;
407 static constexpr uint32_t LRU_REFRESH_INTERVAL_MS = 1000;
408};
409
411
414
415 public:
417 ~AsyncEventSource() override;
418
419 // NOLINTNEXTLINE(readability-identifier-naming)
420 bool canHandle(AsyncWebServerRequest *request) const override {
421 if (request->method() != HTTP_GET)
422 return false;
424 return request->url_to(url_buf) == this->url_;
425 }
426 // NOLINTNEXTLINE(readability-identifier-naming)
427 void handleRequest(AsyncWebServerRequest *request) override;
428 void try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
429 uint32_t reconnect = 0);
430 void deferrable_send_state(void *source, bool detail_all, message_generator_t *message_generator);
431 // The old event name argument would otherwise convert to true
432 void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator) = delete;
434 bool loop();
435 bool empty() { return this->count() == 0; }
436
437 size_t count() const { return this->sessions_.size(); }
438
439 protected:
440 // Cold path: move sessions from pending_sessions_ into sessions_ and greet each one.
441 void __attribute__((noinline, cold)) adopt_pending_sessions_main_loop_();
442
443 StringRef url_; // Must outlive this object (string literal)
444 // Main-loop only. Vector: SSE sessions are 1-5 connections, linear search beats set.
445 std::vector<AsyncEventSourceResponse *> sessions_;
446 // Httpd-task intake; guarded by pending_mutex_, gated by has_pending_sessions_.
447 std::vector<AsyncEventSourceResponse *> pending_sessions_;
450 std::atomic<bool> has_pending_sessions_{false};
451};
452#endif // USE_WEBSERVER
453
455 const char *name;
456 const char *value;
457};
458
461#ifdef USE_WEBSERVER
463#endif
464
465 public:
466 // NOLINTNEXTLINE(readability-identifier-naming)
467 void addHeader(const char *name, const char *value) { this->headers_.push_back({name, value}); }
468
469 // NOLINTNEXTLINE(readability-identifier-naming)
470 static DefaultHeaders &Instance();
471
472 protected:
473 // Stack-allocated, no reallocation machinery. Count defined in web_server_base where headers are added.
475};
476
477} // namespace web_server_idf
478} // namespace esphome
479
480using namespace esphome::web_server_idf; // NOLINT(google-global-names-in-headers)
481
482#endif // !defined(USE_ESP32)
Mutex implementation, with API based on the unavailable std::mutex.
Definition helpers.h:2096
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:286
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
Builder class for creating JSON documents without lambdas.
Definition json_util.h:256
This class allows users to create a web server with their ESP nodes.
Definition web_server.h:201
std::vector< AsyncEventSourceResponse * > pending_sessions_
bool loop()
Returns true if there are sessions remaining (including pending cleanup).
std::vector< AsyncEventSourceResponse * > sessions_
void try_send_nodefer(const char *message, size_t message_len, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
esphome::web_server::WebServer * web_server_
bool canHandle(AsyncWebServerRequest *request) const override
AsyncEventSource(StringRef url, esphome::web_server::WebServer *ws)
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)=delete
void handleRequest(AsyncWebServerRequest *request) override
void __attribute__((noinline, cold)) adopt_pending_sessions_main_loop_()
void deferrable_send_state(void *source, bool detail_all, message_generator_t *message_generator)
void deferrable_send_state(void *source, bool detail_all, message_generator_t *message_generator)
bool try_send_nodefer(const char *message, size_t message_len, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
JsonPrepareResult message_generator_t char * buffer
JsonPrepareResult __attribute__((noinline)) prepare_json_(void *source
esphome::web_server::WebServer * web_server_
void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator)
AsyncEventSourceResponse(const AsyncWebServerRequest *request, esphome::web_server_idf::AsyncEventSource *server, esphome::web_server::WebServer *ws)
JsonPrepareResult message_generator_t * generator
JsonPrepareResult message_generator_t char size_t * size_out
ssize_t send_(struct iovec *iov, int iovcnt)
bool stash_chunk_(const char *prefix, size_t prefix_len, const char *message, size_t message_len, size_t total, size_t sent)
esphome::web_server::ListEntitiesIterator entities_iterator_
bool send_json_(void *source, message_generator_t *generator)
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)=delete
AsyncResponseStream(const AsyncWebServerRequest *req)
void printf(const char *fmt,...) __attribute__((format(printf
const char * get_content_data() const override
virtual bool canHandle(AsyncWebServerRequest *request) const
virtual void handleRequest(AsyncWebServerRequest *request)
virtual void handleUpload(AsyncWebServerRequest *request, const std::string &filename, size_t index, uint8_t *data, size_t len, bool final)
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
AsyncWebParameter(std::string name, std::string value)
std::function< void(AsyncWebServerRequest *request)> on_not_found_
esp_err_t handle_raw_body_(httpd_req_t *r, const char *content_type)
static esp_err_t request_post_handler(httpd_req_t *r)
std::vector< AsyncWebHandler * > handlers_
AsyncWebHandler & addHandler(AsyncWebHandler *handler)
esp_err_t request_handler_(AsyncWebServerRequest *request) const
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type)
static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd)
static esp_err_t request_handler(httpd_req_t *r)
void onNotFound(std::function< void(AsyncWebServerRequest *request)> &&fn)
AsyncWebParameter * getParam(const std::string &name)
AsyncWebServerResponse * beginResponse(int code, const char *content_type, const uint8_t *data, const size_t data_size)
AsyncWebParameter * getParam(const char *name)
optional< std::string > get_header(const char *name) const
void ESPHOME_ALWAYS_INLINE send(int code, const char *content_type=nullptr, const char *content=nullptr)
void onDisconnect(std::function< void()> fn)
Called when the request ends, including early returns.
StringRef url_to(std::span< char, URL_BUF_SIZE > buffer) const
Write URL (without query string) to buffer, returns StringRef pointing to buffer.
AsyncWebServerResponse * beginResponse(int code, const char *content_type, const std::string &content)
std::string arg(const std::string &name)
AsyncWebServerResponse * beginResponse(int code, const char *content_type)
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type)
static constexpr size_t URL_BUF_SIZE
Buffer size for url_to()
optional< std::string > find_query_value_(const char *name) const
void ESPHOME_ALWAYS_INLINE send(AsyncWebServerResponse *response)
AsyncResponseStream * beginResponseStream(const char *content_type)
bool authenticate(const char *username, const char *password) const
AsyncWebServerRequest(httpd_req_t *req, std::string post_query)
std::vector< AsyncWebParameter * > params_
AsyncWebServerResponseContent(const AsyncWebServerRequest *req, std::string content)
AsyncWebServerResponseEmpty(const AsyncWebServerRequest *req)
virtual const char * get_content_data() const =0
AsyncWebServerResponse(const AsyncWebServerRequest *req)
void addHeader(const char *name, const char *value)
AsyncWebServerResponseProgmem(const AsyncWebServerRequest *req, const uint8_t *data, const size_t size)
void addHeader(const char *name, const char *value)
StaticVector< HttpHeader, WEB_SERVER_DEFAULT_HEADERS_COUNT > headers_
const LogString * message
Definition component.cpp:35
uint32_t buffer
Definition gif.cpp:50
__int64 ssize_t
Definition httplib.h:178
const char * format
void(esphome::web_server::WebServer *, void *, json::JsonBuilder &) message_generator_t
constexpr size_t SSE_SUFFIX_LEN
Definition sse_chunk.h:23
std::unique_ptr< T, RAMDeleter< T > > RAMUniquePtr
unique_ptr over RAMAllocator storage
Definition helpers.h:2287
const void size_t len
Definition hal.h:68
uint16_t size
Definition helpers.cpp:34
size_t size_t const char * fmt
Definition helpers.h:1256
STL namespace.
static void uint32_t
bool operator==(const DeferredEvent &test) const
DeferredEvent(void *source, message_generator_t *message_generator)
std::string print()