diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index bf65ae67c0..893e4692bb 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -43,7 +43,11 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) { scan.get_with_auth()); #endif } +#ifdef USE_WEBSERVER + stream->print(ESPHOME_F("],\"web_server\":true}")); +#else stream->print(ESPHOME_F("]}")); +#endif request->send(stream); } void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { @@ -71,7 +75,8 @@ void CaptivePortal::setup() { void CaptivePortal::start() { this->base_->init(); if (!this->initialized_) { - this->base_->add_handler(this); + // Use fallback position so web_server handlers are checked first + this->base_->add_handler(this, web_server_base::HandlerPosition::FALLBACK); } network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index 0c63a3670a..94bcb2f942 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -46,9 +46,10 @@ class CaptivePortal : public AsyncWebHandler, public Component { } bool canHandle(AsyncWebServerRequest *request) const override { - // Handle all GET requests when captive portal is active + // Handle all GET requests when captive portal is active. // This allows us to respond with the portal page for any URL, - // triggering OS captive portal detection + // triggering OS captive portal detection. + // We use add_fallback_handler() so web_server handlers are checked first. return this->active_ && request->method() == HTTP_GET; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e5705d7b47..a11d702f94 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -28,6 +28,10 @@ #include "esphome/components/climate/climate.h" #endif +#ifdef USE_CAPTIVE_PORTAL +#include "esphome/components/captive_portal/captive_portal.h" +#endif + #ifdef USE_WEBSERVER_LOCAL #if USE_WEBSERVER_VERSION == 2 #include "server_index_v2.h" @@ -1957,9 +1961,19 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const { const auto &url = request->url(); const auto method = request->method(); - // Static URL checks + // Handle root URL + if (url == ESPHOME_F("/")) { +#ifdef USE_CAPTIVE_PORTAL + // When captive portal is active, only handle "/" if ?web_server param is present + if (captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active()) { + return request->hasParam(ESPHOME_F("web_server")); + } +#endif + return true; + } + + // Other static URL checks static const char *const STATIC_URLS[] = { - "/", #if !defined(USE_ESP32) && defined(USE_ARDUINO) "/events", #endif diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 6e7097338c..a585b9d3d1 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -11,15 +11,18 @@ static const char *const TAG = "web_server_base"; WebServerBase *global_web_server_base = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -void WebServerBase::add_handler(AsyncWebHandler *handler) { - // remove all handlers - +void WebServerBase::add_handler(AsyncWebHandler *handler, HandlerPosition position) { #ifdef USE_WEBSERVER_AUTH - if (!credentials_.username.empty()) { + if (position != HandlerPosition::FALLBACK && !credentials_.username.empty()) { handler = new internal::AuthMiddlewareHandler(handler, &credentials_); } #endif - this->handlers_.push_back(handler); + if (position == HandlerPosition::FALLBACK) { + this->handlers_.push_back(handler); + } else { + this->handlers_.insert(this->handlers_.begin() + this->fallback_start_, handler); + this->fallback_start_++; + } if (this->server_ != nullptr) { this->server_->addHandler(handler); } diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 7e95e00f29..d014b60d87 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -91,6 +91,11 @@ class AuthMiddlewareHandler : public MiddlewareHandler { } // namespace internal +enum class HandlerPosition : uint8_t { + NORMAL, ///< Before fallback handlers (default) + FALLBACK ///< After normal handlers (catch-all) +}; + class WebServerBase : public Component { public: void init() { @@ -122,7 +127,7 @@ class WebServerBase : public Component { void set_auth_password(std::string auth_password) { credentials_.password = std::move(auth_password); } #endif - void add_handler(AsyncWebHandler *handler); + void add_handler(AsyncWebHandler *handler, HandlerPosition position = HandlerPosition::NORMAL); void set_port(uint16_t port) { port_ = port; } uint16_t get_port() const { return port_; } @@ -132,6 +137,7 @@ class WebServerBase : public Component { uint16_t port_{80}; std::unique_ptr server_{nullptr}; std::vector handlers_; + size_t fallback_start_{0}; ///< Index where fallback handlers begin #ifdef USE_WEBSERVER_AUTH internal::Credentials credentials_; #endif