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:
Alexandr Zarubkin
2017-08-18 17:59:06 +03:00
committed by Me No Dev
parent 77a520ba24
commit 0179c17cf9
7 changed files with 285 additions and 72 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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