[web_server_idf] Replace heap-allocated url() with stack-based url_to() (#13407)

This commit is contained in:
J. Nick Koston
2026-01-27 15:02:19 -10:00
committed by GitHub
parent f084d320fc
commit f9687a2a31
6 changed files with 63 additions and 30 deletions

View File

@@ -96,10 +96,16 @@ void CaptivePortal::start() {
} }
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
if (req->url() == ESPHOME_F("/config.json")) { #ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
StringRef url = req->url_to(url_buf);
#else
const auto &url = req->url();
#endif
if (url == ESPHOME_F("/config.json")) {
this->handle_config(req); this->handle_config(req);
return; return;
} else if (req->url() == ESPHOME_F("/wifisave")) { } else if (url == ESPHOME_F("/wifisave")) {
this->handle_wifisave(req); this->handle_wifisave(req);
return; return;
} }

View File

@@ -41,12 +41,14 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
void add_label_name(EntityBase *obj, const std::string &value) { relabel_map_name_.insert({obj, value}); } void add_label_name(EntityBase *obj, const std::string &value) { relabel_map_name_.insert({obj, value}); }
bool canHandle(AsyncWebServerRequest *request) const override { bool canHandle(AsyncWebServerRequest *request) const override {
if (request->method() == HTTP_GET) { if (request->method() != HTTP_GET)
if (request->url() == "/metrics") return false;
return true; #ifdef USE_ESP32
} char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
return request->url_to(url_buf) == "/metrics";
return false; #else
return request->url() == ESPHOME_F("/metrics");
#endif
} }
void handleRequest(AsyncWebServerRequest *req) override; void handleRequest(AsyncWebServerRequest *req) override;

View File

@@ -32,8 +32,15 @@ class OTARequestHandler : public AsyncWebHandler {
void handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index, uint8_t *data, void handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index, uint8_t *data,
size_t len, bool final) override; size_t len, bool final) override;
bool canHandle(AsyncWebServerRequest *request) const override { bool canHandle(AsyncWebServerRequest *request) const override {
// Check if this is an OTA update request if (request->method() != HTTP_POST)
bool is_ota_request = request->url() == "/update" && request->method() == HTTP_POST; return false;
// Check if this is an OTA update request
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
bool is_ota_request = request->url_to(url_buf) == "/update";
#else
bool is_ota_request = request->url() == ESPHOME_F("/update");
#endif
#if defined(USE_WEBSERVER_OTA_DISABLED) && defined(USE_CAPTIVE_PORTAL) #if defined(USE_WEBSERVER_OTA_DISABLED) && defined(USE_CAPTIVE_PORTAL)
// IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component // IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component

View File

@@ -2187,7 +2187,12 @@ std::string WebServer::update_json_(update::UpdateEntity *obj, JsonDetail start_
#endif #endif
bool WebServer::canHandle(AsyncWebServerRequest *request) const { bool WebServer::canHandle(AsyncWebServerRequest *request) const {
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
StringRef url = request->url_to(url_buf);
#else
const auto &url = request->url(); const auto &url = request->url();
#endif
const auto method = request->method(); const auto method = request->method();
// Static URL checks - use ESPHOME_F to keep strings in flash on ESP8266 // Static URL checks - use ESPHOME_F to keep strings in flash on ESP8266
@@ -2323,30 +2328,35 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
return false; return false;
} }
void WebServer::handleRequest(AsyncWebServerRequest *request) { void WebServer::handleRequest(AsyncWebServerRequest *request) {
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
StringRef url = request->url_to(url_buf);
#else
const auto &url = request->url(); const auto &url = request->url();
#endif
// Handle static routes first // Handle static routes first
if (url == "/") { if (url == ESPHOME_F("/")) {
this->handle_index_request(request); this->handle_index_request(request);
return; return;
} }
#if !defined(USE_ESP32) && defined(USE_ARDUINO) #if !defined(USE_ESP32) && defined(USE_ARDUINO)
if (url == "/events") { if (url == ESPHOME_F("/events")) {
this->events_.add_new_client(this, request); this->events_.add_new_client(this, request);
return; return;
} }
#endif #endif
#ifdef USE_WEBSERVER_CSS_INCLUDE #ifdef USE_WEBSERVER_CSS_INCLUDE
if (url == "/0.css") { if (url == ESPHOME_F("/0.css")) {
this->handle_css_request(request); this->handle_css_request(request);
return; return;
} }
#endif #endif
#ifdef USE_WEBSERVER_JS_INCLUDE #ifdef USE_WEBSERVER_JS_INCLUDE
if (url == "/0.js") { if (url == ESPHOME_F("/0.js")) {
this->handle_js_request(request); this->handle_js_request(request);
return; return;
} }

View File

@@ -246,21 +246,16 @@ optional<std::string> AsyncWebServerRequest::get_header(const char *name) const
return request_get_header(*this, name); return request_get_header(*this, name);
} }
std::string AsyncWebServerRequest::url() const { StringRef AsyncWebServerRequest::url_to(std::span<char, URL_BUF_SIZE> buffer) const {
auto *query_start = strchr(this->req_->uri, '?'); const char *uri = this->req_->uri;
std::string result; const char *query_start = strchr(uri, '?');
if (query_start == nullptr) { size_t uri_len = query_start ? static_cast<size_t>(query_start - uri) : strlen(uri);
result = this->req_->uri; size_t copy_len = std::min(uri_len, URL_BUF_SIZE - 1);
} else { memcpy(buffer.data(), uri, copy_len);
result = std::string(this->req_->uri, query_start - this->req_->uri); buffer[copy_len] = '\0';
}
// Decode URL-encoded characters in-place (e.g., %20 -> space) // Decode URL-encoded characters in-place (e.g., %20 -> space)
// This matches AsyncWebServer behavior on Arduino size_t decoded_len = url_decode(buffer.data());
if (!result.empty()) { return StringRef(buffer.data(), decoded_len);
size_t new_len = url_decode(&result[0]);
result.resize(new_len);
}
return result;
} }
std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); } std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); }

