12#include "esp_tls_crypto.h"
13#include <freertos/FreeRTOS.h>
14#include <freertos/task.h>
19#ifdef USE_WEBSERVER_OTA
20#include <multipart_parser.h>
31#include <sys/socket.h>
36#define HTTPD_409 "409 Conflict"
39#define CRLF_STR "\r\n"
40#define CRLF_LEN (sizeof(CRLF_STR) - 1)
42static const char *
const TAG =
"web_server_idf";
48DefaultHeaders default_headers_instance;
69int nonblocking_send(httpd_handle_t hd,
int sockfd,
const char *buf,
size_t buf_len,
int flags) {
71 return HTTPD_SOCK_ERR_INVALID;
75 int ret = send(sockfd, buf, buf_len,
flags | MSG_DONTWAIT);
77 const int err = errno;
78 if (err == EAGAIN || err == EWOULDBLOCK) {
80 return HTTPD_SOCK_ERR_TIMEOUT;
83 ESP_LOGD(TAG,
"send error: errno %d", err);
84 return HTTPD_SOCK_ERR_FAIL;
107 shutdown(sockfd, SHUT_RD);
126 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
127 config.stack_size = config.stack_size + 256;
128 config.server_port = this->
port_;
129 config.uri_match_fn = [](
const char * ,
const char * ,
size_t ) {
return true; };
134 config.lru_purge_enable =
true;
137 if (httpd_start(&this->
server_, &config) == ESP_OK) {
138 const httpd_uri_t handler_get = {
144 httpd_register_uri_handler(this->
server_, &handler_get);
146 const httpd_uri_t handler_post = {
152 httpd_register_uri_handler(this->
server_, &handler_post);
154 const httpd_uri_t handler_options = {
156 .method = HTTP_OPTIONS,
160 httpd_register_uri_handler(this->
server_, &handler_options);
165 ESP_LOGVV(TAG,
"Enter AsyncWebServer::request_post_handler. uri=%s", r->uri);
169 ESP_LOGW(TAG,
"Content length is required for post: %s", r->uri);
170 httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED,
nullptr);
174 if (content_type.has_value()) {
175 const char *content_type_char = content_type.value().c_str();
178 size_t content_type_len = strlen(content_type_char);
179 if (
strcasestr_n(content_type_char, content_type_len,
"application/x-www-form-urlencoded") !=
nullptr) {
181#ifdef USE_WEBSERVER_OTA
182 }
else if (
strcasestr_n(content_type_char, content_type_len,
"multipart/form-data") !=
nullptr) {
187 ESP_LOGW(TAG,
"Unsupported content type for POST: %s", content_type_char);
194 if (r->content_len > CONFIG_HTTPD_MAX_REQ_HDR_LEN) {
195 ESP_LOGW(TAG,
"Request size is to big: %zu", r->content_len);
196 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
200 std::string post_query;
201 if (r->content_len > 0) {
202 post_query.resize(r->content_len);
203 const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1);
205 if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
206 httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT,
nullptr);
207 return ESP_ERR_TIMEOUT;
209 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
219 ESP_LOGVV(TAG,
"Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
226 if (handler->canHandle(request)) {
229 handler->handleRequest(request);
237 return ESP_ERR_NOT_FOUND;
242 for (
auto *param : this->
params_) {
254 const char *uri = this->
req_->uri;
255 const char *query_start = strchr(uri,
'?');
256 size_t uri_len = query_start ?
static_cast<size_t>(query_start - uri) : strlen(uri);
258 memcpy(buffer.data(), uri, copy_len);
259 buffer[copy_len] =
'\0';
261 size_t decoded_len =
url_decode(buffer.data());
262 return StringRef(buffer.data(), decoded_len);
266 httpd_resp_set_status(*
this,
"302 Found");
267 httpd_resp_set_hdr(*
this,
"Location", url.c_str());
268 httpd_resp_set_hdr(*
this,
"Connection",
"close");
269 httpd_resp_send(*
this,
nullptr, 0);
289 httpd_resp_set_status(*
this,
status);
291 if (content_type && *content_type) {
292 httpd_resp_set_type(*
this, content_type);
294 httpd_resp_set_hdr(*
this,
"Accept-Ranges",
"none");
297 httpd_resp_set_hdr(*
this, header.name, header.value);
304#ifdef USE_WEBSERVER_AUTH
305bool AsyncWebServerRequest::authenticate(
const char *username,
const char *password)
const {
306 if (username ==
nullptr || password ==
nullptr || *username == 0) {
309 auto auth = this->
get_header(
"Authorization");
310 if (!auth.has_value()) {
314 auto *auth_str = auth.value().c_str();
316 const auto auth_prefix_len =
sizeof(
"Basic ") - 1;
317 if (strncmp(
"Basic ", auth_str, auth_prefix_len) != 0) {
318 ESP_LOGW(TAG,
"Only Basic authorization supported yet");
323 constexpr size_t max_user_info_len = 256;
324 char user_info[max_user_info_len];
325 size_t user_len = strlen(username);
326 size_t pass_len = strlen(password);
327 size_t user_info_len = user_len + 1 + pass_len;
329 if (user_info_len >= max_user_info_len) {
330 ESP_LOGW(TAG,
"Credentials too long for authentication");
334 memcpy(user_info, username, user_len);
335 user_info[user_len] =
':';
336 memcpy(user_info + user_len + 1, password, pass_len);
337 user_info[user_info_len] =
'\0';
341 constexpr size_t max_digest_len = 350;
342 char digest[max_digest_len];
344 esp_crypto_base64_encode(
reinterpret_cast<uint8_t *
>(digest), max_digest_len, &out,
345 reinterpret_cast<const uint8_t *
>(user_info), user_info_len);
350 const char *provided = auth_str + auth_prefix_len;
351 size_t digest_len = out;
354 size_t provided_len = auth.value().size() - auth_prefix_len;
358 volatile size_t result = digest_len ^ provided_len;
362 for (
size_t i = 0; i < digest_len; i++) {
363 char provided_ch = (i < provided_len) ? provided[i] : 0;
364 result |=
static_cast<uint8_t
>(digest[i] ^ provided_ch);
370 httpd_resp_set_hdr(*
this,
"Connection",
"keep-alive");
373 httpd_resp_set_hdr(*
this,
"WWW-Authenticate",
"Basic realm=\"Login Required\"");
374 httpd_resp_send_err(*
this, HTTPD_401_UNAUTHORIZED,
nullptr);
380 for (
auto *param : this->
params_) {
381 if (param->name() == name) {
391 if (!
val.has_value()) {
396 this->params_.push_back(param);
403template<
typename Func>
404static auto search_query_sources(httpd_req_t *req,
const std::string &post_query,
const char *name, Func func)
405 ->
decltype(func(
nullptr,
size_t{0}, name)) {
406 if (!post_query.empty()) {
407 auto result = func(post_query.c_str(), post_query.size(), name);
415 auto len = httpd_req_get_url_query_len(req);
419 const char *query = strchr(req->uri,
'?');
420 if (query ==
nullptr) {
424 return func(query,
len, name);
438 if (
val.has_value()) {
439 return std::move(
val.value());
445 httpd_resp_set_hdr(*this->
req_, name, value);
452 int len = snprintf(buf,
sizeof(buf),
"%f", value);
496 for (
size_t i = 0; i < this->
sessions_.size();) {
499 if (ses->fd_.load() == 0) {
500 ESP_LOGD(TAG,
"Removing dead event source session");
515 if (ses->fd_.load() != 0) {
516 ses->try_send_nodefer(
message, event,
id, reconnect);
527 if (ses->fd_.load() != 0) {
528 ses->deferrable_send_state(
source, event_type, message_generator);
536 : server_(server), web_server_(ws), entities_iterator_(ws, server) {
537 httpd_req_t *req = *request;
539 httpd_resp_set_status(req, HTTPD_200);
540 httpd_resp_set_type(req,
"text/event-stream");
541 httpd_resp_set_hdr(req,
"Cache-Control",
"no-cache");
542 httpd_resp_set_hdr(req,
"Connection",
"keep-alive");
545 httpd_resp_set_hdr(req, header.name, header.value);
548 httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN);
550 req->sess_ctx =
this;
553 this->
hd_ = req->handle;
554 this->
fd_.store(httpd_req_to_sockfd(req));
557 httpd_sess_set_send_override(this->
hd_, this->
fd_.load(), nonblocking_send);
564#ifdef USE_WEBSERVER_SORTING
568 JsonObject root = builder.
root();
569 root[
"name"] = group.second.name;
570 root[
"sorting_weight"] = group.second.weight;
591 int fd = rsp->
fd_.exchange(0);
592 ESP_LOGD(TAG,
"Event source connection closed (fd: %d)", fd);
609 this->deferred_queue_.push_back(item);
638 if (bytes_sent == HTTPD_SOCK_ERR_TIMEOUT) {
647 ESP_LOGW(TAG,
"Closing stuck EventSource connection after %" PRIu16
" failed sends",
654 if (bytes_sent == HTTPD_SOCK_ERR_FAIL) {
658 if (bytes_sent <= 0) {
660 ESP_LOGW(TAG,
"Unexpected send result: %d", bytes_sent);
670 ESP_LOGV(TAG,
"Partial send: %d/%zu bytes (total: %zu/%zu)", bytes_sent, remaining,
event_bytes_sent_,
689 if (this->
fd_.load() == 0) {
700 const char chunk_len_header[] =
" " CRLF_STR;
701 const int chunk_len_header_len =
sizeof(chunk_len_header) - 1;
707 constexpr size_t num_buf_size = 32;
708 char num_buf[num_buf_size];
711 int len = snprintf(num_buf, num_buf_size,
"retry: %" PRIu32 CRLF_STR, reconnect);
716 int len = snprintf(num_buf, num_buf_size,
"id: %" PRIu32 CRLF_STR,
id);
720 if (event && *event) {
733 const char *first_n = strchr(
message,
'\n');
734 const char *first_r = strchr(
message,
'\r');
736 if (first_n ==
nullptr && first_r ==
nullptr) {
743 const char *line_start =
message;
744 size_t msg_len = strlen(
message);
745 const char *msg_end =
message + msg_len;
748 const char *next_n = first_n;
749 const char *next_r = first_r;
751 while (line_start <= msg_end) {
752 const char *line_end;
753 const char *next_line;
755 if (next_n ==
nullptr && next_r ==
nullptr) {
764 if (next_n !=
nullptr && next_r !=
nullptr) {
765 if (next_r + 1 == next_n) {
768 next_line = next_n + 1;
771 line_end = (next_r < next_n) ? next_r : next_n;
772 next_line = line_end + 1;
774 }
else if (next_n !=
nullptr) {
777 next_line = next_n + 1;
781 next_line = next_r + 1;
789 line_start = next_line;
792 if (line_start >= msg_end) {
797 next_n = strchr(line_start,
'\n');
798 next_r = strchr(line_start,
'\r');
806 if (
event_buffer_.size() ==
static_cast<size_t>(chunk_len_header_len)) {
815 int chunk_len =
event_buffer_.size() - CRLF_LEN - chunk_len_header_len;
816 char chunk_len_str[9];
817 snprintf(chunk_len_str, 9,
"%08x", chunk_len);
835 if (event_type ==
nullptr)
837 if (message_generator ==
nullptr)
840 if (0 != strcmp(event_type,
"state_detail_all") && 0 != strcmp(event_type,
"state")) {
841 ESP_LOGE(TAG,
"Can't defer non-state event");
860#ifdef USE_WEBSERVER_OTA
862 static constexpr size_t MULTIPART_CHUNK_SIZE = 1460;
863 static constexpr size_t YIELD_INTERVAL_BYTES = 16 * 1024;
866 const char *boundary_start;
869 ESP_LOGE(TAG,
"Failed to parse multipart boundary");
870 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
877 if (
h->canHandle(&req)) {
884 ESP_LOGW(TAG,
"No handler found for OTA request");
885 httpd_resp_send_err(r, HTTPD_404_NOT_FOUND,
nullptr);
890 std::string filename;
893 auto reader = std::make_unique<MultipartReader>(
"--" + std::string(boundary_start, boundary_len));
896 reader->set_data_callback([&](
const uint8_t *data,
size_t len) {
897 if (!reader->has_file() || !
len)
900 if (filename.empty()) {
901 filename = reader->get_current_part().filename;
902 ESP_LOGV(TAG,
"Processing file: '%s'", filename.c_str());
903 handler->
handleUpload(&req, filename, 0,
nullptr, 0,
false);
906 handler->
handleUpload(&req, filename, index,
const_cast<uint8_t *
>(data),
len,
false);
910 reader->set_part_complete_callback([&]() {
912 handler->
handleUpload(&req, filename, index,
nullptr, 0,
true);
919 auto buffer = std::make_unique_for_overwrite<char[]>(MULTIPART_CHUNK_SIZE);
920 size_t bytes_since_yield = 0;
922 for (
size_t remaining = r->content_len; remaining > 0;) {
923 int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, MULTIPART_CHUNK_SIZE));
926 httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
928 return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
931 if (reader->parse(buffer.get(), recv_len) !=
static_cast<size_t>(recv_len)) {
932 ESP_LOGW(TAG,
"Multipart parser error");
933 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
937 remaining -= recv_len;
938 bytes_since_yield += recv_len;
940 if (bytes_since_yield > YIELD_INTERVAL_BYTES) {
942 bytes_since_yield = 0;
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void begin(bool include_internal=false)
StringRef is a reference to a string owned by something else.
Builder class for creating JSON documents without lambdas.
SerializationBuffer serialize()
Serialize the JSON document to a SerializationBuffer (stack-first allocation) Uses 512-byte stack buf...
This class allows users to create a web server with their ESP nodes.
json::SerializationBuffer get_config_json()
Return the webserver configuration as JSON.
std::map< uint64_t, SortingGroup > sorting_groups_
~AsyncEventSource() override
friend class AsyncEventSourceResponse
bool loop()
Returns true if there are sessions remaining (including pending cleanup).
std::vector< AsyncEventSourceResponse * > sessions_
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
esphome::web_server::WebServer * web_server_
void try_send_nodefer(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
void handleRequest(AsyncWebServerRequest *request) override
connect_handler_t on_connect_
static void destroy(void *p)
std::vector< DeferredEvent > deferred_queue_
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)
void process_deferred_queue_()
AsyncEventSourceResponse(const AsyncWebServerRequest *request, esphome::web_server_idf::AsyncEventSource *server, esphome::web_server::WebServer *ws)
static constexpr uint16_t MAX_CONSECUTIVE_SEND_FAILURES
esphome::web_server::ListEntitiesIterator entities_iterator_
uint16_t consecutive_send_failures_
bool try_send_nodefer(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
std::string event_buffer_
void print(const char *str)
void printf(const char *fmt,...) __attribute__((format(printf
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)
std::function< void(AsyncWebServerRequest *request)> on_not_found_
static esp_err_t request_post_handler(httpd_req_t *r)
std::vector< AsyncWebHandler * > handlers_
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)
AsyncWebParameter * getParam(const char *name)
optional< std::string > get_header(const char *name) const
bool hasArg(const char *name)
StringRef url_to(std::span< char, URL_BUF_SIZE > buffer) const
Write URL (without query string) to buffer, returns StringRef pointing to buffer.
bool hasHeader(const char *name) const
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type)
static constexpr size_t URL_BUF_SIZE
Buffer size for url_to()
std::string arg(const char *name)
optional< std::string > find_query_value_(const char *name) const
ESPDEPRECATED("Use url_to() instead. Removed in 2026.9.0", "2026.3.0") std void requestAuthentication(const char *realm=nullptr) const
AsyncWebServerResponse * rsp_
std::vector< AsyncWebParameter * > params_
void redirect(const std::string &url)
const AsyncWebServerRequest * req_
void addHeader(const char *name, const char *value)
bool query_has_key(const char *query_url, size_t query_len, const char *key)
json::SerializationBuffer<>(esphome::web_server::WebServer *, void *) message_generator_t
optional< std::string > request_get_header(httpd_req_t *req, const char *name)
bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len)
optional< std::string > query_key_value(const char *query_url, size_t query_len, const char *key)
const char * strcasestr_n(const char *haystack, size_t haystack_len, const char *needle)
size_t url_decode(char *str)
Decode URL-encoded string in-place (e.g., %20 -> space, + -> space) Returns the new length of the dec...
bool request_has_header(httpd_req_t *req, const char *name)
const char int const __FlashStringHelper va_list args
size_t size_t const char va_start(args, fmt)
size_t size_t const char * fmt
uint32_t IRAM_ATTR HOT millis()
message_generator_t * message_generator_