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:
parent
11b7bd1d3a
commit
afb7dd688a
134
README.md
134
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.
|
||||
|
||||
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.
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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){
|
||||
|
|
Loading…
Reference in New Issue