From 12a670f0015e27ff71c44c0df1aaa71cdc18643c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:11:43 +0000 Subject: [PATCH 1/6] Fix HTTP method conflicts with Arduino core: use HTTP_Method.h enum and list-based composite --- examples/HTTPMethods/HTTPMethods.ino | 17 ++- src/AsyncJson.cpp | 15 +- src/ESPAsyncWebServer.h | 210 +++++++++++++++++++++------ src/Middleware.cpp | 2 +- src/WebHandlerImpl.h | 2 +- src/WebHandlers.cpp | 2 +- src/WebRequest.cpp | 130 ++++++++--------- src/WebServer.cpp | 2 + src/literals.h | 19 +++ 9 files changed, 272 insertions(+), 127 deletions(-) diff --git a/examples/HTTPMethods/HTTPMethods.ino b/examples/HTTPMethods/HTTPMethods.ino index f9864cbf1..ce63d4b05 100644 --- a/examples/HTTPMethods/HTTPMethods.ino +++ b/examples/HTTPMethods/HTTPMethods.ino @@ -23,8 +23,6 @@ #include #endif -#define ASYNCWEBSERVER_NO_GLOBAL_HTTP_METHODS 1 -#undef HTTP_ANY #include static AsyncWebServer server(80); @@ -37,14 +35,19 @@ void setup() { WiFi.softAP("esp-captive"); #endif - // curl -v http://192.168.4.1/get-or-post - // curl -v -X POST -d "a=b" http://192.168.4.1/get-or-post - server.on("/get-or-post", WebRequestMethod::HTTP_GET | WebRequestMethod::HTTP_POST, [](AsyncWebServerRequest *request) { + // curl -v http://192.168.4.1/get-or-post => Hello + // curl -v -X POST -d "a=b" http://192.168.4.1/get-or-post => Hello + // curl -v -X PUT -d "a=b" http://192.168.4.1/get-or-post => 404 + // curl -v -X PATCH -d "a=b" http://192.168.4.1/get-or-post => 404 + server.on("/get-or-post", HTTP_GET | HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200, "text/plain", "Hello"); }); - // curl -v http://192.168.4.1/any - server.on("/any", WebRequestMethod::HTTP_ANY, [](AsyncWebServerRequest *request) { + // curl -v http://192.168.4.1/any => Hello + // curl -v -X POST -d "a=b" http://192.168.4.1/any => Hello + // curl -v -X PUT -d "a=b" http://192.168.4.1/any => Hello + // curl -v -X PATCH -d "a=b" http://192.168.4.1/any => Hello + server.on("/any", HTTP_ANY, [](AsyncWebServerRequest *request) { request->send(200, "text/plain", "Hello"); }); diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index 750b1bc73..a85536c26 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -115,14 +115,11 @@ size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) { #if ARDUINOJSON_VERSION_MAJOR == 6 AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize) - : _uri(std::move(uri)), - _method(AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH), - _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} + : _uri(std::move(uri)), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), + _maxContentLength(16384) {} #else AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest) - : _uri(std::move(uri)), - _method(AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH), - _onRequest(onRequest), _maxContentLength(16384) {} + : _uri(std::move(uri)), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} #endif bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const { @@ -135,17 +132,17 @@ bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) cons } #if ASYNC_MSG_PACK_SUPPORT == 1 - return request->method() == AsyncWebRequestMethod::HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json) + return request->method() == HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json) || request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack); #else - return request->method() == AsyncWebRequestMethod::HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json); + return request->method() == HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json); #endif } void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) { if (_onRequest) { // GET request: - if (request->method() == AsyncWebRequestMethod::HTTP_GET) { + if (request->method() == HTTP_GET) { JsonVariant json; _onRequest(request, json); return; diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 76251d89d..0cd62a44d 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -45,6 +45,72 @@ #error Platform not supported #endif +// HTTP method types from the platform's HTTP parser library. +// Arduino ESP32 core provides HTTP_Method.h which typedef's http_method as +// HTTPMethod and defines HTTP_ANY = (HTTPMethod)(255) as the "match any" sentinel. +// Fall back to the raw http_parser.h (always available via the TCP library) or, +// as a last resort, to a fully inline fallback definition. +#if __has_include() +#include +// HTTP_Method.h provides: typedef enum http_method HTTPMethod; +// and: #define HTTP_ANY (HTTPMethod)(255) +#elif __has_include() +#include +// http_parser.h provides enum http_method but not the HTTP_ANY sentinel. +// Define the sentinel here, matching the value used by Arduino's HTTP_Method.h. +#ifndef HTTP_ANY +#define HTTP_ANY ((http_method)(255)) +#endif +#else +// Full fallback for toolchains that expose neither header. +// Enum values match the llhttp/http_parser spec used by all supported platforms. +typedef enum { + HTTP_DELETE = 0, + HTTP_GET = 1, + HTTP_HEAD = 2, + HTTP_POST = 3, + HTTP_PUT = 4, + /* pathological */ + HTTP_CONNECT = 5, + HTTP_OPTIONS = 6, + HTTP_TRACE = 7, + /* WebDAV */ + HTTP_COPY = 8, + HTTP_LOCK = 9, + HTTP_MKCOL = 10, + HTTP_MOVE = 11, + HTTP_PROPFIND = 12, + HTTP_PROPPATCH = 13, + HTTP_SEARCH = 14, + HTTP_UNLOCK = 15, + HTTP_BIND = 16, + HTTP_REBIND = 17, + HTTP_UNBIND = 18, + HTTP_ACL = 19, + /* subversion */ + HTTP_REPORT = 20, + HTTP_MKACTIVITY = 21, + HTTP_CHECKOUT = 22, + HTTP_MERGE = 23, + /* upnp */ + HTTP_MSEARCH = 24, + HTTP_NOTIFY = 25, + HTTP_SUBSCRIBE = 26, + HTTP_UNSUBSCRIBE = 27, + /* RFC-5789 */ + HTTP_PATCH = 28, + HTTP_PURGE = 29, + /* CalDAV */ + HTTP_MKCALENDAR = 30, + /* RFC-2068, section 19.6.1.2 */ + HTTP_LINK = 31, + HTTP_UNLINK = 32, + /* icecast */ + HTTP_SOURCE = 33, +} http_method; +#define HTTP_ANY ((http_method)(255)) +#endif + #include "AsyncWebServerVersion.h" #define ASYNCWEBSERVER_FORK_ESP32Async @@ -78,44 +144,97 @@ class AsyncCallbackWebHandler; class AsyncResponseStream; class AsyncMiddlewareChain; -// Namespace for web request method defines -namespace AsyncWebRequestMethod { -// The long name here is because we sometimes include this in the global namespace -enum AsyncWebRequestMethodType { - HTTP_GET = 0b0000000000000001, - HTTP_POST = 0b0000000000000010, - HTTP_DELETE = 0b0000000000000100, - HTTP_PUT = 0b0000000000001000, - HTTP_PATCH = 0b0000000000010000, - HTTP_HEAD = 0b0000000000100000, - HTTP_OPTIONS = 0b0000000001000000, - HTTP_PROPFIND = 0b0000000010000000, - HTTP_LOCK = 0b0000000100000000, - HTTP_UNLOCK = 0b0000001000000000, - HTTP_PROPPATCH = 0b0000010000000000, - HTTP_MKCOL = 0b0000100000000000, - HTTP_MOVE = 0b0001000000000000, - HTTP_COPY = 0b0010000000000000, - HTTP_RESERVED = 0b0100000000000000, - HTTP_ANY = 0b0111111111111111, -}; -}; // namespace AsyncWebRequestMethod +// WebRequestMethod: a single HTTP request method, taken directly from the +// platform's http_parser enum. HTTP_GET, HTTP_POST, HTTP_DELETE, HTTP_PUT, +// HTTP_PATCH, HTTP_HEAD, HTTP_OPTIONS, HTTP_PROPFIND, HTTP_LOCK, HTTP_UNLOCK, +// HTTP_PROPPATCH, HTTP_MKCOL, HTTP_MOVE, HTTP_COPY and many others are already +// defined globally by the platform headers above — no redefinition needed here. +// HTTP_ANY (= 255) is the "match any method" sentinel, also from the platform. +typedef http_method WebRequestMethod; + +// WebRequestMethodComposite: an ordered list of HTTP methods that a handler +// accepts. An empty composite matches *nothing*. A composite containing +// HTTP_ANY matches *any* method. +class WebRequestMethodComposite { +public: + // Empty composite = match nothing. + WebRequestMethodComposite() {} -typedef AsyncWebRequestMethod::AsyncWebRequestMethodType WebRequestMethod; -typedef uint16_t WebRequestMethodComposite; + // Single-method composite. + WebRequestMethodComposite(WebRequestMethod m) { + _methods.push_back(m); + } -// Type-safe helper functions for composite methods -extern constexpr inline WebRequestMethodComposite operator|(WebRequestMethodComposite l, WebRequestMethod r) { - return l | static_cast(r); -}; -extern constexpr inline WebRequestMethodComposite operator|(WebRequestMethod l, WebRequestMethod r) { - return static_cast(l) | r; + // Append a method. + WebRequestMethodComposite &add(WebRequestMethod m) { + _methods.push_back(m); + return *this; + } + + // Returns true when this composite contains (or should match) the given + // method. + bool allows(WebRequestMethod m) const { + for (const auto &method : _methods) { + if (method == HTTP_ANY || method == m) { + return true; + } + } + return false; + } + + // Equality: true when the composite holds exactly this one method. + bool operator==(WebRequestMethod m) const { + return _methods.size() == 1 && _methods[0] == m; + } + bool operator!=(WebRequestMethod m) const { + return !(*this == m); + } + + // True when this is an empty (match-nothing) composite. + bool empty() const { + return _methods.empty(); + } + + String toString() const { + if (empty()) { + return "<>"; + } + String result = "<"; + for (size_t i = 0; i < _methods.size(); ++i) { + if (i > 0) { + result += ","; + } + result.concat(_methods[i]); + } + result.concat(">"); + return result; + } + +private: + std::vector _methods; }; -#if !defined(ASYNCWEBSERVER_NO_GLOBAL_HTTP_METHODS) -// Import the method enum values to the global namespace -using namespace AsyncWebRequestMethod; -#endif +// Build a composite from two individual methods: HTTP_GET | HTTP_POST +inline WebRequestMethodComposite operator|(WebRequestMethod l, WebRequestMethod r) { + WebRequestMethodComposite c; + c.add(l).add(r); + return c; +} + +// Extend a composite with one more method: (HTTP_GET | HTTP_POST) | HTTP_PUT +inline WebRequestMethodComposite operator|(WebRequestMethodComposite c, WebRequestMethod r) { + c.add(r); + return c; +} + +// Membership test: returns true when composite c contains method m. +// Usage: if (handler._method & request->method()) { /* matched */ } +inline bool operator&(const WebRequestMethodComposite &c, WebRequestMethod m) { + return c.allows(m); +} +inline bool operator&(WebRequestMethod m, const WebRequestMethodComposite &c) { + return c.allows(m); +} #ifndef HAVE_FS_FILE_OPEN_MODE namespace fs { @@ -188,7 +307,7 @@ class AsyncWebHeader { [[deprecated("Use AsyncWebHeader::parse(data) instead")]] #endif AsyncWebHeader(const String &data) - : AsyncWebHeader(parse(data)){}; + : AsyncWebHeader(parse(data)) {}; AsyncWebHeader &operator=(const AsyncWebHeader &) = default; AsyncWebHeader &operator=(AsyncWebHeader &&other) = default; @@ -265,7 +384,7 @@ class AsyncWebServerRequest { uint8_t _parseState; uint8_t _version; - WebRequestMethodComposite _method; + WebRequestMethod _method; String _url; String _host; String _contentType; @@ -355,7 +474,7 @@ class AsyncWebServerRequest { uint8_t version() const { return _version; } - WebRequestMethodComposite method() const { + WebRequestMethod method() const { return _method; } const String &url() const { @@ -374,19 +493,21 @@ class AsyncWebServerRequest { return _isMultipart; } + const char *methodToString(WebRequestMethod method) const; const char *methodToString() const; const char *requestedConnTypeToString() const; RequestedConnectionType requestedConnType() const { return _reqconntype; } - bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED) - const; + bool isExpectedRequestedConnType( + RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED + ) const; bool isWebSocketUpgrade() const { - return _method == AsyncWebRequestMethod::HTTP_GET && isExpectedRequestedConnType(RCT_WS); + return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS); } bool isSSE() const { - return _method == AsyncWebRequestMethod::HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); + return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); } bool isHTTP() const { return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP); @@ -574,7 +695,8 @@ class AsyncWebServerRequest { return beginResponse(code, contentType.c_str(), content, len, callback); } #ifndef ESP8266 - [[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)" + [[deprecated( + "Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)" )]] #endif AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); @@ -964,6 +1086,8 @@ class AsyncURIMatcher { #endif private: + friend class AsyncWebServer; + // fields String _value; union { @@ -1556,7 +1680,7 @@ class AsyncWebServer : public AsyncMiddlewareChain { bool removeHandler(AsyncWebHandler *handler); AsyncCallbackWebHandler &on(AsyncURIMatcher uri, ArRequestHandlerFunction onRequest) { - return on(std::move(uri), AsyncWebRequestMethod::HTTP_ANY, onRequest); + return on(std::move(uri), HTTP_ANY, onRequest); } AsyncCallbackWebHandler &on( AsyncURIMatcher uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr, diff --git a/src/Middleware.cpp b/src/Middleware.cpp index 42c05af9a..94a720ff8 100644 --- a/src/Middleware.cpp +++ b/src/Middleware.cpp @@ -249,7 +249,7 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext n // Origin header ? => CORS handling if (request->hasHeader(asyncsrv::T_CORS_O)) { // check if this is a preflight request => handle it and return - if (request->method() == AsyncWebRequestMethod::HTTP_OPTIONS) { + if (request->method() == HTTP_OPTIONS) { AsyncWebServerResponse *response = request->beginResponse(200); addCORSHeaders(request, response); request->send(response); diff --git a/src/WebHandlerImpl.h b/src/WebHandlerImpl.h index dac40c6ee..5332e795d 100644 --- a/src/WebHandlerImpl.h +++ b/src/WebHandlerImpl.h @@ -62,7 +62,7 @@ class AsyncCallbackWebHandler : public AsyncWebHandler { bool _isRegex; public: - AsyncCallbackWebHandler() : _uri(), _method(AsyncWebRequestMethod::HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} + AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} void setUri(AsyncURIMatcher uri); void setMethod(WebRequestMethodComposite method) { _method = method; diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index 7439f53fa..dd088322b 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -103,7 +103,7 @@ AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() { } bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) const { - return request->isHTTP() && request->method() == AsyncWebRequestMethod::HTTP_GET && request->url().startsWith(_uri) && _getFile(request); + return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request); } bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) const { diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 3f38fc9d9..4ccac9549 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -39,12 +39,11 @@ enum { }; AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c) - : _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), - _method(AsyncWebRequestMethod::HTTP_ANY), _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), - _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), - _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), - _itemBufferIndex(0), _itemIsFile(false), _chunkStartIndex(0), _chunkOffset(0), _chunkSize(0), _chunkedParseState(CHUNK_NONE), _chunkedLastChar(0), - _tempObject(NULL) { + : _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), + _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), + _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), + _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _chunkStartIndex(0), + _chunkOffset(0), _chunkSize(0), _chunkedParseState(CHUNK_NONE), _chunkedLastChar(0), _tempObject(NULL) { c->onError( [](void *r, AsyncClient *c, int8_t error) { (void)c; @@ -315,33 +314,33 @@ bool AsyncWebServerRequest::_parseReqHead() { _temp = _temp.substring(index + 1); if (m == T_GET) { - _method = AsyncWebRequestMethod::HTTP_GET; + _method = HTTP_GET; } else if (m == T_POST) { - _method = AsyncWebRequestMethod::HTTP_POST; + _method = HTTP_POST; } else if (m == T_DELETE) { - _method = AsyncWebRequestMethod::HTTP_DELETE; + _method = HTTP_DELETE; } else if (m == T_PUT) { - _method = AsyncWebRequestMethod::HTTP_PUT; + _method = HTTP_PUT; } else if (m == T_PATCH) { - _method = AsyncWebRequestMethod::HTTP_PATCH; + _method = HTTP_PATCH; } else if (m == T_HEAD) { - _method = AsyncWebRequestMethod::HTTP_HEAD; + _method = HTTP_HEAD; } else if (m == T_OPTIONS) { - _method = AsyncWebRequestMethod::HTTP_OPTIONS; + _method = HTTP_OPTIONS; } else if (m == T_PROPFIND) { - _method = AsyncWebRequestMethod::HTTP_PROPFIND; + _method = HTTP_PROPFIND; } else if (m == T_LOCK) { - _method = AsyncWebRequestMethod::HTTP_LOCK; + _method = HTTP_LOCK; } else if (m == T_UNLOCK) { - _method = AsyncWebRequestMethod::HTTP_UNLOCK; + _method = HTTP_UNLOCK; } else if (m == T_PROPPATCH) { - _method = AsyncWebRequestMethod::HTTP_PROPPATCH; + _method = HTTP_PROPPATCH; } else if (m == T_MKCOL) { - _method = AsyncWebRequestMethod::HTTP_MKCOL; + _method = HTTP_MKCOL; } else if (m == T_MOVE) { - _method = AsyncWebRequestMethod::HTTP_MOVE; + _method = HTTP_MOVE; } else if (m == T_COPY) { - _method = AsyncWebRequestMethod::HTTP_COPY; + _method = HTTP_COPY; } else { return false; } @@ -1312,52 +1311,53 @@ String AsyncWebServerRequest::urlDecode(const String &text) const { } const char *AsyncWebServerRequest::methodToString() const { - if (_method == AsyncWebRequestMethod::HTTP_ANY) { - return T_ANY; - } - if (_method & AsyncWebRequestMethod::HTTP_GET) { - return T_GET; - } - if (_method & AsyncWebRequestMethod::HTTP_POST) { - return T_POST; - } - if (_method & AsyncWebRequestMethod::HTTP_DELETE) { - return T_DELETE; - } - if (_method & AsyncWebRequestMethod::HTTP_PUT) { - return T_PUT; - } - if (_method & AsyncWebRequestMethod::HTTP_PATCH) { - return T_PATCH; - } - if (_method & AsyncWebRequestMethod::HTTP_HEAD) { - return T_HEAD; - } - if (_method & AsyncWebRequestMethod::HTTP_OPTIONS) { - return T_OPTIONS; - } - if (_method & AsyncWebRequestMethod::HTTP_PROPFIND) { - return T_PROPFIND; - } - if (_method & AsyncWebRequestMethod::HTTP_LOCK) { - return T_LOCK; - } - if (_method & AsyncWebRequestMethod::HTTP_UNLOCK) { - return T_UNLOCK; - } - if (_method & AsyncWebRequestMethod::HTTP_PROPPATCH) { - return T_PROPPATCH; - } - if (_method & AsyncWebRequestMethod::HTTP_MKCOL) { - return T_MKCOL; - } - if (_method & AsyncWebRequestMethod::HTTP_MOVE) { - return T_MOVE; - } - if (_method & AsyncWebRequestMethod::HTTP_COPY) { - return T_COPY; + return methodToString(_method); +} + +const char *AsyncWebServerRequest::methodToString(WebRequestMethod method) const { + switch (method) { + case HTTP_DELETE: return T_DELETE; + case HTTP_GET: return T_GET; + case HTTP_HEAD: return T_HEAD; + case HTTP_POST: return T_POST; + case HTTP_PUT: return T_PUT; + /* pathological */ + case HTTP_CONNECT: return T_CONNECT; + case HTTP_OPTIONS: return T_OPTIONS; + case HTTP_TRACE: return T_TRACE; + /* WebDAV */ + case HTTP_COPY: return T_COPY; + case HTTP_LOCK: return T_LOCK; + case HTTP_MKCOL: return T_MKCOL; + case HTTP_MOVE: return T_MOVE; + case HTTP_PROPFIND: return T_PROPFIND; + case HTTP_PROPPATCH: return T_PROPPATCH; + case HTTP_SEARCH: return T_SEARCH; + case HTTP_UNLOCK: return T_UNLOCK; + case HTTP_BIND: return T_BIND; + case HTTP_REBIND: return T_REBIND; + case HTTP_UNBIND: return T_UNBIND; + case HTTP_ACL: return T_ACL; + /* subversion */ + case HTTP_REPORT: return T_REPORT; + case HTTP_MKACTIVITY: return T_MKACTIVITY; + case HTTP_CHECKOUT: return T_CHECKOUT; + case HTTP_MERGE: return T_MERGE; + /* upnp */ + case HTTP_MSEARCH: return T_MSEARCH; + case HTTP_NOTIFY: return T_NOTIFY; + case HTTP_SUBSCRIBE: return T_SUBSCRIBE; + case HTTP_UNSUBSCRIBE: return T_UNSUBSCRIBE; + /* RFC-5789 */ + case HTTP_PATCH: return T_PATCH; + case HTTP_PURGE: return T_PURGE; + /* CalDAV */ + case HTTP_MKCALENDAR: return T_MKCALENDAR; + /* RFC-2068, section 19.6.1.2 */ + case HTTP_LINK: return T_LINK; + case HTTP_UNLINK: return T_UNLINK; + default: return T_UNKNOWN; } - return T_UNKNOWN; } const char *AsyncWebServerRequest::requestedConnTypeToString() const { diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 2ab5bcd4e..e95c03a4d 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -3,6 +3,7 @@ #include "ESPAsyncWebServer.h" #include "WebHandlerImpl.h" +#include "AsyncWebServerLogging.h" #include #include @@ -156,6 +157,7 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) { AsyncCallbackWebHandler &AsyncWebServer::on( AsyncURIMatcher uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody ) { + async_ws_log_v("+Handler: %s %s", uri._value.c_str(), method.toString().c_str()); AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler(); handler->setUri(std::move(uri)); handler->setMethod(method); diff --git a/src/literals.h b/src/literals.h index 71cf12748..9799e6240 100644 --- a/src/literals.h +++ b/src/literals.h @@ -119,6 +119,25 @@ static constexpr const char T_PROPPATCH[] = "PROPPATCH"; static constexpr const char T_MKCOL[] = "MKCOL"; static constexpr const char T_MOVE[] = "MOVE"; static constexpr const char T_COPY[] = "COPY"; +static constexpr const char T_CONNECT[] = "CONNECT"; +static constexpr const char T_TRACE[] = "TRACE"; +static constexpr const char T_SEARCH[] = "SEARCH"; +static constexpr const char T_BIND[] = "BIND"; +static constexpr const char T_REBIND[] = "REBIND"; +static constexpr const char T_UNBIND[] = "UNBIND"; +static constexpr const char T_ACL[] = "ACL"; +static constexpr const char T_REPORT[] = "REPORT"; +static constexpr const char T_MKACTIVITY[] = "MKACTIVITY"; +static constexpr const char T_CHECKOUT[] = "CHECKOUT"; +static constexpr const char T_MERGE[] = "MERGE"; +static constexpr const char T_MSEARCH[] = "M-SEARCH"; +static constexpr const char T_NOTIFY[] = "NOTIFY"; +static constexpr const char T_SUBSCRIBE[] = "SUBSCRIBE"; +static constexpr const char T_UNSUBSCRIBE[] = "UNSUBSCRIBE"; +static constexpr const char T_PURGE[] = "PURGE"; +static constexpr const char T_MKCALENDAR[] = "MKCALENDAR"; +static constexpr const char T_LINK[] = "LINK"; +static constexpr const char T_UNLINK[] = "UNLINK"; static constexpr const char T_UNKNOWN[] = "UNKNOWN"; // Req content types From d4541fca0cf6d9e7210ff8c6265883bd9c2cfb7c Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Sat, 14 Mar 2026 13:57:04 +0100 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/WebRequest.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 4ccac9549..b392aa152 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -1356,6 +1356,7 @@ const char *AsyncWebServerRequest::methodToString(WebRequestMethod method) const /* RFC-2068, section 19.6.1.2 */ case HTTP_LINK: return T_LINK; case HTTP_UNLINK: return T_UNLINK; + case HTTP_ANY: return T_ANY; default: return T_UNKNOWN; } } From 9b417484832ad45ebb6625d002367447b2b32257 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:57:52 +0000 Subject: [PATCH 3/6] ci(pre-commit): Apply automatic fixes --- src/ESPAsyncWebServer.h | 10 +++---- src/WebRequest.cpp | 62 ++++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 0cd62a44d..00e03b908 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -307,7 +307,7 @@ class AsyncWebHeader { [[deprecated("Use AsyncWebHeader::parse(data) instead")]] #endif AsyncWebHeader(const String &data) - : AsyncWebHeader(parse(data)) {}; + : AsyncWebHeader(parse(data)){}; AsyncWebHeader &operator=(const AsyncWebHeader &) = default; AsyncWebHeader &operator=(AsyncWebHeader &&other) = default; @@ -500,9 +500,8 @@ class AsyncWebServerRequest { RequestedConnectionType requestedConnType() const { return _reqconntype; } - bool isExpectedRequestedConnType( - RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED - ) const; + bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED) + const; bool isWebSocketUpgrade() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS); } @@ -695,8 +694,7 @@ class AsyncWebServerRequest { return beginResponse(code, contentType.c_str(), content, len, callback); } #ifndef ESP8266 - [[deprecated( - "Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)" + [[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)" )]] #endif AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index b392aa152..73ca70a36 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -1316,48 +1316,48 @@ const char *AsyncWebServerRequest::methodToString() const { const char *AsyncWebServerRequest::methodToString(WebRequestMethod method) const { switch (method) { - case HTTP_DELETE: return T_DELETE; - case HTTP_GET: return T_GET; - case HTTP_HEAD: return T_HEAD; - case HTTP_POST: return T_POST; - case HTTP_PUT: return T_PUT; + case HTTP_DELETE: return T_DELETE; + case HTTP_GET: return T_GET; + case HTTP_HEAD: return T_HEAD; + case HTTP_POST: return T_POST; + case HTTP_PUT: return T_PUT; /* pathological */ - case HTTP_CONNECT: return T_CONNECT; - case HTTP_OPTIONS: return T_OPTIONS; - case HTTP_TRACE: return T_TRACE; + case HTTP_CONNECT: return T_CONNECT; + case HTTP_OPTIONS: return T_OPTIONS; + case HTTP_TRACE: return T_TRACE; /* WebDAV */ - case HTTP_COPY: return T_COPY; - case HTTP_LOCK: return T_LOCK; - case HTTP_MKCOL: return T_MKCOL; - case HTTP_MOVE: return T_MOVE; - case HTTP_PROPFIND: return T_PROPFIND; + case HTTP_COPY: return T_COPY; + case HTTP_LOCK: return T_LOCK; + case HTTP_MKCOL: return T_MKCOL; + case HTTP_MOVE: return T_MOVE; + case HTTP_PROPFIND: return T_PROPFIND; case HTTP_PROPPATCH: return T_PROPPATCH; - case HTTP_SEARCH: return T_SEARCH; - case HTTP_UNLOCK: return T_UNLOCK; - case HTTP_BIND: return T_BIND; - case HTTP_REBIND: return T_REBIND; - case HTTP_UNBIND: return T_UNBIND; - case HTTP_ACL: return T_ACL; + case HTTP_SEARCH: return T_SEARCH; + case HTTP_UNLOCK: return T_UNLOCK; + case HTTP_BIND: return T_BIND; + case HTTP_REBIND: return T_REBIND; + case HTTP_UNBIND: return T_UNBIND; + case HTTP_ACL: return T_ACL; /* subversion */ - case HTTP_REPORT: return T_REPORT; + case HTTP_REPORT: return T_REPORT; case HTTP_MKACTIVITY: return T_MKACTIVITY; - case HTTP_CHECKOUT: return T_CHECKOUT; - case HTTP_MERGE: return T_MERGE; + case HTTP_CHECKOUT: return T_CHECKOUT; + case HTTP_MERGE: return T_MERGE; /* upnp */ - case HTTP_MSEARCH: return T_MSEARCH; - case HTTP_NOTIFY: return T_NOTIFY; - case HTTP_SUBSCRIBE: return T_SUBSCRIBE; + case HTTP_MSEARCH: return T_MSEARCH; + case HTTP_NOTIFY: return T_NOTIFY; + case HTTP_SUBSCRIBE: return T_SUBSCRIBE; case HTTP_UNSUBSCRIBE: return T_UNSUBSCRIBE; /* RFC-5789 */ - case HTTP_PATCH: return T_PATCH; - case HTTP_PURGE: return T_PURGE; + case HTTP_PATCH: return T_PATCH; + case HTTP_PURGE: return T_PURGE; /* CalDAV */ case HTTP_MKCALENDAR: return T_MKCALENDAR; /* RFC-2068, section 19.6.1.2 */ - case HTTP_LINK: return T_LINK; - case HTTP_UNLINK: return T_UNLINK; - case HTTP_ANY: return T_ANY; - default: return T_UNKNOWN; + case HTTP_LINK: return T_LINK; + case HTTP_UNLINK: return T_UNLINK; + case HTTP_ANY: return T_ANY; + default: return T_UNKNOWN; } } From 7a7a648b6c9f00b8f7936ec56e259431c2bfe213 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Sat, 14 Mar 2026 13:58:18 +0100 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/WebRequest.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 73ca70a36..5e57082d0 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -341,6 +341,12 @@ bool AsyncWebServerRequest::_parseReqHead() { _method = HTTP_MOVE; } else if (m == T_COPY) { _method = HTTP_COPY; + } else if (m == T_CONNECT) { + _method = HTTP_CONNECT; + } else if (m == T_TRACE) { + _method = HTTP_TRACE; + } else if (m == T_SEARCH) { + _method = HTTP_SEARCH; } else { return false; } From 1725e51655dbffff491ba73c35ddd58aaa39548d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:08:46 +0000 Subject: [PATCH 5/6] Initial plan From 796e2b8da9049845afb0cd9b5d589663a08f3e88 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:15:23 +0000 Subject: [PATCH 6/6] Replace std::vector with uint64_t bitset in WebRequestMethodComposite Co-authored-by: mathieucarbou <61346+mathieucarbou@users.noreply.github.com> --- src/ESPAsyncWebServer.h | 70 +++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 00e03b908..48ef75265 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -152,39 +152,49 @@ class AsyncMiddlewareChain; // HTTP_ANY (= 255) is the "match any method" sentinel, also from the platform. typedef http_method WebRequestMethod; -// WebRequestMethodComposite: an ordered list of HTTP methods that a handler -// accepts. An empty composite matches *nothing*. A composite containing -// HTTP_ANY matches *any* method. +// WebRequestMethodComposite: a compact, allocation-free set of HTTP methods +// that a handler accepts. Internally stored as a 64-bit bitmask (one bit per +// http_method enum value, which spans 0–33 in the current llhttp spec) plus a +// single boolean sentinel for HTTP_ANY (= 255, the "match anything" value). +// A uint64_t bitmask supports up to 64 distinct enum values; the http_method +// enum currently defines 34 values (0–33), so all methods fit with room to +// spare. An empty composite (no bits set, _any == false) matches *nothing*. +// A composite with _any == true matches *any* method. class WebRequestMethodComposite { public: // Empty composite = match nothing. - WebRequestMethodComposite() {} + WebRequestMethodComposite() : _bits(0), _any(false) {} // Single-method composite. - WebRequestMethodComposite(WebRequestMethod m) { - _methods.push_back(m); + WebRequestMethodComposite(WebRequestMethod m) : _bits(0), _any(false) { + _set(m); } // Append a method. WebRequestMethodComposite &add(WebRequestMethod m) { - _methods.push_back(m); + _set(m); return *this; } // Returns true when this composite contains (or should match) the given // method. bool allows(WebRequestMethod m) const { - for (const auto &method : _methods) { - if (method == HTTP_ANY || method == m) { - return true; - } - } - return false; + if (_any) + return true; + if (m == HTTP_ANY) + return false; // HTTP_ANY is not a real request method; only matches when _any is set + const unsigned idx = static_cast(m); + return idx < 64 && (_bits & (1ULL << idx)) != 0; } // Equality: true when the composite holds exactly this one method. bool operator==(WebRequestMethod m) const { - return _methods.size() == 1 && _methods[0] == m; + if (m == HTTP_ANY) + return _any && _bits == 0; + const unsigned idx = static_cast(m); + if (idx >= 64) + return false; + return !_any && _bits == (1ULL << idx); } bool operator!=(WebRequestMethod m) const { return !(*this == m); @@ -192,7 +202,7 @@ class WebRequestMethodComposite { // True when this is an empty (match-nothing) composite. bool empty() const { - return _methods.empty(); + return _bits == 0 && !_any; } String toString() const { @@ -200,18 +210,38 @@ class WebRequestMethodComposite { return "<>"; } String result = "<"; - for (size_t i = 0; i < _methods.size(); ++i) { - if (i > 0) { - result += ","; + bool first = true; + if (_any) { + result.concat(static_cast(HTTP_ANY)); + first = false; + } + for (unsigned i = 0; i < 64; ++i) { + if (_bits & (1ULL << i)) { + if (!first) { + result += ","; + } + result.concat(static_cast(i)); + first = false; } - result.concat(_methods[i]); } result.concat(">"); return result; } private: - std::vector _methods; + uint64_t _bits; + bool _any; + + void _set(WebRequestMethod m) { + if (m == HTTP_ANY) { + _any = true; + } else { + const unsigned idx = static_cast(m); + if (idx < 64) { + _bits |= (1ULL << idx); + } + } + } }; // Build a composite from two individual methods: HTTP_GET | HTTP_POST