11#include "esp_tls_crypto.h"
12#include <freertos/FreeRTOS.h>
13#include <freertos/task.h>
18#ifdef USE_WEBSERVER_OTA
19#include <multipart_parser.h>
30#include <sys/socket.h>
33namespace web_server_idf {
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 if (errno == EAGAIN || errno == EWOULDBLOCK) {
79 return HTTPD_SOCK_ERR_TIMEOUT;
82 ESP_LOGD(TAG,
"send error: errno %d", errno);
83 return HTTPD_SOCK_ERR_FAIL;
100 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
101 config.server_port = this->
port_;
102 config.uri_match_fn = [](
const char * ,
const char * ,
size_t ) {
return true; };
103 if (httpd_start(&this->
server_, &config) == ESP_OK) {
104 const httpd_uri_t handler_get = {
110 httpd_register_uri_handler(this->
server_, &handler_get);
112 const httpd_uri_t handler_post = {
118 httpd_register_uri_handler(this->
server_, &handler_post);
120 const httpd_uri_t handler_options = {
122 .method = HTTP_OPTIONS,
126 httpd_register_uri_handler(this->
server_, &handler_options);
131 ESP_LOGVV(TAG,
"Enter AsyncWebServer::request_post_handler. uri=%s", r->uri);
135 ESP_LOGW(TAG,
"Content length is required for post: %s", r->uri);
136 httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED,
nullptr);
140 if (content_type.has_value()) {
141 const char *content_type_char = content_type.value().c_str();
144 if (
stristr(content_type_char,
"application/x-www-form-urlencoded") !=
nullptr) {
146#ifdef USE_WEBSERVER_OTA
147 }
else if (
stristr(content_type_char,
"multipart/form-data") !=
nullptr) {
152 ESP_LOGW(TAG,
"Unsupported content type for POST: %s", content_type_char);
159 if (r->content_len > CONFIG_HTTPD_MAX_REQ_HDR_LEN) {
160 ESP_LOGW(TAG,
"Request size is to big: %zu", r->content_len);
161 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
165 std::string post_query;
166 if (r->content_len > 0) {
167 post_query.resize(r->content_len);
168 const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1);
170 if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
171 httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT,
nullptr);
172 return ESP_ERR_TIMEOUT;
174 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
184 ESP_LOGVV(TAG,
"Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
191 if (handler->canHandle(request)) {
194 handler->handleRequest(request);
202 return ESP_ERR_NOT_FOUND;
207 for (
auto *param : this->
params_) {
219 auto *str = strchr(this->
req_->uri,
'?');
220 if (str ==
nullptr) {
221 return this->
req_->uri;
223 return std::string(this->
req_->uri, str - this->req_->uri);
235 httpd_resp_send(*
this, content, HTTPD_RESP_USE_STRLEN);
237 httpd_resp_send(*
this,
nullptr, 0);
242 httpd_resp_set_status(*
this,
"302 Found");
243 httpd_resp_set_hdr(*
this,
"Location",
url.c_str());
244 httpd_resp_send(*
this,
nullptr, 0);
249 const char *
status =
nullptr;
263 httpd_resp_set_status(*
this,
status ==
nullptr ? to_string(code).c_str() :
status);
265 if (content_type && *content_type) {
266 httpd_resp_set_type(*
this, content_type);
268 httpd_resp_set_hdr(*
this,
"Accept-Ranges",
"none");
271 httpd_resp_set_hdr(*
this, pair.first.c_str(), pair.second.c_str());
278#ifdef USE_WEBSERVER_AUTH
280 if (username ==
nullptr || password ==
nullptr || *username == 0) {
283 auto auth = this->
get_header(
"Authorization");
284 if (!auth.has_value()) {
288 auto *auth_str = auth.value().c_str();
290 const auto auth_prefix_len =
sizeof(
"Basic ") - 1;
291 if (strncmp(
"Basic ", auth_str, auth_prefix_len) != 0) {
292 ESP_LOGW(TAG,
"Only Basic authorization supported yet");
296 std::string user_info;
297 user_info += username;
299 user_info += password;
302 esp_crypto_base64_encode(
nullptr, 0, &n,
reinterpret_cast<const uint8_t *
>(user_info.c_str()), user_info.size());
304 auto digest = std::unique_ptr<char[]>(
new char[n + 1]);
305 esp_crypto_base64_encode(
reinterpret_cast<uint8_t *
>(digest.get()), n, &out,
306 reinterpret_cast<const uint8_t *
>(user_info.c_str()), user_info.size());
308 return strcmp(digest.get(), auth_str + auth_prefix_len) == 0;
312 httpd_resp_set_hdr(*
this,
"Connection",
"keep-alive");
313 auto auth_val =
str_sprintf(
"Basic realm=\"%s\"", realm ? realm :
"Login Required");
314 httpd_resp_set_hdr(*
this,
"WWW-Authenticate", auth_val.c_str());
315 httpd_resp_send_err(*
this, HTTPD_401_UNAUTHORIZED,
nullptr);
321 for (
auto *param : this->
params_) {
322 if (param->name() == name) {
329 if (!
val.has_value()) {
331 if (url_query.has_value()) {
338 if (!
val.has_value()) {
343 this->params_.push_back(param);
348 httpd_resp_set_hdr(*this->
req_, name, value);
357 const int length = vsnprintf(
nullptr, 0, fmt, args);
364 vsnprintf(&str[0],
length + 1, fmt, args);
394 if (ses->fd_.load() == 0) {
395 ESP_LOGD(TAG,
"Removing dead event source session");
407 if (ses->fd_.load() != 0) {
408 ses->try_send_nodefer(
message, event,
id, reconnect);
416 if (ses->fd_.load() != 0) {
417 ses->deferrable_send_state(source, event_type, message_generator);
425 : server_(server), web_server_(ws), entities_iterator_(new
esphome::web_server::ListEntitiesIterator(ws, server)) {
426 httpd_req_t *req = *request;
428 httpd_resp_set_status(req, HTTPD_200);
429 httpd_resp_set_type(req,
"text/event-stream");
430 httpd_resp_set_hdr(req,
"Cache-Control",
"no-cache");
431 httpd_resp_set_hdr(req,
"Connection",
"keep-alive");
434 httpd_resp_set_hdr(req, pair.first.c_str(), pair.second.c_str());
437 httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN);
439 req->sess_ctx =
this;
442 this->
hd_ = req->handle;
443 this->
fd_.store(httpd_req_to_sockfd(req));
446 httpd_sess_set_send_override(this->
hd_, this->
fd_.load(), nonblocking_send);
453#ifdef USE_WEBSERVER_SORTING
457 JsonObject root = builder.
root();
458 root[
"name"] = group.second.name;
459 root[
"sorting_weight"] = group.second.weight;
480 ESP_LOGD(TAG,
"Event source connection closed (fd: %d)", rsp->fd_.load());
497 this->deferred_queue_.push_back(item);
526 if (bytes_sent == HTTPD_SOCK_ERR_TIMEOUT) {
535 ESP_LOGW(TAG,
"Closing stuck EventSource connection after %" PRIu16
" failed sends",
542 if (bytes_sent == HTTPD_SOCK_ERR_FAIL) {
546 if (bytes_sent <= 0) {
548 ESP_LOGW(TAG,
"Unexpected send result: %d", bytes_sent);
558 ESP_LOGV(TAG,
"Partial send: %d/%zu bytes (total: %zu/%zu)", bytes_sent, remaining,
event_bytes_sent_,
576 uint32_t reconnect) {
577 if (this->
fd_.load() == 0) {
588 const char chunk_len_header[] =
" " CRLF_STR;
589 const int chunk_len_header_len =
sizeof(chunk_len_header) - 1;
605 if (event && *event) {
625 int chunk_len =
event_buffer_.size() - CRLF_LEN - chunk_len_header_len;
626 char chunk_len_str[9];
627 snprintf(chunk_len_str, 9,
"%08x", chunk_len);
643 if (source ==
nullptr)
645 if (event_type ==
nullptr)
647 if (message_generator ==
nullptr)
650 if (0 != strcmp(event_type,
"state_detail_all") && 0 != strcmp(event_type,
"state")) {
651 ESP_LOGE(TAG,
"Can't defer non-state event");
670#ifdef USE_WEBSERVER_OTA
672 static constexpr size_t MULTIPART_CHUNK_SIZE = 1460;
673 static constexpr size_t YIELD_INTERVAL_BYTES = 16 * 1024;
676 const char *boundary_start;
679 ESP_LOGE(TAG,
"Failed to parse multipart boundary");
680 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
687 if (
h->canHandle(&req)) {
694 ESP_LOGW(TAG,
"No handler found for OTA request");
695 httpd_resp_send_err(r, HTTPD_404_NOT_FOUND,
nullptr);
700 std::string filename;
703 auto reader = std::make_unique<MultipartReader>(
"--" + std::string(boundary_start, boundary_len));
706 reader->set_data_callback([&](
const uint8_t *data,
size_t len) {
707 if (!reader->has_file() || !
len)
710 if (filename.empty()) {
711 filename = reader->get_current_part().filename;
712 ESP_LOGV(TAG,
"Processing file: '%s'", filename.c_str());
713 handler->
handleUpload(&req, filename, 0,
nullptr, 0,
false);
716 handler->
handleUpload(&req, filename, index,
const_cast<uint8_t *
>(data),
len,
false);
720 reader->set_part_complete_callback([&]() {
722 handler->
handleUpload(&req, filename, index,
nullptr, 0,
true);
729 std::unique_ptr<char[]> buffer(
new char[MULTIPART_CHUNK_SIZE]);
730 size_t bytes_since_yield = 0;
732 for (
size_t remaining = r->content_len; remaining > 0;) {
733 int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, MULTIPART_CHUNK_SIZE));
736 httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
738 return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
741 if (reader->parse(buffer.get(), recv_len) !=
static_cast<size_t>(recv_len)) {
742 ESP_LOGW(TAG,
"Multipart parser error");
743 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
747 remaining -= recv_len;
748 bytes_since_yield += recv_len;
750 if (bytes_since_yield > YIELD_INTERVAL_BYTES) {
752 bytes_since_yield = 0;
void begin(bool include_internal=false)
Builder class for creating JSON documents without lambdas.
value_type const & value() const
This class allows users to create a web server with their ESP nodes.
std::string get_config_json()
Return the webserver configuration as JSON.
std::map< uint64_t, SortingGroup > sorting_groups_
~AsyncEventSource() override
friend class AsyncEventSourceResponse
std::set< 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
std::unique_ptr< 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 esp_err_t request_handler(httpd_req_t *r)
AsyncWebParameter * getParam(const std::string &name)
optional< std::string > get_header(const char *name) const
void send(AsyncWebServerResponse *response)
bool hasHeader(const char *name) const
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type)
void requestAuthentication(const char *realm=nullptr) const
AsyncWebServerResponse * rsp_
bool authenticate(const char *username, const char *password) const
std::vector< AsyncWebParameter * > params_
void redirect(const std::string &url)
const AsyncWebServerRequest * req_
virtual const char * get_content_data() const =0
virtual size_t get_content_size() const =0
void addHeader(const char *name, const char *value)
optional< std::string > request_get_url_query(httpd_req_t *req)
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)
std::string(esphome::web_server::WebServer *, void *) message_generator_t
optional< std::string > query_key_value(const std::string &query_url, const std::string &key)
const char * stristr(const char *haystack, const char *needle)
bool request_has_header(httpd_req_t *req, const char *name)
Providing packet encoding functions for exchanging data with a remote host.
std::string str_sprintf(const char *fmt,...)
uint32_t IRAM_ATTR HOT millis()
message_generator_t * message_generator_