diff --git a/README.md b/README.md index 364f4750..4b0720e3 100644 --- a/README.md +++ b/README.md @@ -31,30 +31,48 @@ To use this library you need to have the latest git versions of either [ESP8266] - Listens for connections - Wraps the new clients into ```Request``` - Keeps track of clients and cleans memory +- Manages ```Rewrites``` and apply them on the request url - Manages ```Handlers``` and attaches them to Requests ### Request Life Cycle - TCP connection is received by the server - The connection is wrapped inside ```Request``` object - When the request head is received (type, url, get params, http version and host), - the server goes through all attached ```Handlers```(in the order they are attached) trying to find one + the server goes through all ```Rewrites``` (in the order they were added) to rewrite the url and inject query parameters, + next, it goes through all attached ```Handlers```(in the order they were added) trying to find one that ```canHandle``` the given request. If none are found, the default(catch-all) handler is attached. - The rest of the request is received, calling the ```handleUpload``` or ```handleBody``` methods of the ```Handler``` if they are needed (POST+File/Body) - When the whole request is parsed, the result is given to the ```handleRequest``` method of the ```Handler``` and is ready to be responded to - In the ```handleRequest``` method, to the ```Request``` is attached a ```Response``` object (see below) that will serve the response data back to the client - When the ```Response``` is sent, the client is closed and freed from the memory +### Rewrites and how do they work +- The ```Rewrites``` are used to rewrite the request url and/or inject get parameters for a specific request url path. +- All ```Rewrites``` are evaluated on the request in the order they have been added to the server. +- The ```Rewrite``` will change the request url only if the request url (excluding get parameters) is fully match + the rewrite url, and when the optional ```Filter``` callback return true. +- Setting a ```Filter``` to the ```Rewrite``` enables to control when to apply the rewrite, decision can be based on + request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP. +- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface, + ```ON_SAT_FILTER``` to execute the rewrite when request is made to the STA interface. +- The ```Rewrite``` can specify a target url with optional get parameters, e.g. ```/to-url?with=params``` + ### Handlers and how do they work - The ```Handlers``` are used for executing specific actions to particular requests - One ```Handler``` instance can be attached to any request and lives together with the server -- The ```canHandle``` method is used for filtering the requests that can be handled - and declaring any interesting headers that the ```Request``` should collect -- Decision can be based on request method, request url, http version, request host/port/target host and GET parameters +- Setting a ```Filter``` to the ```Handler``` enables to control when to apply the handler, decision can be based on + request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP. +- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface, + ```ON_SAT_FILTER``` to execute the rewrite when request is made to the STA interface. +- The ```canHandle``` method is used for handler specific control on whether the requests can be handled + and for declaring any interesting headers that the ```Request``` should parse. Decision can be based on request + method, request url, http version, request host/port/target host and get parameters - Once a ```Handler``` is attached to given ```Request``` (```canHandle``` returned true) that ```Handler``` takes care to receive any file/data upload and attach a ```Response``` once the ```Request``` has been fully parsed -- ```Handler's``` ```canHandle``` is called in the order they are attached to the server. - If a ```Handler``` attached earlier returns ```true``` on ```canHandle```, then this ```Hander's``` ```canHandle``` will never be called +- ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only + if the ```Filter``` that was set to the ```Handler``` return true. +- The first ```Handler``` that can handle the request is selected, not further ```Filter``` and ```canHandle``` are called. ### Responses and how do they work - The ```Response``` objects are used to send the response data back to the client @@ -397,25 +415,99 @@ response->setLength(); request->send(response); ``` -### FileFallBackHandler -Example provided by [@sticilface](https://github.com/sticilface) +## Serving static files +In addition to serving files from SPIFFS as described above, the server provide a dedicated handler that optimize the +performance of serving files from SPIFFS - ```AsyncStaticWebHandler```. Use ```server.serveStatic()``` function to +initialize and add a new instance of ```AsyncStaticWebHandler``` to the server. +The Handler will not handle the request if the file does not exists, e.g. the server will continue to look for another +handler that can handle the request. +Notice that you can chain setter functions to setup the handler, or keep a pointer to change it at a later time. -This handler is useful for serving content from a CDN when the ESP is connected to a wifi -network, but falling back to local copies of the file stored in SPIFFS when the ESP is in -AP mode and the client does not have internet access. It will work when both AP mode and -STA mode are active. It works by returning 302 HTTP code, with a Location header that -you specify. It is much quicker than requiring the ESP to handle all the files. +### Serving specific file by name ```cpp -#include "FileFallbackHandler.h" // include this in the sketch. +// Serve the file "/www/page.htm" when request url is "/page.htm" +server.serveStatic("/page.htm", SPIFFS, "/www/page.htm"); +``` -server.addHandler( new FileFallbackHandler(SPIFFS, "/path_to_SPIFFS_file", "/uri", "url_to_forward", "optional_cache_control_header")); +### Serving files in directory +To serve files in a directory, the path to the files should specify a directory in SPIFFS and ends with "/". +```cpp +// Serve files in directory "/www/" when request url starts with "/" +// Request to the root or none existing files will try to server the defualt +// file name "index.htm" if exists +server.serveStatic("/", SPIFFS, "/www/"); -// These three lines will serve all the jquery requirements from SPIFFS (if they are there) in AP mode, but forward the URL to CDN if not. -server.addHandler( new FileFallbackHandler(_fs, "/jquery/jqm1.4.5.css", "/jquery/jqm1.4.5.css", "http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css", "max-age=86400")); -server.addHandler( new FileFallbackHandler(_fs, "/jquery/jq1.11.1.js" , "/jquery/jq1.11.1.js" , "http://code.jquery.com/jquery-1.11.1.min.js", "max-age=86400")); -server.addHandler( new FileFallbackHandler(_fs, "/jquery/jqm1.4.5.js" , "/jquery/jqm1.4.5.js" , "http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js", "max-age=86400")); +// Server with different default file +server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("default.html"); ``` +### Specifying Cache-Control header +It is possible to specify Cache-Control header value to reduce the number of calls to the server once the client loaded +the files. For more information on Cache-Control values see [Cache-Control](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) +```cpp +// Cache responses for 10 minutes (600 seconds) +server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age:600"); + +//*** Change Cache-Control after server setup *** + +// During setup - keep a pointer to the handler +AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age:600"); + +// At a later event - change Cache-Control +handler->setCacheControl("max-age:30"); +``` + +### Specifying Date-Modified header +It is possible to specify Date-Modified header to enable the server to return Not-Modified (304) response for requests +with "If-Modified-Since" header with the same value, instead of responding with the actual file content. +```cpp +// Update the date modified string every time files are updated +server.serveStatic("/", SPIFFS, "/www/").setLastModified("Mon, 20 Jun 2016 14:00:00 GMT"); + +//*** Chage last modified value at a later stage *** + +// During setup - read last modified value from config or EEPROM +String date_modified = loadDateModified(); +AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/"); +handler->setLastModified(date_modified); + +// At a later event when files are updated +String date_modified = getNewDateModfied(); +saveDateModified(date_modified); // Save for next reset +handler->setLastModified(date_modified); +``` + +## Using filters +Filters can be set to `Rewrite` or `Handler` in order to control when to apply the rewrite and consider the handler. +A filter is a callback function that evaluates the request and return a boolean `true` to include the item +or `false` to exclude it. +Two filter callback are provided for convince: +* `ON_SAT_FILTER` - return true when requests are made to the STA (station mode) interface. +* `ON_AP_FILTER` - return true when requests are made to the AP (access point) interface. + +### Serve different site files in AP mode +```cpp +server.serveStatic("/", SPIFFS, "/www/").setFilter(ON_SAT_FILTER); +server.serveStatic("/", SPIFFS, "/ap/").setFilter(ON_AP_FILTER); +``` + +### Rewrite to different index on AP +```cpp +// Serve the file "/www/index-ap.htm" in AP, and the file "/www/index.htm" on STA +server.rewrite("/", "index.htm"); +server.rewrite("/index.htm", "index-ap.htm").setFilter(ON_AP_FILTER); +server.serveStatic("/", SPIFFS, "/www/"); +``` + +### Serving different hosts +```cpp +// Filter callback using request host +bool filterOnHost1(AsyncWebServerRequest *request) { return request->host() == "host1"; } + +// Server setup: server files in "/host1/" to requests for "host1", and files in "/www/" otherwise. +server.serveStatic("/", SPIFFS, "/host1/").setFilter(filterOnHost1); +server.serveStatic("/", SPIFFS, "/www/"); +``` ## Bad Responses Some responses are implemented, but you should not use them, because they do not conform to HTTP. diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 6800a821..7ae38b43 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -29,6 +29,14 @@ #include "StringArray.h" +#if defined(ESP31B) +#include +#elif defined(ESP8266) +#include +#else +#error Platform not supported +#endif + #define DEBUGF(...) //Serial.printf(__VA_ARGS__) @@ -37,7 +45,10 @@ class AsyncWebServerRequest; class AsyncWebServerResponse; class AsyncWebHeader; class AsyncWebParameter; +class AsyncWebRewrite; class AsyncWebHandler; +class AsyncStaticWebHandler; +class AsyncCallbackWebHandler; class AsyncResponseStream; typedef enum { @@ -100,6 +111,7 @@ class AsyncWebHeader { typedef std::function AwsResponseFiller; class AsyncWebServerRequest { + friend class AsyncWebServer; private: AsyncClient* _client; AsyncWebServer* _server; @@ -152,7 +164,7 @@ class AsyncWebServerRequest { void _parseLine(); void _parsePlainPostChar(uint8_t data); void _parseMultipartPostByte(uint8_t data, bool last); - void _addGetParam(String param); + void _addGetParams(String params); void _handleUploadStart(); void _handleUploadByte(uint8_t data, bool last); @@ -223,14 +235,58 @@ class AsyncWebServerRequest { String urlDecode(const String& text); }; +/* + * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) + * */ + +typedef std::function ArRequestFilterFunction; + +static bool ON_STA_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() == request->client()->localIP(); +} + +static bool ON_AP_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() != request->client()->localIP(); +} + +/* + * REWRITE :: One instance can be handle any Request (done by the Server) + * */ + +class AsyncWebRewrite { + protected: + String _from; + String _toUrl; + String _params; + ArRequestFilterFunction _filter; + public: + AsyncWebRewrite* next; + AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL), next(NULL){ + int index = _toUrl.indexOf('?'); + if (index > 0) { + _params = _toUrl.substring(index +1); + _toUrl = _toUrl.substring(0, index); + } + } + AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; } + bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } + String from(void) { return _from; } + String toUrl(void) { return _toUrl; } + String params(void) { return _params; } +}; + /* * HANDLER :: One instance can be attached to any Request (done by the Server) * */ class AsyncWebHandler { + protected: + ArRequestFilterFunction _filter; public: AsyncWebHandler* next; AsyncWebHandler(): next(NULL){} + AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; } + bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } virtual ~AsyncWebHandler(){} virtual bool canHandle(AsyncWebServerRequest *request){ return false; } virtual void handleRequest(AsyncWebServerRequest *request){} @@ -286,31 +342,39 @@ typedef std::function - -#if defined(ESP31B) -#include -#elif defined(ESP8266) -#include -#else -#error Platform not supported -#endif - -bool FileFallbackHandler::canHandle(AsyncWebServerRequest *request) -{ - if (request->method() != HTTP_GET) { - return false; - } - if ((_isFile && request->url() != _uri) ) { - return false; - } - // if the root of the request matches the _uri then it checks to see if there is a file it can handle. - if (request->url().startsWith(_uri)) { - String path = _getPath(request); - if (_fs.exists(path) || _fs.exists(path + ".gz")) { - DEBUGF("[FileFallbackHandler::canHandle] TRUE\n"); - return true; - } - } - - return false; -} - -String FileFallbackHandler::_getPath(AsyncWebServerRequest *request) -{ - - String path = request->url(); - DEBUGF("[FileFallbackHandler::_getPath]\n"); - DEBUGF(" [stored] _uri = %s, _path = %s\n" , _uri.c_str(), _path.c_str() ) ; - DEBUGF(" [request] url = %s\n", request->url().c_str() ); - - if (!_isFile) { - DEBUGF(" _isFile = false\n"); - String baserequestUrl = request->url().substring(_uri.length()); // this is the request - stored _uri... /espman/ - DEBUGF(" baserequestUrl = %s\n", baserequestUrl.c_str()); - - if (!baserequestUrl.length()) { - baserequestUrl += "/"; - } - - path = _path + baserequestUrl; - DEBUGF(" path = path + baserequestUrl, path = %s\n", path.c_str()); - - if (path.endsWith("/")) { - DEBUGF(" 3 path ends with / : path = index.htm \n"); - path += "index.htm"; - } - } else { - path = _path; - } - - DEBUGF(" final path = %s\n", path.c_str()); - DEBUGF("[FileFallbackHandler::_getPath] END\n\n"); - - return path; -} - - -void FileFallbackHandler::handleRequest(AsyncWebServerRequest *request) -{ - - String path = _getPath(request); - - - if ( request->client()->localIP() == WiFi.localIP() ) { - - AsyncWebServerResponse *response = request->beginResponse(302); //Sends 404 File Not Found - response->addHeader("Location", _forwardUri ); - if (_cache_header.length() != 0) { - response->addHeader("Cache-Control", _cache_header); - } - request->send(response); - - } else if (_fs.exists(path) || _fs.exists(path + ".gz")) { - AsyncWebServerResponse * response = request->beginResponse(_fs, path); - if (_cache_header.length() != 0) { - response->addHeader("Cache-Control", _cache_header); - } - request->send(response); - } else { - request->send(404); - } - - path = String(); - -} diff --git a/src/FileFallbackHandler.h b/src/FileFallbackHandler.h deleted file mode 100644 index abb932c3..00000000 --- a/src/FileFallbackHandler.h +++ /dev/null @@ -1,55 +0,0 @@ -// FileFallbackHandler.h -/* - FileFallbackHandler Response to use with asyncwebserver - Written by Andrew Melvin (SticilFace), based on ServeStatic, with help from me-no-dev. - - This handler will serve a 302 response to a client request for a SPIFFS file if the request comes from the STA side of the ESP network. - If the request comes from the AP side then it serves the file from SPIFFS. - This is useful if you have content that is available from a CDN but you want it to work in AP mode. - This also speeds things up a lot as the ESP is not left serving files, and the client can cache them as well. - - - FileFallbackHandler(SPIFFS, File_location, Uri, fallback_uri, cache_control header (optional) ) - - Example of callback in use - - server.addHandler( new FileFallbackHandler(SPIFFS, "/jquery/jqm1.4.5.css", "/jquery/jqm1.4.5.css", "http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css", "max-age=86400")); - - -*/ -#ifndef ASYNC_FILEFALLBACK_H_ -#define ASYNC_FILEFALLBACK_H_ - -#include - - -class FileFallbackHandler: public AsyncWebHandler { - private: - String _getPath(AsyncWebServerRequest *request); - protected: - FS _fs; - String _uri; - String _path; - String _forwardUri; - String _cache_header; - bool _isFile; - public: - FileFallbackHandler(FS& fs, const char* path, const char* uri, const char* forwardUri ,const char* cache_header) - : _fs(fs), _uri(uri), _path(path), _forwardUri(forwardUri),_cache_header(cache_header){ - - _isFile = _fs.exists(path) || _fs.exists((String(path)+".gz").c_str()); - if (_uri != "/" && _uri.endsWith("/")) { - _uri = _uri.substring(0, _uri.length() - 1); - DEBUGF("[FileFallbackHandler] _uri / removed\n"); - } - if (_path != "/" && _path.endsWith("/")) { - _path = _path.substring(0, _path.length() - 1); - DEBUGF("[FileFallbackHandler] _path / removed\n"); - } - } - bool canHandle(AsyncWebServerRequest *request); - void handleRequest(AsyncWebServerRequest *request); - -}; - -#endif diff --git a/src/WebHandlerImpl.h b/src/WebHandlerImpl.h index 158cb60c..3d5eca8b 100644 --- a/src/WebHandlerImpl.h +++ b/src/WebHandlerImpl.h @@ -33,15 +33,20 @@ class AsyncStaticWebHandler: public AsyncWebHandler { FS _fs; String _uri; String _path; - String _cache_header; + String _default_file; + String _cache_control; + String _last_modified; bool _isDir; bool _gzipFirst; uint8_t _gzipStats; - uint8_t _fileStats; public: - AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header); + AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); bool canHandle(AsyncWebServerRequest *request); void handleRequest(AsyncWebServerRequest *request); + AsyncStaticWebHandler& setIsDir(bool isDir); + AsyncStaticWebHandler& setDefaultFile(const char* filename); + AsyncStaticWebHandler& setCacheControl(const char* cache_control); + AsyncStaticWebHandler& setLastModified(const char* last_modified); }; class AsyncCallbackWebHandler: public AsyncWebHandler { diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index e7ee62f1..f59d262c 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -21,8 +21,8 @@ #include "ESPAsyncWebServer.h" #include "WebHandlerImpl.h" -AsyncStaticWebHandler::AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header) - : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) +AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) + : _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(NULL) { // Ensure leading '/' if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; @@ -39,8 +39,27 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(FS& fs, const char* path, const cha // Reset stats _gzipFirst = false; - _gzipStats = 0; - _fileStats = 0; + _gzipStats = 0xF8; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ + _isDir = isDir; + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ + _default_file = String(filename); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ + _cache_control = String(cache_control); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ + _last_modified = String(last_modified); + return *this; } bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) @@ -49,6 +68,10 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) request->url().startsWith(_uri) && _getFile(request)) { + // We interested in "If-Modified-Since" header to check if file was modified + if (_last_modified.length()) + request->addInterestingHeader("If-Modified-Since"); + DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); return true; } @@ -70,10 +93,14 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) if (!canSkipFileCheck && _fileExists(request, path)) return true; - // Try to add default page, ensure there is a trailing '/' ot the path. + // Can't handle if not default file + if (_default_file.length() == 0) + return false; + + // Try to add default file, ensure there is a trailing '/' ot the path. if (path.length() == 0 || path[path.length()-1] != '/') path += "/"; - path += "index.htm"; + path += _default_file; return _fileExists(request, path); } @@ -104,13 +131,17 @@ bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const St bool found = fileFound || gzipFound; if (found) { - size_t plen = path.length(); - char * _tempPath = (char*)malloc(plen+1); - snprintf(_tempPath, plen+1, "%s", path.c_str()); + // Extract the file name from the path and keep it in _tempObject + size_t pathLen = path.length(); + char * _tempPath = (char*)malloc(pathLen+1); + snprintf(_tempPath, pathLen+1, "%s", path.c_str()); request->_tempObject = (void*)_tempPath; - _gzipStats = (_gzipStats << 1) + gzipFound ? 1 : 0; - _fileStats = (_fileStats << 1) + fileFound ? 1 : 0; - _gzipFirst = _countBits(_gzipStats) > _countBits(_fileStats); + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip + else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first } return found; @@ -126,13 +157,22 @@ uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) { + // Get the filename from request->_tempObject and free it + String filename = String((char*)request->_tempObject); + free(request->_tempObject); + request->_tempObject = NULL; + if (request->_tempFile == true) { - AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, String((char*)request->_tempObject)); - free(request->_tempObject); - request->_tempObject = NULL; - if (_cache_header.length() != 0) - response->addHeader("Cache-Control", _cache_header); - request->send(response); + if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { + request->send(304); // Not modified + } else { + AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename); + if (_last_modified.length()) + response->addHeader("Last-Modified", _last_modified); + if (_cache_control.length()) + response->addHeader("Cache-Control", _cache_control); + request->send(response); + } } else { request->send(404); } diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 238f5b63..b4149aad 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -206,19 +206,20 @@ void AsyncWebServerRequest::_addParam(AsyncWebParameter *p){ } } -void AsyncWebServerRequest::_addGetParam(String param){ - param = urlDecode(param); - String name = param; - String value = ""; - int index = param.indexOf('='); - if(index > 0){ - name = param.substring(0, index); - value = param.substring(index + 1); +void AsyncWebServerRequest::_addGetParams(String params){ + int start = 0; + while (start < params.length()){ + int end = params.indexOf('&', start); + if (end < 0) end = params.length(); + int equal = params.indexOf('=', start); + if (equal < 0 || equal > end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + _addParam(new AsyncWebParameter(name, value)); + start = end + 1; } - _addParam(new AsyncWebParameter(name, value)); } - bool AsyncWebServerRequest::_parseReqHead(){ // Split the head into method, url and version int index = _temp.indexOf(' '); @@ -247,27 +248,15 @@ bool AsyncWebServerRequest::_parseReqHead(){ String g = String(); index = u.indexOf('?'); if(index > 0){ - g = u.substring(index+1); + g = u.substring(index +1); u = u.substring(0, index); } _url = u; - if(g.length()){ - while(true){ - if(g.length() == 0) - break; - index = g.indexOf('&'); - if(index > 0){ - _addGetParam(g.substring(0, index)); - g = g.substring(index+1); - } else { - _addGetParam(g); - break; - } - } - } + _addGetParams(g); if(_temp.startsWith("HTTP/1.1")) _version = 1; + _temp = String(); return true; } @@ -279,7 +268,8 @@ bool AsyncWebServerRequest::_parseReqHeader(){ String value = _temp.substring(index + 2); if(name == "Host"){ _host = value; - _server->_handleRequest(this); + _server->_rewriteRequest(this); + _server->_attachHandler(this); } else if(name == "Content-Type"){ if (value.startsWith("multipart/")){ _boundary = value.substring(value.indexOf('=')+1); diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 7f26accf..882fe152 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -22,7 +22,7 @@ #include "WebHandlerImpl.h" -AsyncWebServer::AsyncWebServer(uint16_t port):_server(port), _handlers(0){ +AsyncWebServer::AsyncWebServer(uint16_t port):_server(port), _rewrites(0), _handlers(0){ _catchAllHandler = new AsyncCallbackWebHandler(); if(_catchAllHandler == NULL) return; @@ -38,7 +38,38 @@ AsyncWebServer::AsyncWebServer(uint16_t port):_server(port), _handlers(0){ }, this); } -void AsyncWebServer::addHandler(AsyncWebHandler* handler){ +AsyncWebServer::~AsyncWebServer(){ + while(_rewrites != NULL){ + AsyncWebRewrite *r = _rewrites; + _rewrites = r->next; + delete r; + } + while(_handlers != NULL){ + AsyncWebHandler *h = _handlers; + _handlers = h->next; + delete h; + } + if (_catchAllHandler != NULL){ + delete _catchAllHandler; + } +} + +AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ + if (_rewrites == NULL){ + _rewrites = rewrite; + } else { + AsyncWebRewrite *r = _rewrites; + while(r->next != NULL) r = r->next; + r->next = rewrite; + } + return *rewrite; +} + +AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ + return addRewrite(new AsyncWebRewrite(from, to)); +} + +AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ if(_handlers == NULL){ _handlers = handler; } else { @@ -46,6 +77,7 @@ void AsyncWebServer::addHandler(AsyncWebHandler* handler){ while(h->next != NULL) h = h->next; h->next = handler; } + return *handler; } void AsyncWebServer::begin(){ @@ -56,13 +88,26 @@ void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ delete request; } -void AsyncWebServer::_handleRequest(AsyncWebServerRequest *request){ +void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ + AsyncWebRewrite *r = _rewrites; + while(r){ + if (r->from() == request->_url && r->filter(request)){ + request->_url = r->toUrl(); + request->_addGetParams(r->params()); + } + r = r->next; + } +} + +void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ if(_handlers){ AsyncWebHandler* h = _handlers; - while(h && !h->canHandle(request)) h = h->next; - if(h){ - request->setHandler(h); - return; + while(h){ + if (h->filter(request) && h->canHandle(request)){ + request->setHandler(h); + return; + } + h = h->next; } } request->addInterestingHeader("ANY"); @@ -70,7 +115,7 @@ void AsyncWebServer::_handleRequest(AsyncWebServerRequest *request){ } -void AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); handler->setUri(uri); handler->setMethod(method); @@ -78,34 +123,40 @@ void AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandl handler->onUpload(onUpload); handler->onBody(onBody); addHandler(handler); + return *handler; } -void AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); handler->setUri(uri); handler->setMethod(method); handler->onRequest(onRequest); handler->onUpload(onUpload); addHandler(handler); + return *handler; } -void AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest){ +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest){ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); handler->setUri(uri); handler->setMethod(method); handler->onRequest(onRequest); addHandler(handler); + return *handler; } -void AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); handler->setUri(uri); handler->onRequest(onRequest); addHandler(handler); + return *handler; } -void AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header){ - addHandler(new AsyncStaticWebHandler(fs, path, uri, cache_header)); +AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ + AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); + addHandler(handler); + return *handler; } void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){