Skip to content

Commit 9ef3718

Browse files
committed
Introduce AsyncURIMatcher class to encapsulate the URI matching logic with and without regex support (-D ASYNCWEBSERVER_REGEX=1)
1 parent b18bfeb commit 9ef3718

File tree

8 files changed

+136
-81
lines changed

8 files changed

+136
-81
lines changed

platformio.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ build_flags =
5151
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64
5252
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
5353
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096
54+
-D ASYNCWEBSERVER_REGEX=1
5455
; -D CONFIG_ASYNC_TCP_USE_WDT=0
5556
; -D CONFIG_ARDUHAL_LOG_COLORS
5657
; -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE

src/AsyncJson.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,19 @@ size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) {
112112
// Body handler supporting both content types: JSON and MessagePack
113113

114114
#if ARDUINOJSON_VERSION_MAJOR == 6
115-
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
116-
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
115+
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
116+
: _uri(std::move(uri)), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
117117
#else
118-
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest)
119-
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
118+
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest)
119+
: _uri(std::move(uri)), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
120120
#endif
121121

122122
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
123123
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
124124
return false;
125125
}
126126

127-
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
127+
if (!_uri.matches(request)) {
128128
return false;
129129
}
130130

src/AsyncJson.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class AsyncMessagePackResponse : public AsyncJsonResponse {
8888

8989
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
9090
protected:
91-
String _uri;
91+
AsyncURIMatcher _uri;
9292
WebRequestMethodComposite _method;
9393
ArJsonRequestHandlerFunction _onRequest;
9494
#if ARDUINOJSON_VERSION_MAJOR == 6
@@ -98,9 +98,9 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
9898

9999
public:
100100
#if ARDUINOJSON_VERSION_MAJOR == 6
101-
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
101+
AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
102102
#else
103-
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
103+
AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest = nullptr);
104104
#endif
105105

