forked from me-no-dev/ESPAsyncWebServer
Added simple template processor to AsyncFileResponse. (#189)
Unzipped files in SPIFFS, Streams, PROGMEM strings, callback/chunked responses may have template placeholders like %TEMPLATE_VAR% inside. If callback is specified in Async...Response constructor call, it will be used to replace these with actual strings. The prototype of callback is String(const String&), i.e. it gets variable name and returns its value. Template variables' delimiter is currently percent sign ('%'). Maximal placeholder length is 32 chars (chosen somewhat arbitrarily, it may be stored on stack during processing). It is not guaranteed that placeholders longer than that will be processed. Signed-off-by: Alexandr Zarubkin <me21@yandex.ru> # Conflicts: # src/WebResponses.cpp
This commit is contained in:
committed by
Me No Dev
parent
77a520ba24
commit
0179c17cf9
42
README.md
42
README.md
@@ -37,6 +37,7 @@ To use this library you might need to have the latest git versions of [ESP8266](
|
||||
- [Respond with content coming from a Stream and extra headers](#respond-with-content-coming-from-a-stream-and-extra-headers)
|
||||
- [Respond with content coming from a File](#respond-with-content-coming-from-a-file)
|
||||
- [Respond with content coming from a File and extra headers](#respond-with-content-coming-from-a-file-and-extra-headers)
|
||||
- [Respond with content coming from a File containing templates](#respond-with-content-coming-from-a-file-containing-templates)
|
||||
- [Respond with content using a callback](#respond-with-content-using-a-callback)
|
||||
- [Respond with content using a callback and extra headers](#respond-with-content-using-a-callback-and-extra-headers)
|
||||
- [Chunked Response](#chunked-response)
|
||||
@@ -48,6 +49,7 @@ To use this library you might need to have the latest git versions of [ESP8266](
|
||||
- [Serving files in directory](#serving-files-in-directory)
|
||||
- [Specifying Cache-Control header](#specifying-cache-control-header)
|
||||
- [Specifying Date-Modified header](#specifying-date-modified-header)
|
||||
- [Specifying Template Processor callback](#specifying-template-processor-callback)
|
||||
- [Using filters](#using-filters)
|
||||
- [Serve different site files in AP mode](#serve-different-site-files-in-ap-mode)
|
||||
- [Rewrite to different index on AP](#rewrite-to-different-index-on-ap)
|
||||
@@ -411,6 +413,29 @@ response->addHeader("Server","ESP Async Web Server");
|
||||
request->send(response);
|
||||
```
|
||||
|
||||
### Respond with content coming from a File containing templates
|
||||
Internally uses [Chunked Response](#chunked-response).
|
||||
|
||||
Index.htm contents:
|
||||
```
|
||||
%HELLO_FROM_TEMPLATE%
|
||||
```
|
||||
|
||||
Somewhere in source files:
|
||||
```cpp
|
||||
String processor(const String& var)
|
||||
{
|
||||
if(var == "HELLO_FROM_TEMPLATE")
|
||||
return F("Hello world!");
|
||||
return String();
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
//Send index.htm with template processor function
|
||||
request->send(SPIFFS, "/index.htm", String(), false, processor);
|
||||
```
|
||||
|
||||
### Respond with content using a callback
|
||||
```cpp
|
||||
//send 128 bytes as plain text
|
||||
@@ -610,6 +635,23 @@ saveDateModified(date_modified); // Save for next reset
|
||||
handler->setLastModified(date_modified);
|
||||
```
|
||||
|
||||
### Specifying Template Processor callback
|
||||
It is possible to specify template processor for static files. For information on template processor see
|
||||
[Respond with content coming from a File containing templates](#respond-with-content-coming-from-a-file-containing-templates).
|
||||
```cpp
|
||||
String processor(const String& var)
|
||||
{
|
||||
if(var == "HELLO_FROM_TEMPLATE")
|
||||
return F("Hello world!");
|
||||
return String();
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
server.serveStatic("/", SPIFFS, "/www/").setTemplateProcessor(processor);
|
||||
```
|
||||
|
||||
|
||||
## 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
|
||||
|
@@ -116,6 +116,7 @@ class AsyncWebHeader {
|
||||
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType;
|
||||
|
||||
typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller;
|
||||
typedef std::function<String(const String&)> AwsTemplateProcessor;
|
||||
|
||||
class AsyncWebServerRequest {
|
||||
using File = fs::File;
|
||||
@@ -216,23 +217,23 @@ class AsyncWebServerRequest {
|
||||
|
||||
void send(AsyncWebServerResponse *response);
|
||||
void send(int code, const String& contentType=String(), const String& content=String());
|
||||
void send(FS &fs, const String& path, const String& contentType=String(), bool download=false);
|
||||
void send(File content, const String& path, const String& contentType=String(), bool download=false);
|
||||
void send(Stream &stream, const String& contentType, size_t len);
|
||||
void send(const String& contentType, size_t len, AwsResponseFiller callback);
|
||||
void sendChunked(const String& contentType, AwsResponseFiller callback);
|
||||
void send_P(int code, const String& contentType, const uint8_t * content, size_t len);
|
||||
void send_P(int code, const String& contentType, PGM_P content);
|
||||
void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||
void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||
void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
||||
|
||||
AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String());
|
||||
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false);
|
||||
AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false);
|
||||
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len);
|
||||
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback);
|
||||
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback);
|
||||
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||
AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460);
|
||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len);
|
||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content);
|
||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
||||
|
||||
size_t headers() const; // get header count
|
||||
bool hasHeader(const String& name) const; // check if header exists
|
||||
|
@@ -39,6 +39,7 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
|
||||
String _default_file;
|
||||
String _cache_control;
|
||||
String _last_modified;
|
||||
AwsTemplateProcessor _callback;
|
||||
bool _isDir;
|
||||
bool _gzipFirst;
|
||||
uint8_t _gzipStats;
|
||||
@@ -55,6 +56,7 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
|
||||
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
||||
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
||||
#endif
|
||||
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
|
||||
};
|
||||
|
||||
class AsyncCallbackWebHandler: public AsyncWebHandler {
|
||||
|
@@ -22,7 +22,7 @@
|
||||
#include "WebHandlerImpl.h"
|
||||
|
||||
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("")
|
||||
: _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr)
|
||||
{
|
||||
// Ensure leading '/'
|
||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
|
||||
@@ -199,7 +199,7 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
||||
response->addHeader("ETag", etag);
|
||||
request->send(response);
|
||||
} else {
|
||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename);
|
||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
||||
if (_last_modified.length())
|
||||
response->addHeader("Last-Modified", _last_modified);
|
||||
if (_cache_control.length()){
|
||||
|
@@ -710,78 +710,78 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const St
|
||||
return new AsyncBasicResponse(code, contentType, content);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download){
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||
if(fs.exists(path) || (!download && fs.exists(path+".gz")))
|
||||
return new AsyncFileResponse(fs, path, contentType, download);
|
||||
return new AsyncFileResponse(fs, path, contentType, download, callback);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download){
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||
if(content == true)
|
||||
return new AsyncFileResponse(content, path, contentType, download);
|
||||
return new AsyncFileResponse(content, path, contentType, download, callback);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len){
|
||||
return new AsyncStreamResponse(stream, contentType, len);
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
||||
return new AsyncStreamResponse(stream, contentType, len, callback);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback){
|
||||
return new AsyncCallbackResponse(contentType, len, callback);
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||
return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback){
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||
if(_version)
|
||||
return new AsyncChunkedResponse(contentType, callback);
|
||||
return new AsyncCallbackResponse(contentType, 0, callback);
|
||||
return new AsyncChunkedResponse(contentType, callback, templateCallback);
|
||||
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
|
||||
}
|
||||
|
||||
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){
|
||||
return new AsyncResponseStream(contentType, bufferSize);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len){
|
||||
return new AsyncProgmemResponse(code, contentType, content, len);
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
|
||||
return new AsyncProgmemResponse(code, contentType, content, len, callback);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content){
|
||||
return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content));
|
||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
|
||||
return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
|
||||
send(beginResponse(code, contentType, content));
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download){
|
||||
void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||
if(fs.exists(path) || (!download && fs.exists(path+".gz"))){
|
||||
send(beginResponse(fs, path, contentType, download));
|
||||
send(beginResponse(fs, path, contentType, download, callback));
|
||||
} else send(404);
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download){
|
||||
void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||
if(content == true){
|
||||
send(beginResponse(content, path, contentType, download));
|
||||
send(beginResponse(content, path, contentType, download, callback));
|
||||
} else send(404);
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len){
|
||||
send(beginResponse(stream, contentType, len));
|
||||
void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
||||
send(beginResponse(stream, contentType, len, callback));
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback){
|
||||
send(beginResponse(contentType, len, callback));
|
||||
void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||
send(beginResponse(contentType, len, callback, templateCallback));
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback){
|
||||
send(beginChunkedResponse(contentType, callback));
|
||||
void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||
send(beginChunkedResponse(contentType, callback, templateCallback));
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len){
|
||||
send(beginResponse_P(code, contentType, content, len));
|
||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
|
||||
send(beginResponse_P(code, contentType, content, len, callback));
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content){
|
||||
send(beginResponse_P(code, contentType, content));
|
||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
|
||||
send(beginResponse_P(code, contentType, content, callback));
|
||||
}
|
||||
|
||||
void AsyncWebServerRequest::redirect(const String& url){
|
||||
|
@@ -21,6 +21,14 @@
|
||||
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||
|
||||
#ifdef Arduino_h
|
||||
// arduino is not compatible with std::vector
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
#include <vector>
|
||||
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
||||
|
||||
class AsyncBasicResponse: public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
@@ -34,13 +42,21 @@ class AsyncBasicResponse: public AsyncWebServerResponse {
|
||||
class AsyncAbstractResponse: public AsyncWebServerResponse {
|
||||
private:
|
||||
String _head;
|
||||
std::vector<uint8_t> _cache;
|
||||
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
|
||||
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
|
||||
protected:
|
||||
AwsTemplateProcessor _callback;
|
||||
public:
|
||||
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr);
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return false; }
|
||||
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
||||
};
|
||||
|
||||
#define TEMPLATE_PLACEHOLDER '%'
|
||||
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
||||
class AsyncFileResponse: public AsyncAbstractResponse {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
@@ -49,8 +65,8 @@ class AsyncFileResponse: public AsyncAbstractResponse {
|
||||
String _path;
|
||||
void _setContentType(const String& path);
|
||||
public:
|
||||
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false);
|
||||
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false);
|
||||
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||
~AsyncFileResponse();
|
||||
bool _sourceValid() const { return !!(_content); }
|
||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||
@@ -60,7 +76,7 @@ class AsyncStreamResponse: public AsyncAbstractResponse {
|
||||
private:
|
||||
Stream *_content;
|
||||
public:
|
||||
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len);
|
||||
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
bool _sourceValid() const { return !!(_content); }
|
||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||
};
|
||||
@@ -68,8 +84,9 @@ class AsyncStreamResponse: public AsyncAbstractResponse {
|
||||
class AsyncCallbackResponse: public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
public:
|
||||
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback);
|
||||
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
bool _sourceValid() const { return !!(_content); }
|
||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||
};
|
||||
@@ -77,8 +94,9 @@ class AsyncCallbackResponse: public AsyncAbstractResponse {
|
||||
class AsyncChunkedResponse: public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
public:
|
||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback);
|
||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
bool _sourceValid() const { return !!(_content); }
|
||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||
};
|
||||
@@ -86,8 +104,9 @@ class AsyncChunkedResponse: public AsyncAbstractResponse {
|
||||
class AsyncProgmemResponse: public AsyncAbstractResponse {
|
||||
private:
|
||||
const uint8_t * _content;
|
||||
size_t _readLength;
|
||||
public:
|
||||
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len);
|
||||
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
bool _sourceValid() const { return true; }
|
||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||
};
|
||||
|
@@ -22,6 +22,17 @@
|
||||
#include "WebResponseImpl.h"
|
||||
#include "cbuf.h"
|
||||
|
||||
// Since ESP8266 does not link memchr by default, here's its implementation.
|
||||
void* memchr(void* ptr, int ch, size_t count)
|
||||
{
|
||||
unsigned char* p = static_cast<unsigned char*>(ptr);
|
||||
while(count--)
|
||||
if(*p++ == static_cast<unsigned char>(ch))
|
||||
return --p;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Abstract Response
|
||||
* */
|
||||
@@ -228,6 +239,16 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint
|
||||
* Abstract Response
|
||||
* */
|
||||
|
||||
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback)
|
||||
{
|
||||
// In case of template processing, we're unable to determine real response size
|
||||
if(callback) {
|
||||
_contentLength = 0;
|
||||
_sendContentLength = false;
|
||||
_chunked = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
|
||||
addHeader("Connection","close");
|
||||
_head = _assembleHead(request->version());
|
||||
@@ -277,6 +298,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
||||
}
|
||||
|
||||
if(headLen){
|
||||
//TODO: memcpy should be faster?
|
||||
sprintf((char*)buf, "%s", _head.c_str());
|
||||
_head = String();
|
||||
}
|
||||
@@ -295,7 +317,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
||||
buf[outLen++] = '\r';
|
||||
buf[outLen++] = '\n';
|
||||
} else {
|
||||
outLen = _fillBuffer(buf+headLen, outLen) + headLen;
|
||||
outLen = _fillBufferAndProcessTemplates(buf+headLen, outLen) + headLen;
|
||||
}
|
||||
|
||||
if(outLen)
|
||||
@@ -308,7 +330,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
||||
|
||||
free(buf);
|
||||
|
||||
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || _sentLength == _contentLength){
|
||||
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
}
|
||||
return outLen;
|
||||
@@ -323,7 +345,118 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len)
|
||||
{
|
||||
// If we have something in cache, copy it to buffer
|
||||
const size_t readFromCache = std::min(len, _cache.size());
|
||||
if(readFromCache) {
|
||||
memcpy(data, _cache.data(), readFromCache);
|
||||
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
|
||||
}
|
||||
// If we need to read more...
|
||||
const size_t needFromFile = len - readFromCache;
|
||||
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
|
||||
return readFromCache + readFromContent;
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len)
|
||||
{
|
||||
if(!_callback)
|
||||
return _fillBuffer(data, len);
|
||||
|
||||
const size_t originalLen = len;
|
||||
len = _readDataFromCacheOrContent(data, len);
|
||||
// Now we've read 'len' bytes, either from cache or from file
|
||||
// Search for template placeholders
|
||||
uint8_t* pTemplateStart = data;
|
||||
while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
|
||||
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
|
||||
// temporary buffer to hold parameter name
|
||||
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
|
||||
String paramName;
|
||||
// cache position to insert remainder of template parameter value
|
||||
std::vector<uint8_t>::iterator i = _cache.end();
|
||||
// If closing placeholder is found:
|
||||
if(pTemplateEnd) {
|
||||
// prepare argument to callback
|
||||
const size_t paramNameLength = std::min(sizeof(buf) - 1, (unsigned int)(pTemplateEnd - pTemplateStart - 1));
|
||||
if(paramNameLength) {
|
||||
memcpy(buf, pTemplateStart + 1, paramNameLength);
|
||||
buf[paramNameLength] = 0;
|
||||
paramName = String(reinterpret_cast<char*>(buf));
|
||||
} else { // double percent sign encountered, this is single percent sign escaped.
|
||||
// remove the 2nd percent sign
|
||||
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
|
||||
++pTemplateStart;
|
||||
}
|
||||
} else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
||||
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
|
||||
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
|
||||
if(readFromCacheOrContent) {
|
||||
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
||||
if(pTemplateEnd) {
|
||||
// prepare argument to callback
|
||||
*pTemplateEnd = 0;
|
||||
paramName = String(reinterpret_cast<char*>(buf));
|
||||
// Copy remaining read-ahead data into cache (when std::vector::insert returning iterator will be available, these 3 lines can be simplified into 1)
|
||||
const size_t pos = _cache.size();
|
||||
_cache.insert(_cache.end(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||
i = _cache.begin() + pos;
|
||||
pTemplateEnd = &data[len - 1];
|
||||
}
|
||||
else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
||||
{
|
||||
// but first, store read file data in cache
|
||||
_cache.insert(_cache.end(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||
++pTemplateStart;
|
||||
}
|
||||
}
|
||||
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
++pTemplateStart;
|
||||
}
|
||||
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
++pTemplateStart;
|
||||
if(paramName.length()) {
|
||||
// call callback and replace with result.
|
||||
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
|
||||
// Data after pTemplateEnd may need to be moved.
|
||||
// The first byte of data after placeholder is located at pTemplateEnd + 1.
|
||||
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
|
||||
const String paramValue(_callback(paramName));
|
||||
const char* pvstr = paramValue.c_str();
|
||||
const unsigned int pvlen = paramValue.length();
|
||||
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
|
||||
// make room for param value
|
||||
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
|
||||
if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
|
||||
size_t pos = i - _cache.begin();
|
||||
_cache.insert(i, &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
|
||||
i = _cache.begin() + pos;
|
||||
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
|
||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
|
||||
} else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
|
||||
//2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
|
||||
// Move the entire data after the placeholder
|
||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||
// 3. replace placeholder with actual value
|
||||
memcpy(pTemplateStart, pvstr, numBytesCopied);
|
||||
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
|
||||
if(numBytesCopied < pvlen) {
|
||||
_cache.insert(i, pvstr + numBytesCopied, pvstr + pvlen);
|
||||
} else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
||||
// there is some free room, fill it from cache
|
||||
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
|
||||
const size_t totalFreeRoom = originalLen - len + roomFreed;
|
||||
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
|
||||
} else { // result is copied fully; it is longer than placeholder text
|
||||
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
|
||||
len = std::min(len + roomTaken, originalLen);
|
||||
}
|
||||
}
|
||||
} // while(pTemplateStart)
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@@ -357,13 +490,16 @@ void AsyncFileResponse::_setContentType(const String& path){
|
||||
else _contentType = "text/plain";
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download){
|
||||
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
|
||||
_code = 200;
|
||||
_path = path;
|
||||
|
||||
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
|
||||
_path = _path+".gz";
|
||||
addHeader("Content-Encoding", "gzip");
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
}
|
||||
|
||||
_content = fs.open(_path, "r");
|
||||
@@ -386,18 +522,22 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& c
|
||||
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
|
||||
}
|
||||
addHeader("Content-Disposition", buf);
|
||||
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download){
|
||||
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
|
||||
_code = 200;
|
||||
_path = path;
|
||||
|
||||
if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){
|
||||
addHeader("Content-Encoding", "gzip");
|
||||
_callback = nullptr; // Unable to process gzipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
}
|
||||
|
||||
_content = content;
|
||||
_contentLength = _content.size();
|
||||
|
||||
if(!download && String(_content.name()).endsWith(".gz") && !path.endsWith(".gz"))
|
||||
addHeader("Content-Encoding", "gzip");
|
||||
|
||||
if(contentType == "")
|
||||
_setContentType(path);
|
||||
else
|
||||
@@ -424,7 +564,7 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
* Stream Response
|
||||
* */
|
||||
|
||||
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len){
|
||||
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_content = &stream;
|
||||
_contentLength = len;
|
||||
@@ -444,55 +584,64 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
* Callback Response
|
||||
* */
|
||||
|
||||
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback){
|
||||
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = len;
|
||||
if(!len)
|
||||
_sendContentLength = false;
|
||||
_contentType = contentType;
|
||||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
return _content(data, len, _sentLength);
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
_filledLength += ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Chunked Response
|
||||
* */
|
||||
|
||||
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback){
|
||||
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = 0;
|
||||
_contentType = contentType;
|
||||
_sendContentLength = false;
|
||||
_chunked = true;
|
||||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
return _content(data, len, _sentLength);
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
_filledLength += ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Progmem Response
|
||||
* */
|
||||
|
||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len){
|
||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
|
||||
_code = code;
|
||||
_content = content;
|
||||
_contentType = contentType;
|
||||
_contentLength = len;
|
||||
_readLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
size_t left = _contentLength - _sentLength;
|
||||
if (left > len) {
|
||||
memcpy_P(data, _content + _sentLength, len);
|
||||
return len;
|
||||
}
|
||||
memcpy_P(data, _content + _sentLength, left);
|
||||
return left;
|
||||
size_t left = _contentLength - _readLength;
|
||||
if (left > len) {
|
||||
memcpy_P(data, _content + _readLength, len);
|
||||
_readLength += len;
|
||||
return len;
|
||||
}
|
||||
memcpy_P(data, _content + _readLength, left);
|
||||
_readLength += left;
|
||||
return left;
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user