From 6a4e2f532912f229ed8cb4699cd5114ba0b946c3 Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 2 Oct 2019 13:37:55 +0200 Subject: [PATCH] Support route parameters with regex patterns (#560) * Fix compile warnings * first test * Add regex path * Add simple example * add support for esp8266 * Update AsyncJson.h --- examples/regex_patterns/regex_patterns.ino | 65 ++++++++++++++++++++++ src/ESPAsyncWebServer.h | 5 ++ src/WebHandlerImpl.h | 23 +++++++- src/WebRequest.cpp | 11 ++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 examples/regex_patterns/regex_patterns.ino diff --git a/examples/regex_patterns/regex_patterns.ino b/examples/regex_patterns/regex_patterns.ino new file mode 100644 index 0000000..ad66dae --- /dev/null +++ b/examples/regex_patterns/regex_patterns.ino @@ -0,0 +1,65 @@ +// +// A simple server implementation with regex routes: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include + +AsyncWebServer server(80); + +const char* ssid = "YOUR_SSID"; +const char* password = "YOUR_PASSWORD"; + +const char* PARAM_MESSAGE = "message"; + +void notFound(AsyncWebServerRequest *request) { + request->send(404, "text/plain", "Not found"); +} + +void setup() { + + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", "Hello, world"); + }); + + // Send a GET request to /sensor/ + server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { + String sensorNumber = request->pathArg(0); + request->send(200, "text/plain", "Hello, sensor: " + sensorNumber); + }); + + // Send a GET request to /sensor//action/ + server.on("^\\/sensor\\/([0-9]+)\\/action\//([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { + String sensorNumber = request->pathArg(0); + String action = request->pathArg(1); + request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action); + }); + + server.onNotFound(notFound); + + server.begin(); +} + +void loop() { +} diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index cfe1fa7..4f0574a 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -129,6 +129,7 @@ class AsyncWebServerRequest { using File = fs::File; using FS = fs::FS; friend class AsyncWebServer; + friend class AsyncCallbackWebHandler; private: AsyncClient* _client; AsyncWebServer* _server; @@ -158,6 +159,7 @@ class AsyncWebServerRequest { LinkedList _headers; LinkedList _params; + LinkedList _pathParams; uint8_t _multiParseState; uint8_t _boundaryPosition; @@ -179,6 +181,7 @@ class AsyncWebServerRequest { void _onData(void *buf, size_t len); void _addParam(AsyncWebParameter*); + void _addPathParam(const char *param); bool _parseReqHead(); bool _parseReqHeader(); @@ -268,6 +271,8 @@ class AsyncWebServerRequest { bool hasArg(const char* name) const; // check if argument exists bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists + const String& pathArg(size_t i) const; + const String& header(const char* name) const;// get request header value by name const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) const String& header(size_t i) const; // get request header value by number diff --git a/src/WebHandlerImpl.h b/src/WebHandlerImpl.h index 65b580f..5ae7b49 100644 --- a/src/WebHandlerImpl.h +++ b/src/WebHandlerImpl.h @@ -21,6 +21,8 @@ #ifndef ASYNCWEBSERVERHANDLERIMPL_H_ #define ASYNCWEBSERVERHANDLERIMPL_H_ +#include +#include #include "stddef.h" #include @@ -67,9 +69,13 @@ class AsyncCallbackWebHandler: public AsyncWebHandler { ArRequestHandlerFunction _onRequest; ArUploadHandlerFunction _onUpload; ArBodyHandlerFunction _onBody; + bool _isRegex; public: - AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL){} - void setUri(const String& uri){ _uri = uri; } + AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false){} + void setUri(const String& uri){ + _uri = uri; + _isRegex = uri.startsWith("^") && uri.endsWith("$"); + } void setMethod(WebRequestMethodComposite method){ _method = method; } void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } @@ -83,7 +89,18 @@ class AsyncCallbackWebHandler: public AsyncWebHandler { if(!(_method & request->method())) return false; - if (_uri.length() && _uri.endsWith("*")) { + if (_isRegex) { + std::regex rgx(_uri.c_str()); + std::smatch matches; + std::string s(request->url().c_str()); + if(std::regex_search(s, matches, rgx)) { + for (size_t i = 1; i < matches.size(); ++i) { // start from 1 + request->_addPathParam(matches[i].str().c_str()); + } + } else { + return false; + } + } else if (_uri.length() && _uri.endsWith("*")) { String uriTemplate = String(_uri); uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); if (!request->url().startsWith(uriTemplate)) diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index dbcb927..45a0a39 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -55,6 +55,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) , _parsedLength(0) , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) , _params(LinkedList([](AsyncWebParameter *p){ delete p; })) + , _pathParams(LinkedList([](String *p){ delete p; })) , _multiParseState(0) , _boundaryPosition(0) , _itemStartIndex(0) @@ -80,6 +81,7 @@ AsyncWebServerRequest::~AsyncWebServerRequest(){ _headers.free(); _params.free(); + _pathParams.free(); _interestingHeaders.free(); @@ -230,6 +232,10 @@ void AsyncWebServerRequest::_addParam(AsyncWebParameter *p){ _params.add(p); } +void AsyncWebServerRequest::_addPathParam(const char *p){ + _pathParams.add(new String(p)); +} + void AsyncWebServerRequest::_addGetParams(const String& params){ size_t start = 0; while (start < params.length()){ @@ -912,6 +918,11 @@ const String& AsyncWebServerRequest::argName(size_t i) const { return getParam(i)->name(); } +const String& AsyncWebServerRequest::pathArg(size_t i) const { + auto param = _pathParams.nth(i); + return param ? **param : SharedEmptyString; +} + const String& AsyncWebServerRequest::header(const char* name) const { AsyncWebHeader* h = getHeader(String(name)); return h ? h->value() : SharedEmptyString;