View File

@@ -3,12 +3,14 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
#include <esp_http_server.h> #include <esp_http_server.h>
#include <atomic> #include <atomic>
#include <functional> #include <functional>
#include <list> #include <list>
#include <map> #include <map>
#include <span>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -110,7 +112,15 @@ class AsyncWebServerRequest {
~AsyncWebServerRequest(); ~AsyncWebServerRequest();
http_method method() const { return static_cast<http_method>(this->req_->method); } http_method method() const { return static_cast<http_method>(this->req_->method); }
std::string url() const; static constexpr size_t URL_BUF_SIZE = CONFIG_HTTPD_MAX_URI_LEN + 1; ///< Buffer size for url_to()
/// Write URL (without query string) to buffer, returns StringRef pointing to buffer.
/// URL is decoded (e.g., %20 -> space).
StringRef url_to(std::span<char, URL_BUF_SIZE> buffer) const;
/// Get URL as std::string. Prefer url_to() to avoid heap allocation.
std::string url() const {
char buffer[URL_BUF_SIZE];
return std::string(this->url_to(buffer));
}
std::string host() const; std::string host() const;
// NOLINTNEXTLINE(readability-identifier-naming) // NOLINTNEXTLINE(readability-identifier-naming)
size_t contentLength() const { return this->req_->content_len; } size_t contentLength() const { return this->req_->content_len; }
@@ -306,7 +316,10 @@ class AsyncEventSource : public AsyncWebHandler {
// NOLINTNEXTLINE(readability-identifier-naming) // NOLINTNEXTLINE(readability-identifier-naming)
bool canHandle(AsyncWebServerRequest *request) const override { bool canHandle(AsyncWebServerRequest *request) const override {
return request->method() == HTTP_GET && request->url() == this->url_; if (request->method() != HTTP_GET)
return false;
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
return request->url_to(url_buf) == this->url_;
} }
// NOLINTNEXTLINE(readability-identifier-naming) // NOLINTNEXTLINE(readability-identifier-naming)
void handleRequest(AsyncWebServerRequest *request) override; void handleRequest(AsyncWebServerRequest *request) override;