Revert direct req->uri access (unsafe — ESP-IDF uses parsed offsets,
not strchr). Instead fix the root cause: query_key_value used a full
CONFIG_HTTPD_MAX_URI_LEN+1 (513 byte) stack buffer while
search_query_sources had another 513 byte buffer on the stack
simultaneously, totaling ~1KB on the httpd thread's limited stack.
Use SmallBufferWithHeapFallback<128> for the value extraction buffer.
128 bytes covers typical parameter values on stack; longer values
(e.g. base64 IR data) fall back to heap.
search_query_sources was copying the URL query string into a 513-byte
stack buffer, then query_key_value added another 513-byte buffer for
the extracted value — 1026 bytes simultaneously on the httpd thread's
limited stack, causing a crash in lwip_select.
The query string already lives in req->uri after the '?'. Access it
directly via pointer instead of copying, eliminating one buffer entirely.
Query strings cannot exceed the max URI length, so SmallBufferWithHeapFallback
is unnecessary. Use a plain stack array instead for zero heap allocation.
- Add early return for empty post_query (common GET request path)
- Refactor getParam to use find_query_value_ instead of duplicating search logic
- Remove now-unused request_get_url_query (and its heap allocation)
- Remove unused std::string overload of query_key_value
Replace request_get_url_query (returns std::string) with inline
httpd_req_get_url_query_str into a SmallBufferWithHeapFallback
stack buffer. Typical query strings (<256 bytes) now use zero
heap allocations for parameter lookups.
Both methods iterated post_query_ then url_query with the same
pattern. Extracted a file-local template that takes a callback,
avoiding duplicated request_get_url_query heap allocation logic.
hasArg only needs to know if a key exists, not its value.
query_has_key uses a 1-byte buffer and checks the return code
from httpd_query_key_value — no url_decode, no std::string.
Use const auto& to bind arg() result directly, avoiding
unnecessary std::string intermediate copies. Construct
std::string only where needed (lambda capture, setter call).
Use const auto& to bind directly to arg() result (std::string on
IDF, Arduino String on Arduino) and pass c_str()/length() to the
setter. No intermediate std::string copy needed.
ClimateCall has overloaded set_target_temperature*(float) and
set_target_temperature*(optional<float>), so the compiler can't
infer NumT. Use static_cast to select the float overload.
- Mark find_query_value_ as const
- Remove redundant hasArg() guards where parse_number() already
handles empty strings (returns nullopt)
- Use !empty() instead of hasArg() for parse_bool_param_
- Merge parse_float_param_ and parse_int_param_ into single
parse_num_param_ template
- Combine missing/empty checks for IR data parameter
On Arduino, arg() returns const String&, so auto copies unnecessarily.
const auto& binds to the reference on Arduino and extends the temporary
lifetime on IDF.
Switch all web_server callers from getParam()/hasParam() to arg()/hasArg().
Both APIs exist on Arduino ESPAsyncWebServer and our IDF implementation.
On the IDF side, getParam() allocated a new AsyncWebParameter on the heap
for every successful lookup, cached it in a vector, and required cleanup
in the destructor. No caller ever held the pointer or called getParam
twice with the same name - every use was just getParam("x")->value()
immediately.
Rewrite IDF arg()/hasArg() to call query_key_value() directly, bypassing
getParam entirely. The linker strips the now-unreferenced getParam,
AsyncWebParameter, and cache machinery.
Saves ~348 bytes flash on ESP32-IDF, ~272 bytes on ESP8266 Arduino.