Skip to content

Commit 3df830d

Browse files
committed
Introduce AsyncURIMatcher class to encapsulate the URI matching logic with and without regex support (-D ASYNCWEBSERVER_REGEX=1)
1 parent 04eeea4 commit 3df830d

File tree

6 files changed

+120
-53
lines changed

6 files changed

+120
-53
lines changed

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: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class AsyncStaticWebHandler;
7070
class AsyncCallbackWebHandler;
7171
class AsyncResponseStream;
7272
class AsyncMiddlewareChain;
73+
class AsyncURIMatcher;
7374

7475
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
7576
typedef enum http_method WebRequestMethod;
@@ -220,6 +221,7 @@ class AsyncWebServerRequest {
220221
friend class AsyncCallbackWebHandler;
221222
friend class AsyncFileResponse;
222223
friend class AsyncStaticWebHandler;
224+
friend class AsyncURIMatcher;
223225

224226
private:
225227
AsyncClient *_client;
@@ -729,6 +731,103 @@ class AsyncWebServerRequest {
729731
String urlDecode(const String &text) const;
730732
};
731733

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

1264-
AsyncCallbackWebHandler &on(const char *uri, ArRequestHandlerFunction onRequest) {
1265-
return on(uri, HTTP_ANY, onRequest);
1363+
AsyncCallbackWebHandler &on(AsyncURIMatcher uri, ArRequestHandlerFunction onRequest) {
1364+
return on(std::move(uri), HTTP_ANY, onRequest);
12661365
}
12671366
AsyncCallbackWebHandler &on(
1268-
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr,
1367+
AsyncURIMatcher uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr,
12691368
ArBodyHandlerFunction onBody = nullptr
12701369
);
12711370

12721371
#if ASYNC_JSON_SUPPORT == 1
1273-
AsyncCallbackJsonWebHandler &on(const char *uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onBody);
1372+
AsyncCallbackJsonWebHandler &on(AsyncURIMatcher uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onBody);
12741373
#endif
12751374

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

src/WebHandlerImpl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
5454
class AsyncCallbackWebHandler : public AsyncWebHandler {
5555
private:
5656
protected:
57-
String _uri;
57+
AsyncURIMatcher _uri;
5858
WebRequestMethodComposite _method;
5959
ArRequestHandlerFunction _onRequest;
6060
ArUploadHandlerFunction _onUpload;
@@ -63,7 +63,7 @@ class AsyncCallbackWebHandler : public AsyncWebHandler {
6363

6464
public:
6565
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
66-
void setUri(const String &uri);
66+
void setUri(AsyncURIMatcher uri);
6767
void setMethod(WebRequestMethodComposite method) {
6868
_method = method;
6969
}

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->_pathParams.emplace_back(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/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)