106106
void setMethod(WebRequestMethodComposite method) {

src/ESPAsyncWebServer.h

Lines changed: 118 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@
5050
#define ASYNCWEBSERVER_FORK_ESP32Async
5151

5252
#ifdef ASYNCWEBSERVER_REGEX
53-
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
54-
#else
55-
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
53+
#include <regex>
5654
#endif
5755

5856
// See https://github.com/ESP32Async/ESPAsyncWebServer/commit/3d3456e9e81502a477f6498c44d0691499dda8f9#diff-646b25b11691c11dce25529e3abce843f0ba4bd07ab75ec9eee7e72b06dbf13fR388-R392
@@ -72,6 +70,7 @@ class AsyncStaticWebHandler;
7270
class AsyncCallbackWebHandler;
7371
class AsyncResponseStream;
7472
class AsyncMiddlewareChain;
73+
class AsyncURIMatcher;
7574

7675
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
7776
typedef enum http_method WebRequestMethod;
@@ -222,6 +221,7 @@ class AsyncWebServerRequest {
222221
friend class AsyncCallbackWebHandler;
223222
friend class AsyncFileResponse;
224223
friend class AsyncStaticWebHandler;
224+
friend class AsyncURIMatcher;
225225

226226
private:
227227
AsyncClient *_client;
@@ -254,7 +254,9 @@ class AsyncWebServerRequest {
254254

255255
std::list<AsyncWebHeader> _headers;
256256
std::list<AsyncWebParameter> _params;
257+
#ifdef ASYNCWEBSERVER_REGEX
257258
std::list<String> _pathParams;
259+
#endif
258260

259261
std::unordered_map<const char *, String, std::hash<const char *>, std::equal_to<const char *>> _attributes;
260262

@@ -277,8 +279,6 @@ class AsyncWebServerRequest {
277279
void _onDisconnect();
278280
void _onData(void *buf, size_t len);
279281

280-
void _addPathParam(const char *param);
281-
282282
bool _parseReqHead();
283283
bool _parseReqHeader();
284284
void _parseLine();
@@ -615,10 +615,19 @@ class AsyncWebServerRequest {
615615
bool hasArg(const __FlashStringHelper *data) const; // check if F(argument) exists
616616
#endif
617617

618-
const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
619-
const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(int i) const {
618+
#ifdef ASYNCWEBSERVER_REGEX
619+
const String &pathArg(size_t i) const {
620+
if (i >= _pathParams.size()) {
621+
return emptyString;
622+
}
623+
auto it = _pathParams.begin();
624+
std::advance(it, i);
625+
return *it;
626+
}
627+
const String &pathArg(int i) const {
620628
return i < 0 ? emptyString : pathArg((size_t)i);
621629
}
630+
#endif
622631

623632
// get request header value by name
624633
const String &header(const char *name) const;
@@ -719,6 +728,104 @@ class AsyncWebServerRequest {
719728
String urlDecode(const String &text) const;
720729
};
721730

731+
class AsyncURIMatcher {
732+
public:
733+
AsyncURIMatcher() {}
734+
AsyncURIMatcher(const char *uri, bool ignoreCase = false) : _value(uri), _ignoreCase(ignoreCase) {
735+
if (_ignoreCase) {
736+
_value.toLowerCase();
737+
}
738+
#ifdef ASYNCWEBSERVER_REGEX
739+
if (isRegex()) {
740+
pattern = _ignoreCase ? std::regex(_value.c_str(), std::regex::icase) : std::regex(_value.c_str());
741+
}
742+
#endif
743+
}
744+
AsyncURIMatcher(String uri, bool ignoreCase = false) : _value(std::move(uri)), _ignoreCase(ignoreCase) {
745+
if (_ignoreCase) {
746+
_value.toLowerCase();
747+
}
748+
#ifdef ASYNCWEBSERVER_REGEX
749+
if (isRegex()) {
750+
pattern = _ignoreCase ? std::regex(_value.c_str(), std::regex::icase) : std::regex(_value.c_str());
751+
}
752+
#endif
753+
}
754+
755+
AsyncURIMatcher(const AsyncURIMatcher &) = default;
756+
AsyncURIMatcher(AsyncURIMatcher &&) = default;
757+
~AsyncURIMatcher() = default;
758+
759+
AsyncURIMatcher &operator=(const AsyncURIMatcher &) = default;
760+
AsyncURIMatcher &operator=(AsyncURIMatcher &&) = default;
761+
762+
#ifdef ASYNCWEBSERVER_REGEX
763+
bool isRegex() const {
764+
return _value.startsWith("^") && _value.endsWith("$");
765+
}
766+
#endif
767+
768+
bool matches(AsyncWebServerRequest *request) const {
769+
#ifdef ASYNCWEBSERVER_REGEX
770+
if (isRegex()) {
771+
std::smatch matches;
772+
std::string s(request->url().c_str());
773+
if (std::regex_search(s, matches, pattern)) {
774+
for (size_t i = 1; i < matches.size(); ++i) {
775+
request->_pathParams.emplace_back(matches[i].str().c_str());
776+
}
777+
return true;
778+
} else {
779+
return false;
780+
}
781+
}
782+
#endif
783+
784+
// empty URI matches everything
785+
if (!_value.length()) {
786+
return true;
787+
}
788+
789+
String path = request->url();
790+
if (_ignoreCase) {
791+
path.toLowerCase();
792+
}
793+
794+
// exact match (should be the most common case)
795+
if (_value == path) {
796+
return true;
797+
}
798+
799+
// wildcard match with * at the end
800+
if (_value.endsWith("*")) {
801+
return path.startsWith(_value.substring(0, _value.length() - 1));
802+
}
803+
804+
// prefix match with /*.ext
805+
// matches any path ending with .ext
806+
// e.g. /images/*.png will match /images/pic.png and /images/2023/pic.png but not /img/pic.png
807+
if (_value.startsWith("/*.")) {
808+
return path.endsWith(_value.substring(_value.lastIndexOf(".")));
809+
}
810+
811+
// finally check for prefix match with / at the end
812+
// e.g. /images will also match /images/pic.png and /images/2023/pic.png but not /img/pic.png
813+
if (path.startsWith(_value + "/")) {
814+
return true;
815+
}
816+
817+
// we did not match
818+
return false;
819+
}
820+
821+
private:
822+
String _value;
823+
bool _ignoreCase = false;
824+
#ifdef ASYNCWEBSERVER_REGEX
825+
std::regex pattern;
826+
#endif
827+
};
828+
722829
/*
723830
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
724831
* */
@@ -1251,16 +1358,16 @@ class AsyncWebServer : public AsyncMiddlewareChain {
12511358
AsyncWebHandler &addHandler(AsyncWebHandler *handler);
12521359
bool removeHandler(AsyncWebHandler *handler);
12531360

1254-
AsyncCallbackWebHandler &on(const char *uri, ArRequestHandlerFunction onRequest) {
1255-
return on(uri, HTTP_ANY, onRequest);
1361+
AsyncCallbackWebHandler &on(AsyncURIMatcher uri, ArRequestHandlerFunction onRequest) {
1362+
return on(std::move(uri), HTTP_ANY, onRequest);
12561363
}
12571364
AsyncCallbackWebHandler &on(
1258-
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr,
1365+
AsyncURIMatcher uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr,
12591366
ArBodyHandlerFunction onBody = nullptr
12601367
);
12611368

12621369
#if ASYNC_JSON_SUPPORT == 1
1263-
AsyncCallbackJsonWebHandler &on(const char *uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onBody);
1370+
AsyncCallbackJsonWebHandler &on(AsyncURIMatcher uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onBody);
12641371
#endif
12651372

12661373
AsyncStaticWebHandler &serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control = NULL);

src/WebHandlerImpl.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
#define ASYNCWEBSERVERHANDLERIMPL_H_
66

77
#include <string>
8-
#ifdef ASYNCWEBSERVER_REGEX
9-
#include <regex>
10-
#endif
11-
128
#include "stddef.h"
139
#include <time.h>
1410

@@ -58,7 +54,7 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
5854
class AsyncCallbackWebHandler : public AsyncWebHandler {
5955
private:
6056
protected:
61-
String _uri;
57+
AsyncURIMatcher _uri;
6258
WebRequestMethodComposite _method;
6359
ArRequestHandlerFunction _onRequest;
6460
ArUploadHandlerFunction _onUpload;
@@ -67,7 +63,7 @@ class AsyncCallbackWebHandler : public AsyncWebHandler {
6763

6864
public:
6965
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
70-
void setUri(const String &uri);
66+
void setUri(AsyncURIMatcher uri);
7167
void setMethod(WebRequestMethodComposite method) {
7268
_method = method;
7369
}

src/WebHandlers.cpp

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -295,47 +295,15 @@ AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplatePr
295295
return *this;
296296
}
297297

298-
void AsyncCallbackWebHandler::setUri(const String &uri) {
299-
_uri = uri;
300-
_isRegex = uri.startsWith("^") && uri.endsWith("$");
298+
void AsyncCallbackWebHandler::setUri(AsyncURIMatcher uri) {
299+
_uri = std::move(uri);
301300
}
302301

303302
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const {
304303
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
305304
return false;
306305
}
307-
308-
#ifdef ASYNCWEBSERVER_REGEX
309-
if (_isRegex) {
310-
std::regex pattern(_uri.c_str());
311-
std::smatch matches;
312-
std::string s(request->url().c_str());
313-
if (std::regex_search(s, matches, pattern)) {
314-
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
315-
request->_addPathParam(matches[i].str().c_str());
316-
}
317-
} else {
318-
return false;
319-
}
320-
} else
321-
#endif
322-
if (_uri.length() && _uri.startsWith("/*.")) {
323-
String uriTemplate = String(_uri);
324-
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
325-
if (!request->url().endsWith(uriTemplate)) {
326-
return false;
327-
}
328-
} else if (_uri.length() && _uri.endsWith("*")) {
329-
String uriTemplate = String(_uri);
330-
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
331-
if (!request->url().startsWith(uriTemplate)) {
332-
return false;
333-
}
334-
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
335-
return false;
336-
}
337-
338-
return true;
306+
return _uri.matches(request);
339307
}
340308

341309
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest *request) {

src/WebRequest.cpp

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,6 @@ AsyncWebServerRequest::~AsyncWebServerRequest() {
9090

9191
_this.reset();
9292

93-
_headers.clear();
94-
95-
_pathParams.clear();
96-
9793
AsyncWebServerResponse *r = _response;
9894
_response = NULL;
9995
delete r;
@@ -271,10 +267,6 @@ void AsyncWebServerRequest::_onDisconnect() {
271267
_server->_handleDisconnect(this);
272268
}
273269

274-
void AsyncWebServerRequest::_addPathParam(const char *p) {
275-
_pathParams.emplace_back(p);
276-
}
277-
278270
void AsyncWebServerRequest::_addGetParams(const String &params) {
279271
size_t start = 0;
280272
while (start < params.length()) {
@@ -1075,15 +1067,6 @@ const String &AsyncWebServerRequest::argName(size_t i) const {
10751067
return getParam(i)->name();
10761068
}
10771069

1078-
const String &AsyncWebServerRequest::pathArg(size_t i) const {
1079-
if (i >= _pathParams.size()) {
1080-
return emptyString;
1081-
}
1082-
auto it = _pathParams.begin();
1083-
std::advance(it, i);
1084-
return *it;
1085-
}
1086-
10871070
const String &AsyncWebServerRequest::header(const char *name) const {
10881071
const AsyncWebHeader *h = getHeader(name);
10891072
return h ? h->value() : emptyString;

src/WebServer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) {
151151
}
152152

153153
AsyncCallbackWebHandler &AsyncWebServer::on(
154-
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
154+
AsyncURIMatcher uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
155155
) {
156156
AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
157-
handler->setUri(uri);
157+
handler->setUri(std::move(uri));
158158
handler->setMethod(method);
159159
handler->onRequest(onRequest);
160160
handler->onUpload(onUpload);
@@ -164,8 +164,8 @@ AsyncCallbackWebHandler &AsyncWebServer::on(
164164
}
165165

166166
#if ASYNC_JSON_SUPPORT == 1
167-
AsyncCallbackJsonWebHandler &AsyncWebServer::on(const char *uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onBody) {
168-
AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler(uri, onBody);
167+
AsyncCallbackJsonWebHandler &AsyncWebServer::on(AsyncURIMatcher uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onBody) {
168+
AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler(std::move(uri), onBody);
169169
handler->setMethod(method);
170170
addHandler(handler);
171171
return *handler;

0 commit comments

Comments
 (0)