Refactor WebServer request handling for improved maintainability (#9470)

This commit is contained in:
J. Nick Koston 2025-07-14 09:24:20 -10:00 committed by GitHub
parent e231d334a3
commit f8c45573f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1711,162 +1711,161 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
#endif #endif
bool WebServer::canHandle(AsyncWebServerRequest *request) const { bool WebServer::canHandle(AsyncWebServerRequest *request) const {
if (request->url() == "/") const auto &url = request->url();
const auto method = request->method();
// Simple URL checks
if (url == "/")
return true; return true;
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
if (request->url() == "/events") { if (url == "/events")
return true; return true;
}
#endif #endif
#ifdef USE_WEBSERVER_CSS_INCLUDE #ifdef USE_WEBSERVER_CSS_INCLUDE
if (request->url() == "/0.css") if (url == "/0.css")
return true; return true;
#endif #endif
#ifdef USE_WEBSERVER_JS_INCLUDE #ifdef USE_WEBSERVER_JS_INCLUDE
if (request->url() == "/0.js") if (url == "/0.js")
return true; return true;
#endif #endif
#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA))
return true; return true;
}
#endif #endif
// Store the URL to prevent temporary string destruction // Parse URL for component checks
// request->url() returns a reference to a String (on Arduino) or std::string (on ESP-IDF)
// UrlMatch stores pointers to the string's data, so we must ensure the string outlives match_url()
const auto &url = request->url();
UrlMatch match = match_url(url.c_str(), url.length(), true); UrlMatch match = match_url(url.c_str(), url.length(), true);
if (!match.valid) if (!match.valid)
return false; return false;
// Common pattern check
bool is_get = method == HTTP_GET;
bool is_post = method == HTTP_POST;
bool is_get_or_post = is_get || is_post;
if (!is_get_or_post)
return false;
// GET-only components
if (is_get) {
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (request->method() == HTTP_GET && match.domain_equals("sensor")) if (match.domain_equals("sensor"))
return true; return true;
#endif #endif
#ifdef USE_SWITCH
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("switch"))
return true;
#endif
#ifdef USE_BUTTON
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("button"))
return true;
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (request->method() == HTTP_GET && match.domain_equals("binary_sensor")) if (match.domain_equals("binary_sensor"))
return true; return true;
#endif #endif
#ifdef USE_FAN
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("fan"))
return true;
#endif
#ifdef USE_LIGHT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("light"))
return true;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (request->method() == HTTP_GET && match.domain_equals("text_sensor")) if (match.domain_equals("text_sensor"))
return true; return true;
#endif #endif
#ifdef USE_COVER
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("cover"))
return true;
#endif
#ifdef USE_NUMBER
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("number"))
return true;
#endif
#ifdef USE_DATETIME_DATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("date"))
return true;
#endif
#ifdef USE_DATETIME_TIME
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("time"))
return true;
#endif
#ifdef USE_DATETIME_DATETIME
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("datetime"))
return true;
#endif
#ifdef USE_TEXT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("text"))
return true;
#endif
#ifdef USE_SELECT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("select"))
return true;
#endif
#ifdef USE_CLIMATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("climate"))
return true;
#endif
#ifdef USE_LOCK
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("lock"))
return true;
#endif
#ifdef USE_VALVE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("valve"))
return true;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if ((request->method() == HTTP_GET || request->method() == HTTP_POST) && match.domain_equals("alarm_control_panel"))
return true;
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
if (request->method() == HTTP_GET && match.domain_equals("event")) if (match.domain_equals("event"))
return true; return true;
#endif #endif
}
#ifdef USE_UPDATE // GET+POST components
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("update")) if (is_get_or_post) {
return true; #ifdef USE_SWITCH
if (match.domain_equals("switch"))
return true;
#endif #endif
#ifdef USE_BUTTON
if (match.domain_equals("button"))
return true;
#endif
#ifdef USE_FAN
if (match.domain_equals("fan"))
return true;
#endif
#ifdef USE_LIGHT
if (match.domain_equals("light"))
return true;
#endif
#ifdef USE_COVER
if (match.domain_equals("cover"))
return true;
#endif
#ifdef USE_NUMBER
if (match.domain_equals("number"))
return true;
#endif
#ifdef USE_DATETIME_DATE
if (match.domain_equals("date"))
return true;
#endif
#ifdef USE_DATETIME_TIME
if (match.domain_equals("time"))
return true;
#endif
#ifdef USE_DATETIME_DATETIME
if (match.domain_equals("datetime"))
return true;
#endif
#ifdef USE_TEXT
if (match.domain_equals("text"))
return true;
#endif
#ifdef USE_SELECT
if (match.domain_equals("select"))
return true;
#endif
#ifdef USE_CLIMATE
if (match.domain_equals("climate"))
return true;
#endif
#ifdef USE_LOCK
if (match.domain_equals("lock"))
return true;
#endif
#ifdef USE_VALVE
if (match.domain_equals("valve"))
return true;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if (match.domain_equals("alarm_control_panel"))
return true;
#endif
#ifdef USE_UPDATE
if (match.domain_equals("update"))
return true;
#endif
}
return false; return false;
} }
void WebServer::handleRequest(AsyncWebServerRequest *request) { void WebServer::handleRequest(AsyncWebServerRequest *request) {
if (request->url() == "/") { const auto &url = request->url();
// Handle static routes first
if (url == "/") {
this->handle_index_request(request); this->handle_index_request(request);
return; return;
} }
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
if (request->url() == "/events") { if (url == "/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 (request->url() == "/0.css") { if (url == "/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 (request->url() == "/0.js") { if (url == "/0.js") {
this->handle_js_request(request); this->handle_js_request(request);
return; return;
} }
@ -1879,147 +1878,85 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
} }
#endif #endif
// See comment in canHandle() for why we store the URL reference // Parse URL for component routing
const auto &url = request->url();
UrlMatch match = match_url(url.c_str(), url.length(), false); UrlMatch match = match_url(url.c_str(), url.length(), false);
// Component routing using minimal code repetition
struct ComponentRoute {
const char *domain;
void (WebServer::*handler)(AsyncWebServerRequest *, const UrlMatch &);
};
static const ComponentRoute routes[] = {
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (match.domain_equals("sensor")) { {"sensor", &WebServer::handle_sensor_request},
this->handle_sensor_request(request, match);
return;
}
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (match.domain_equals("switch")) { {"switch", &WebServer::handle_switch_request},
this->handle_switch_request(request, match);
return;
}
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
if (match.domain_equals("button")) { {"button", &WebServer::handle_button_request},
this->handle_button_request(request, match);
return;
}
#endif #endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (match.domain_equals("binary_sensor")) { {"binary_sensor", &WebServer::handle_binary_sensor_request},
this->handle_binary_sensor_request(request, match);
return;
}
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
if (match.domain_equals("fan")) { {"fan", &WebServer::handle_fan_request},
this->handle_fan_request(request, match);
return;
}
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
if (match.domain_equals("light")) { {"light", &WebServer::handle_light_request},
this->handle_light_request(request, match);
return;
}
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (match.domain_equals("text_sensor")) { {"text_sensor", &WebServer::handle_text_sensor_request},
this->handle_text_sensor_request(request, match);
return;
}
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
if (match.domain_equals("cover")) { {"cover", &WebServer::handle_cover_request},
this->handle_cover_request(request, match);
return;
}
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (match.domain_equals("number")) { {"number", &WebServer::handle_number_request},
this->handle_number_request(request, match);
return;
}
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
if (match.domain_equals("date")) { {"date", &WebServer::handle_date_request},
this->handle_date_request(request, match);
return;
}
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
if (match.domain_equals("time")) { {"time", &WebServer::handle_time_request},
this->handle_time_request(request, match);
return;
}
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
if (match.domain_equals("datetime")) { {"datetime", &WebServer::handle_datetime_request},
this->handle_datetime_request(request, match);
return;
}
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
if (match.domain_equals("text")) { {"text", &WebServer::handle_text_request},
this->handle_text_request(request, match);
return;
}
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
if (match.domain_equals("select")) { {"select", &WebServer::handle_select_request},
this->handle_select_request(request, match);
return;
}
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
if (match.domain_equals("climate")) { {"climate", &WebServer::handle_climate_request},
this->handle_climate_request(request, match);
return;
}
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
if (match.domain_equals("lock")) { {"lock", &WebServer::handle_lock_request},
this->handle_lock_request(request, match);
return;
}
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
if (match.domain_equals("valve")) { {"valve", &WebServer::handle_valve_request},
this->handle_valve_request(request, match);
return;
}
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
if (match.domain_equals("alarm_control_panel")) { {"alarm_control_panel", &WebServer::handle_alarm_control_panel_request},
this->handle_alarm_control_panel_request(request, match);
return;
}
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
if (match.domain_equals("update")) { {"update", &WebServer::handle_update_request},
this->handle_update_request(request, match);
return;
}
#endif #endif
};
// Check each route
for (const auto &route : routes) {
if (match.domain_equals(route.domain)) {
(this->*route.handler)(request, match);
return;
}
}
// No matching handler found - send 404 // No matching handler found - send 404
ESP_LOGV(TAG, "Request for unknown URL: %s", request->url().c_str()); ESP_LOGV(TAG, "Request for unknown URL: %s", url.c_str());
request->send(404, "text/plain", "Not Found"); request->send(404, "text/plain", "Not Found");
} }