New - rewrite, filters and last-modified (#47)

* New - server.rewirte() to rewrite the request url with optional get parameters injection.
New - rewrite.setFilter() and handler.setFilter() to specify a filter callback for more control on when to include them.
New - static file handler can be set with Last-Modified header value to support Not-Modified (304) response instead of serving the actual file.

* Remove clearRewrites & clearHandlers from server.mplement server distractor to delete internal members. Fixed and improved gzip stats calculation.
This commit is contained in:
Hagai Shatz 2016-06-25 20:04:06 +01:00 committed by Me No Dev
parent 11b7bd1d3a
commit afb7dd688a
8 changed files with 332 additions and 242 deletions

134
README.md
View File

@ -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.
server.addHandler( new FileFallbackHandler(SPIFFS, "/path_to_SPIFFS_file", "/uri", "url_to_forward", "optional_cache_control_header"));
// 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"));
// Serve the file "/www/page.htm" when request url is "/page.htm"
server.serveStatic("/page.htm", SPIFFS, "/www/page.htm");
```
### 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/");
// 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.

View File

@ -29,6 +29,14 @@
#include "StringArray.h"
#if defined(ESP31B)
#include <ESP31BWiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#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<size_t(uint8_t*, size_t, size_t)> 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<bool(AsyncWebServerRequest *request)> 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<void(AsyncWebServerRequest *request, uint8_t *data, size_t
class AsyncWebServer {
private:
AsyncServer _server;
AsyncWebRewrite* _rewrites;
AsyncWebHandler* _handlers;
AsyncWebHandler* _catchAllHandler;
public:
AsyncWebServer(uint16_t port);
~AsyncWebServer(){}
~AsyncWebServer();
void begin();
void addHandler(AsyncWebHandler* handler);
void on(const char* uri, ArRequestHandlerFunction onRequest);
void on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest);
void on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
void on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL);
AsyncWebRewrite& rewrite(const char* from, const char* to);
AsyncWebHandler& addHandler(AsyncWebHandler* handler);
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethod method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
void _handleDisconnect(AsyncWebServerRequest *request);
void _handleRequest(AsyncWebServerRequest *request);
void _attachHandler(AsyncWebServerRequest *request);
void _rewriteRequest(AsyncWebServerRequest *request);
};
#include "WebResponseImpl.h"
#include "WebHandlerImpl.h"
#include "AsyncWebSocket.h"
#endif /* _AsyncWebServer_H_ */

View File

@ -1,97 +0,0 @@
#include "FileFallbackHandler.h"
#include <ESPAsyncWebServer.h>
#if defined(ESP31B)
#include <ESP31BWiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#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();
}

View File

@ -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 <ESPAsyncWebServer.h>
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

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);

View File

@ -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){