mirror of
https://github.com/esphome/esphome.git
synced 2026-03-01 02:14:19 -07:00
[http_request] Lowercase collect headers at config time, eliminate per-request overhead
Move header lowercasing from the per-request start() path to config time: - Python codegen now lowercases collect_headers values before passing to C++ - add_collect_header() stores values as-is (already lowered by Python) - start() with std::vector is now a direct passthrough to perform() - Deprecated std::set overload still lowercases for external callers Rename collect_headers_ to lower_case_collect_headers_ and update all parameter names throughout the chain to make the lowercase invariant explicit in the API contract. This eliminates per-request allocation of a temporary vector and str_lower_case() calls on every HTTP request, reducing stack usage in the perform() call chain where stack space is critical for HTTPS TLS handshakes.
This commit is contained in:
@@ -310,7 +310,7 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
||||
cg.add(var.add_request_header(key, template_))
|
||||
|
||||
for value in config.get(CONF_COLLECT_HEADERS, []):
|
||||
cg.add(var.add_collect_header(value))
|
||||
cg.add(var.add_collect_header(value.lower()))
|
||||
|
||||
if response_conf := config.get(CONF_ON_RESPONSE):
|
||||
if capture_response:
|
||||
|
||||
@@ -81,9 +81,9 @@ inline bool is_redirect(int const status) {
|
||||
inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; }
|
||||
|
||||
/// Check if a header name should be collected (linear scan, fine for small lists)
|
||||
inline bool should_collect_header(const std::vector<std::string> &collect_headers,
|
||||
inline bool should_collect_header(const std::vector<std::string> &lower_case_collect_headers,
|
||||
const std::string &lower_header_name) {
|
||||
for (const auto &h : collect_headers) {
|
||||
for (const auto &h : lower_case_collect_headers) {
|
||||
if (h == lower_header_name)
|
||||
return true;
|
||||
}
|
||||
@@ -336,8 +336,8 @@ class HttpRequestComponent : public Component {
|
||||
return this->start(url, "GET", "", request_headers);
|
||||
}
|
||||
std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) {
|
||||
return this->start(url, "GET", "", request_headers, collect_headers);
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
return this->start(url, "GET", "", request_headers, lower_case_collect_headers);
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body) {
|
||||
return this->start(url, "POST", body, {});
|
||||
@@ -348,8 +348,8 @@ class HttpRequestComponent : public Component {
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) {
|
||||
return this->start(url, "POST", body, request_headers, collect_headers);
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
return this->start(url, "POST", body, request_headers, lower_case_collect_headers);
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
@@ -364,25 +364,24 @@ class HttpRequestComponent : public Component {
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
return this->start(url, method, body, request_headers,
|
||||
std::vector<std::string>(collect_headers.begin(), collect_headers.end()));
|
||||
std::vector<std::string> lower;
|
||||
lower.reserve(collect_headers.size());
|
||||
for (const auto &h : collect_headers) {
|
||||
lower.push_back(str_lower_case(h));
|
||||
}
|
||||
return this->perform(url, method, body, request_headers, lower);
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) {
|
||||
std::vector<std::string> lower_case_collect_headers;
|
||||
lower_case_collect_headers.reserve(collect_headers.size());
|
||||
for (const auto &header : collect_headers) {
|
||||
lower_case_collect_headers.push_back(str_lower_case(header));
|
||||
}
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
return this->perform(url, method, body, request_headers, lower_case_collect_headers);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
|
||||
const std::string &body, const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) = 0;
|
||||
const std::vector<std::string> &lower_case_collect_headers) = 0;
|
||||
const char *useragent_{nullptr};
|
||||
bool follow_redirects_{};
|
||||
uint16_t redirect_limit_{};
|
||||
@@ -404,7 +403,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
this->request_headers_.insert({key, value});
|
||||
}
|
||||
|
||||
void add_collect_header(const char *value) { this->collect_headers_.push_back(value); }
|
||||
void add_collect_header(const char *value) { this->lower_case_collect_headers_.push_back(value); }
|
||||
|
||||
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
|
||||
|
||||
@@ -446,7 +445,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
}
|
||||
|
||||
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, request_headers,
|
||||
this->collect_headers_);
|
||||
this->lower_case_collect_headers_);
|
||||
|
||||
auto captured_args = std::make_tuple(x...);
|
||||
|
||||
@@ -509,7 +508,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
|
||||
HttpRequestComponent *parent_;
|
||||
std::map<const char *, TemplatableValue<const char *, Ts...>> request_headers_{};
|
||||
std::vector<std::string> collect_headers_{"content-type", "content-length"};
|
||||
std::vector<std::string> lower_case_collect_headers_{"content-type", "content-length"};
|
||||
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
||||
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||
#ifdef USE_HTTP_REQUEST_RESPONSE
|
||||
|
||||
@@ -27,7 +27,7 @@ static constexpr int ESP8266_SSL_ERR_OOM = -1000;
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) {
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -107,9 +107,9 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
||||
}
|
||||
|
||||
// returned needed headers must be collected before the requests
|
||||
const char *header_keys[collect_headers.size()];
|
||||
const char *header_keys[lower_case_collect_headers.size()];
|
||||
int index = 0;
|
||||
for (auto const &header_name : collect_headers) {
|
||||
for (auto const &header_name : lower_case_collect_headers) {
|
||||
header_keys[index++] = header_name.c_str();
|
||||
}
|
||||
container->client_.collectHeaders(header_keys, index);
|
||||
@@ -164,7 +164,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
||||
auto header_count = container->client_.headers();
|
||||
for (int i = 0; i < header_count; i++) {
|
||||
const std::string header_name = str_lower_case(container->client_.headerName(i).c_str());
|
||||
if (should_collect_header(collect_headers, header_name)) {
|
||||
if (should_collect_header(lower_case_collect_headers, header_name)) {
|
||||
std::string header_value = container->client_.header(i).c_str();
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
container->response_headers_.push_back({header_name, header_value});
|
||||
|
||||
@@ -50,7 +50,7 @@ class HttpRequestArduino : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) override;
|
||||
const std::vector<std::string> &lower_case_collect_headers) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::http_request
|
||||
|
||||
@@ -19,7 +19,7 @@ static const char *const TAG = "http_request.host";
|
||||
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) {
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -116,7 +116,7 @@ std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url,
|
||||
for (auto header : response.headers) {
|
||||
ESP_LOGD(TAG, "Header: %s: %s", header.first.c_str(), header.second.c_str());
|
||||
auto lower_name = str_lower_case(header.first);
|
||||
if (should_collect_header(collect_headers, lower_name)) {
|
||||
if (should_collect_header(lower_case_collect_headers, lower_name)) {
|
||||
container->response_headers_.push_back({lower_name, header.second});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class HttpRequestHost : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) override;
|
||||
const std::vector<std::string> &lower_case_collect_headers) override;
|
||||
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace esphome::http_request {
|
||||
static const char *const TAG = "http_request.idf";
|
||||
|
||||
struct UserData {
|
||||
const std::vector<std::string> &collect_headers;
|
||||
const std::vector<std::string> &lower_case_collect_headers;
|
||||
std::vector<Header> &response_headers;
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_HEADER: {
|
||||
const std::string header_name = str_lower_case(evt->header_key);
|
||||
if (should_collect_header(user_data->collect_headers, header_name)) {
|
||||
if (should_collect_header(user_data->lower_case_collect_headers, header_name)) {
|
||||
const std::string header_value = evt->header_value;
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
user_data->response_headers.push_back({header_name, header_value});
|
||||
@@ -55,7 +55,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) {
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -118,7 +118,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
|
||||
container->set_secure(secure);
|
||||
|
||||
auto user_data = UserData{collect_headers, container->response_headers_};
|
||||
auto user_data = UserData{lower_case_collect_headers, container->response_headers_};
|
||||
esp_http_client_set_user_data(client, static_cast<void *>(&user_data));
|
||||
|
||||
for (const auto &header : request_headers) {
|
||||
|
||||
@@ -38,7 +38,7 @@ class HttpRequestIDF : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::vector<std::string> &collect_headers) override;
|
||||
const std::vector<std::string> &lower_case_collect_headers) override;
|
||||
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
||||
uint16_t buffer_size_rx_{};
|
||||
uint16_t buffer_size_tx_{};
|
||||
|
||||
Reference in New Issue
Block a user