ESPHome 2026.10.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#endif
22
23namespace esphome {
24#ifdef USE_WEBSERVER
25namespace web_server {
26class WebServer;
27}; // namespace web_server
28#endif
29namespace web_server_idf {
30
32 public:
33 AsyncWebParameter(std::string name, std::string value) : name_(std::move(name)), value_(std::move(value)) {}
34 const std::string &name() const { return this->name_; }
35 const std::string &value() const { return this->value_; }
36
37 protected:
38 std::string name_;
39 std::string value_;
40};
41
43
45 public:
48
49 // NOLINTNEXTLINE(readability-identifier-naming)
50 void addHeader(const char *name, const char *value);
51
52 virtual const char *get_content_data() const = 0;
53 virtual size_t get_content_size() const = 0;
54
55 protected:
57};
58
60 public:
62
63 const char *get_content_data() const override { return nullptr; };
64 size_t get_content_size() const override { return 0; };
65};
66
68 public:
70 : AsyncWebServerResponse(req), content_(std::move(content)) {}
71
72 const char *get_content_data() const override { return this->content_.c_str(); };
73 size_t get_content_size() const override { return this->content_.size(); };
74
75 protected:
76 std::string content_;
77};
78
80 public:
82
83 const char *get_content_data() const override { return this->content_.c_str(); };
84 size_t get_content_size() const override { return this->content_.size(); };
85
86 void print(const char *str) { this->content_.append(str); }
87 void print(const std::string &str) { this->content_.append(str); }
88 void print(float value);
89 void printf(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
90 void write(uint8_t c) { this->content_.push_back(static_cast<char>(c)); }
91
92 protected:
93 std::string content_;
94};
95
97 public:
98 AsyncWebServerResponseProgmem(const AsyncWebServerRequest *req, const uint8_t *data, const size_t size)
99 : AsyncWebServerResponse(req), data_(data), size_(size) {}
100
101 const char *get_content_data() const override { return reinterpret_cast<const char *>(this->data_); };
102 size_t get_content_size() const override { return this->size_; };
103
104 protected:
105 const uint8_t *data_;
106 size_t size_;
107};
108
110 friend class AsyncWebServer;
111
112 public:
114
115 http_method method() const { return static_cast<http_method>(this->req_->method); }
116 static constexpr size_t URL_BUF_SIZE = CONFIG_HTTPD_MAX_URI_LEN + 1;
119 StringRef url_to(std::span<char, URL_BUF_SIZE> buffer) const;
120 // NOLINTNEXTLINE(readability-identifier-naming)
121 size_t contentLength() const { return this->req_->content_len; }
122
123#ifdef USE_WEBSERVER_AUTH
124 bool authenticate(const char *username, const char *password) const;
125 // NOLINTNEXTLINE(readability-identifier-naming)
126 void requestAuthentication() const;
127#endif
128
129 void redirect(const std::string &url);
130
131 inline void ESPHOME_ALWAYS_INLINE send(AsyncWebServerResponse *response) {
132 httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
133 }
134 inline void ESPHOME_ALWAYS_INLINE send(int code, const char *content_type = nullptr, const char *content = nullptr) {
135 this->init_response_(nullptr, code, content_type);
136 if (content) {
137 httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
138 } else {
139 httpd_resp_send(*this, nullptr, 0);
140 }
141 }
142 // NOLINTNEXTLINE(readability-identifier-naming)
143 AsyncWebServerResponse *beginResponse(int code, const char *content_type) {
144 auto *res = new AsyncWebServerResponseEmpty(this); // NOLINT(cppcoreguidelines-owning-memory)
145 this->init_response_(res, code, content_type);
146 return res;
147 }
148 // NOLINTNEXTLINE(readability-identifier-naming)
149 AsyncWebServerResponse *beginResponse(int code, const char *content_type, const std::string &content) {
150 auto *res = new AsyncWebServerResponseContent(this, content); // NOLINT(cppcoreguidelines-owning-memory)
151 this->init_response_(res, code, content_type);
152 return res;
153 }
154 // NOLINTNEXTLINE(readability-identifier-naming)
155 AsyncWebServerResponse *beginResponse(int code, const char *content_type, const uint8_t *data,
156 const size_t data_size) {
157 auto *res = new AsyncWebServerResponseProgmem(this, data, data_size); // NOLINT(cppcoreguidelines-owning-memory)
158 this->init_response_(res, code, content_type);
159 return res;
160 }
161 // NOLINTNEXTLINE(readability-identifier-naming)
162 AsyncResponseStream *beginResponseStream(const char *content_type) {
163 auto *res = new AsyncResponseStream(this); // NOLINT(cppcoreguidelines-owning-memory)
164 this->init_response_(res, 200, content_type);
165 return res;
166 }
167
168 // NOLINTNEXTLINE(readability-identifier-naming)
169 bool hasParam(const char *name) { return this->getParam(name) != nullptr; }
170 // NOLINTNEXTLINE(readability-identifier-naming)
171 bool hasParam(const std::string &name) { return this->getParam(name.c_str()) != nullptr; }
172 // NOLINTNEXTLINE(readability-identifier-naming)
173 AsyncWebParameter *getParam(const char *name);
174 // NOLINTNEXTLINE(readability-identifier-naming)
175 AsyncWebParameter *getParam(const std::string &name) { return this->getParam(name.c_str()); }
176
177 // NOLINTNEXTLINE(readability-identifier-naming)
178 bool hasArg(const char *name);
179 std::string arg(const char *name);
180 std::string arg(const std::string &name) { return this->arg(name.c_str()); }
181
182 operator httpd_req_t *() const { return this->req_; }
183 optional<std::string> get_header(const char *name) const;
184 // NOLINTNEXTLINE(readability-identifier-naming)
185 bool hasHeader(const char *name) const;
186
187 protected:
188 httpd_req_t *req_;
190 // Use vector instead of map/unordered_map: most requests have 0-3 params, so linear search
191 // is faster than tree/hash overhead. AsyncWebParameter stores both name and value to avoid
192 // duplicate storage. Only successful lookups are cached to prevent cache pollution when
193 // handlers check for optional parameters that don't exist.
194 optional<std::string> find_query_value_(const char *name) const;
195 std::vector<AsyncWebParameter *> params_;
196 std::string post_query_;
197 AsyncWebServerRequest(httpd_req_t *req) : req_(req) {}
198 AsyncWebServerRequest(httpd_req_t *req, std::string post_query) : req_(req), post_query_(std::move(post_query)) {}
199 void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type);
200};
201
202class AsyncWebHandler;
203
205 public:
206 AsyncWebServer(uint16_t port) : port_(port){};
207 ~AsyncWebServer() { this->end(); }
208
209 // NOLINTNEXTLINE(readability-identifier-naming)
210 void onNotFound(std::function<void(AsyncWebServerRequest *request)> &&fn) { on_not_found_ = std::move(fn); }
211
212 void begin();
213 void end();
214
215 // NOLINTNEXTLINE(readability-identifier-naming)
217 this->handlers_.push_back(handler);
218 return *handler;
219 }
220
221 httpd_handle_t get_server() { return this->server_; }
222
223 protected:
224 uint16_t port_{};
225 httpd_handle_t server_{};
226 static esp_err_t request_handler(httpd_req_t *r);
227 static esp_err_t request_post_handler(httpd_req_t *r);
228 esp_err_t request_handler_(AsyncWebServerRequest *request) const;
229 static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd);
230 esp_err_t handle_raw_body_(httpd_req_t *r, const char *content_type);
231#ifdef USE_WEBSERVER_OTA
232 esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
233#endif
234 std::vector<AsyncWebHandler *> handlers_;
235 std::function<void(AsyncWebServerRequest *request)> on_not_found_{};
236};
237
239 public:
240 virtual ~AsyncWebHandler() {}
241 // NOLINTNEXTLINE(readability-identifier-naming)
242 virtual bool canHandle(AsyncWebServerRequest *request) const { return false; }
243 // NOLINTNEXTLINE(readability-identifier-naming)
244 virtual void handleRequest(AsyncWebServerRequest *request) {}
245 // NOLINTNEXTLINE(readability-identifier-naming)
246 virtual void handleUpload(AsyncWebServerRequest *request, const std::string &filename, size_t index, uint8_t *data,
247 size_t len, bool final) {}
248 // NOLINTNEXTLINE(readability-identifier-naming)
249 virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {}
250 // NOLINTNEXTLINE(readability-identifier-naming)
251 virtual bool isRequestHandlerTrivial() const { return true; }
252};
253
254#ifdef USE_WEBSERVER
255class AsyncEventSource;
257
259
260/*
261 This class holds a pointer to the source component that wants to publish a state event, and a pointer to a function
262 that will lazily generate that event. The two pointers allow dedup in the deferred queue if multiple publishes for
263 the same component are backed up, and take up only two pointers of memory. The entry in the deferred queue (a
264 std::vector) is the DeferredEvent instance itself (not a pointer to one elsewhere in heap) so still only two pointers
265 per entry (and no heap fragmentation). Even 100 backed up events (you'd have to have at least 100 sensors publishing
266 because of dedup) would take up only 0.8 kB.
267*/
270
271 protected:
272 void *source_;
274
275 public:
276 DeferredEvent(void *source, message_generator_t *message_generator)
277 : source_(source), message_generator_(message_generator) {}
278 bool operator==(const DeferredEvent &test) const {
279 return (source_ == test.source_ && message_generator_ == test.message_generator_);
280 }
281};
282static_assert(sizeof(DeferredEvent) == sizeof(void *) + sizeof(message_generator_t *),
283 "DeferredEvent should have no padding");
284
286 friend class AsyncEventSource;
287
288 public:
289 bool try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
290 uint32_t reconnect = 0);
291 void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
292 void loop();
293
294 protected:
297
298 // Main-loop only: sends initial ping/config/sorting_groups, starts entity iterator.
300
301 void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator);
303 void process_buffer_();
304
305 static void destroy(void *p);
307 httpd_handle_t hd_{};
308 std::atomic<int> fd_{};
309 std::vector<DeferredEvent> deferred_queue_;
312 std::string event_buffer_;
315 static constexpr uint16_t MAX_CONSECUTIVE_SEND_FAILURES = 2500; // ~20 seconds at 125Hz loop rate
316};
317
319
322 using connect_handler_t = std::function<void(AsyncEventSourceClient *)>;
323
324 public:
325 AsyncEventSource(std::string url, esphome::web_server::WebServer *ws) : url_(std::move(url)), web_server_(ws) {}
326 ~AsyncEventSource() override;
327
328 // NOLINTNEXTLINE(readability-identifier-naming)
329 bool canHandle(AsyncWebServerRequest *request) const override {
330 if (request->method() != HTTP_GET)
331 return false;
333 return request->url_to(url_buf) == this->url_;
334 }
335 // NOLINTNEXTLINE(readability-identifier-naming)
336 void handleRequest(AsyncWebServerRequest *request) override;
337 // Callback runs on the main loop (not the httpd task) after the session's
338 // initial ping/config/sorting_groups have been sent.
339 // NOLINTNEXTLINE(readability-identifier-naming)
340 void onConnect(connect_handler_t &&cb) { this->on_connect_ = std::move(cb); }
341
342 void try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0,
343 uint32_t reconnect = 0);
344 void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
346 bool loop();
347 bool empty() { return this->count() == 0; }
348
349 size_t count() const { return this->sessions_.size(); }
350
351 protected:
352 // Cold path: move sessions from pending_sessions_ into sessions_ and greet each one.
353 void __attribute__((noinline, cold)) adopt_pending_sessions_main_loop_();
354
355 std::string url_;
356 // Main-loop only. Vector: SSE sessions are 1-5 connections, linear search beats set.
357 std::vector<AsyncEventSourceResponse *> sessions_;
358 // Httpd-task intake; guarded by pending_mutex_, gated by has_pending_sessions_.
359 std::vector<AsyncEventSourceResponse *> pending_sessions_;
361 connect_handler_t on_connect_{};
363 std::atomic<bool> has_pending_sessions_{false};
364};
365#endif // USE_WEBSERVER
366
368 const char *name;
369 const char *value;
370};
371
374#ifdef USE_WEBSERVER
376#endif
377
378 public:
379 // NOLINTNEXTLINE(readability-identifier-naming)
380 void addHeader(const char *name, const char *value) { this->headers_.push_back({name, value}); }
381
382 // NOLINTNEXTLINE(readability-identifier-naming)
383 static DefaultHeaders &Instance();
384
385 protected:
386 // Stack-allocated, no reallocation machinery. Count defined in web_server_base where headers are added.
388};
389
390} // namespace web_server_idf
391} // namespace esphome
392
393using namespace esphome::web_server_idf; // NOLINT(google-global-names-in-headers)
394
395#endif // !defined(USE_ESP32)
Mutex implementation, with API based on the unavailable std::mutex.
Definition helpers.h:1933
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:227
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
Buffer for JSON serialization that uses stack allocation for small payloads.
Definition json_util.h:21
This class allows users to create a web server with their ESP nodes.
Definition web_server.h:193
std::vector< AsyncEventSourceResponse * > pending_sessions_
AsyncEventSource(std::string url, esphome::web_server::WebServer *ws)
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)
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
esphome::web_server::WebServer * web_server_
bool canHandle(AsyncWebServerRequest *request) const override
void onConnect(connect_handler_t &&cb)
void handleRequest(AsyncWebServerRequest *request) override
void __attribute__((noinline, cold)) adopt_pending_sessions_main_loop_()
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)
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)
esphome::web_server::ListEntitiesIterator entities_iterator_
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)
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
const char * format
json::SerializationBuffer<>(esphome::web_server::WebServer *, void *) message_generator_t
const void size_t len
Definition hal.h:64
uint16_t size
Definition helpers.cpp:25
size_t size_t const char * fmt
Definition helpers.h:1093
STL namespace.
static void uint32_t
bool operator==(const DeferredEvent &test) const
DeferredEvent(void *source, message_generator_t *message_generator)
std::string print()