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 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](#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 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](#respond-with-content-using-a-callback)
|
||||||
- [Respond with content using a callback and extra headers](#respond-with-content-using-a-callback-and-extra-headers)
|
- [Respond with content using a callback and extra headers](#respond-with-content-using-a-callback-and-extra-headers)
|
||||||
- [Chunked Response](#chunked-response)
|
- [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)
|
- [Serving files in directory](#serving-files-in-directory)
|
||||||
- [Specifying Cache-Control header](#specifying-cache-control-header)
|
- [Specifying Cache-Control header](#specifying-cache-control-header)
|
||||||
- [Specifying Date-Modified header](#specifying-date-modified-header)
|
- [Specifying Date-Modified header](#specifying-date-modified-header)
|
||||||
|
- [Specifying Template Processor callback](#specifying-template-processor-callback)
|
||||||
- [Using filters](#using-filters)
|
- [Using filters](#using-filters)
|
||||||
- [Serve different site files in AP mode](#serve-different-site-files-in-ap-mode)
|
- [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)
|
- [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);
|
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
|
### Respond with content using a callback
|
||||||
```cpp
|
```cpp
|
||||||
//send 128 bytes as plain text
|
//send 128 bytes as plain text
|
||||||
@@ -610,6 +635,23 @@ saveDateModified(date_modified); // Save for next reset
|
|||||||
handler->setLastModified(date_modified);
|
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
|
## Using filters
|
||||||
Filters can be set to `Rewrite` or `Handler` in order to control when to apply the rewrite and consider the handler.
|
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
|
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 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<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller;
|
||||||
|
typedef std::function<String(const String&)> AwsTemplateProcessor;
|
||||||
|
|
||||||
class AsyncWebServerRequest {
|
class AsyncWebServerRequest {
|
||||||
using File = fs::File;
|
using File = fs::File;
|
||||||
@@ -216,23 +217,23 @@ class AsyncWebServerRequest {
|
|||||||
|
|
||||||
void send(AsyncWebServerResponse *response);
|
void send(AsyncWebServerResponse *response);
|
||||||
void send(int code, const String& contentType=String(), const String& content=String());
|
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(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);
|
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);
|
void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
void send(const String& contentType, size_t len, AwsResponseFiller callback);
|
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
void sendChunked(const String& contentType, AwsResponseFiller callback);
|
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);
|
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);
|
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(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(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);
|
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);
|
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback);
|
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback);
|
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460);
|
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, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content);
|
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
||||||
|
|
||||||
size_t headers() const; // get header count
|
size_t headers() const; // get header count
|
||||||
bool hasHeader(const String& name) const; // check if header exists
|
bool hasHeader(const String& name) const; // check if header exists
|
||||||
|
@@ -39,6 +39,7 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
|
|||||||
String _default_file;
|
String _default_file;
|
||||||
String _cache_control;
|
String _cache_control;
|
||||||
String _last_modified;
|
String _last_modified;
|
||||||
|
AwsTemplateProcessor _callback;
|
||||||
bool _isDir;
|
bool _isDir;
|
||||||
bool _gzipFirst;
|
bool _gzipFirst;
|
||||||
uint8_t _gzipStats;
|
uint8_t _gzipStats;
|
||||||
@@ -55,6 +56,7 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
|
|||||||
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
||||||
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
||||||
#endif
|
#endif
|
||||||
|
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncCallbackWebHandler: public AsyncWebHandler {
|
class AsyncCallbackWebHandler: public AsyncWebHandler {
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
#include "WebHandlerImpl.h"
|
#include "WebHandlerImpl.h"
|
||||||
|
|
||||||
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
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 '/'
|
// Ensure leading '/'
|
||||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
|
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
|
||||||
@@ -199,7 +199,7 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
|||||||
response->addHeader("ETag", etag);
|
response->addHeader("ETag", etag);
|
||||||
request->send(response);
|
request->send(response);
|
||||||
} else {
|
} else {
|
||||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename);
|
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
||||||
if (_last_modified.length())
|
if (_last_modified.length())
|
||||||
response->addHeader("Last-Modified", _last_modified);
|
response->addHeader("Last-Modified", _last_modified);
|
||||||
if (_cache_control.length()){
|
if (_cache_control.length()){
|
||||||
|
@@ -710,78 +710,78 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const St
|
|||||||
return new AsyncBasicResponse(code, contentType, content);
|
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")))
|
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;
|
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)
|
if(content == true)
|
||||||
return new AsyncFileResponse(content, path, contentType, download);
|
return new AsyncFileResponse(content, path, contentType, download, callback);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len){
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
||||||
return new AsyncStreamResponse(stream, contentType, len);
|
return new AsyncStreamResponse(stream, contentType, len, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback){
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||||
return new AsyncCallbackResponse(contentType, len, callback);
|
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)
|
if(_version)
|
||||||
return new AsyncChunkedResponse(contentType, callback);
|
return new AsyncChunkedResponse(contentType, callback, templateCallback);
|
||||||
return new AsyncCallbackResponse(contentType, 0, callback);
|
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){
|
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){
|
||||||
return new AsyncResponseStream(contentType, bufferSize);
|
return new AsyncResponseStream(contentType, bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t 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);
|
return new AsyncProgmemResponse(code, contentType, content, len, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_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));
|
return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
|
void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
|
||||||
send(beginResponse(code, contentType, 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"))){
|
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);
|
} 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){
|
if(content == true){
|
||||||
send(beginResponse(content, path, contentType, download));
|
send(beginResponse(content, path, contentType, download, callback));
|
||||||
} else send(404);
|
} else send(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len){
|
void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
||||||
send(beginResponse(stream, contentType, len));
|
send(beginResponse(stream, contentType, len, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback){
|
void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||||
send(beginResponse(contentType, len, callback));
|
send(beginResponse(contentType, len, callback, templateCallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback){
|
void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||||
send(beginChunkedResponse(contentType, callback));
|
send(beginChunkedResponse(contentType, callback, templateCallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t 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));
|
send(beginResponse_P(code, contentType, content, len, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content){
|
void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
|
||||||
send(beginResponse_P(code, contentType, content));
|
send(beginResponse_P(code, contentType, content, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::redirect(const String& url){
|
void AsyncWebServerRequest::redirect(const String& url){
|
||||||
|
@@ -21,6 +21,14 @@
|
|||||||
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
|
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||||
#define 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 {
|
class AsyncBasicResponse: public AsyncWebServerResponse {
|
||||||
private:
|
private:
|
||||||
String _content;
|
String _content;
|
||||||
@@ -34,13 +42,21 @@ class AsyncBasicResponse: public AsyncWebServerResponse {
|
|||||||
class AsyncAbstractResponse: public AsyncWebServerResponse {
|
class AsyncAbstractResponse: public AsyncWebServerResponse {
|
||||||
private:
|
private:
|
||||||
String _head;
|
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:
|
public:
|
||||||
|
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr);
|
||||||
void _respond(AsyncWebServerRequest *request);
|
void _respond(AsyncWebServerRequest *request);
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||||
bool _sourceValid() const { return false; }
|
bool _sourceValid() const { return false; }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
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 {
|
class AsyncFileResponse: public AsyncAbstractResponse {
|
||||||
using File = fs::File;
|
using File = fs::File;
|
||||||
using FS = fs::FS;
|
using FS = fs::FS;
|
||||||
@@ -49,8 +65,8 @@ class AsyncFileResponse: public AsyncAbstractResponse {
|
|||||||
String _path;
|
String _path;
|
||||||
void _setContentType(const String& path);
|
void _setContentType(const String& path);
|
||||||
public:
|
public:
|
||||||
AsyncFileResponse(FS &fs, 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);
|
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||||
~AsyncFileResponse();
|
~AsyncFileResponse();
|
||||||
bool _sourceValid() const { return !!(_content); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
@@ -60,7 +76,7 @@ class AsyncStreamResponse: public AsyncAbstractResponse {
|
|||||||
private:
|
private:
|
||||||
Stream *_content;
|
Stream *_content;
|
||||||
public:
|
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); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
@@ -68,8 +84,9 @@ class AsyncStreamResponse: public AsyncAbstractResponse {
|
|||||||
class AsyncCallbackResponse: public AsyncAbstractResponse {
|
class AsyncCallbackResponse: public AsyncAbstractResponse {
|
||||||
private:
|
private:
|
||||||
AwsResponseFiller _content;
|
AwsResponseFiller _content;
|
||||||
|
size_t _filledLength;
|
||||||
public:
|
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); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
@@ -77,8 +94,9 @@ class AsyncCallbackResponse: public AsyncAbstractResponse {
|
|||||||
class AsyncChunkedResponse: public AsyncAbstractResponse {
|
class AsyncChunkedResponse: public AsyncAbstractResponse {
|
||||||
private:
|
private:
|
||||||
AwsResponseFiller _content;
|
AwsResponseFiller _content;
|
||||||
|
size_t _filledLength;
|
||||||
public:
|
public:
|
||||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback);
|
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
bool _sourceValid() const { return !!(_content); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
@@ -86,8 +104,9 @@ class AsyncChunkedResponse: public AsyncAbstractResponse {
|
|||||||
class AsyncProgmemResponse: public AsyncAbstractResponse {
|
class AsyncProgmemResponse: public AsyncAbstractResponse {
|
||||||
private:
|
private:
|
||||||
const uint8_t * _content;
|
const uint8_t * _content;
|
||||||
|
size_t _readLength;
|
||||||
public:
|
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; }
|
bool _sourceValid() const { return true; }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
|
@@ -22,6 +22,17 @@
|
|||||||
#include "WebResponseImpl.h"
|
#include "WebResponseImpl.h"
|
||||||
#include "cbuf.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
|
* Abstract Response
|
||||||
* */
|
* */
|
||||||
@@ -228,6 +239,16 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint
|
|||||||
* Abstract Response
|
* 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){
|
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
|
||||||
addHeader("Connection","close");
|
addHeader("Connection","close");
|
||||||
_head = _assembleHead(request->version());
|
_head = _assembleHead(request->version());
|
||||||
@@ -277,6 +298,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(headLen){
|
if(headLen){
|
||||||
|
//TODO: memcpy should be faster?
|
||||||
sprintf((char*)buf, "%s", _head.c_str());
|
sprintf((char*)buf, "%s", _head.c_str());
|
||||||
_head = String();
|
_head = String();
|
||||||
}
|
}
|
||||||
@@ -295,7 +317,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
|||||||
buf[outLen++] = '\r';
|
buf[outLen++] = '\r';
|
||||||
buf[outLen++] = '\n';
|
buf[outLen++] = '\n';
|
||||||
} else {
|
} else {
|
||||||
outLen = _fillBuffer(buf+headLen, outLen) + headLen;
|
outLen = _fillBufferAndProcessTemplates(buf+headLen, outLen) + headLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(outLen)
|
if(outLen)
|
||||||
@@ -308,7 +330,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
|||||||
|
|
||||||
free(buf);
|
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;
|
_state = RESPONSE_WAIT_ACK;
|
||||||
}
|
}
|
||||||
return outLen;
|
return outLen;
|
||||||
@@ -323,7 +345,118 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
|||||||
return 0;
|
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";
|
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;
|
_code = 200;
|
||||||
_path = path;
|
_path = path;
|
||||||
|
|
||||||
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
|
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
|
||||||
_path = _path+".gz";
|
_path = _path+".gz";
|
||||||
addHeader("Content-Encoding", "gzip");
|
addHeader("Content-Encoding", "gzip");
|
||||||
|
_callback = nullptr; // Unable to process zipped templates
|
||||||
|
_sendContentLength = true;
|
||||||
|
_chunked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_content = fs.open(_path, "r");
|
_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);
|
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
|
||||||
}
|
}
|
||||||
addHeader("Content-Disposition", buf);
|
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;
|
_code = 200;
|
||||||
_path = path;
|
_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;
|
_content = content;
|
||||||
_contentLength = _content.size();
|
_contentLength = _content.size();
|
||||||
|
|
||||||
if(!download && String(_content.name()).endsWith(".gz") && !path.endsWith(".gz"))
|
|
||||||
addHeader("Content-Encoding", "gzip");
|
|
||||||
|
|
||||||
if(contentType == "")
|
if(contentType == "")
|
||||||
_setContentType(path);
|
_setContentType(path);
|
||||||
else
|
else
|
||||||
@@ -424,7 +564,7 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
|
|||||||
* Stream Response
|
* 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;
|
_code = 200;
|
||||||
_content = &stream;
|
_content = &stream;
|
||||||
_contentLength = len;
|
_contentLength = len;
|
||||||
@@ -444,55 +584,64 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
|
|||||||
* Callback Response
|
* 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;
|
_code = 200;
|
||||||
_content = callback;
|
_content = callback;
|
||||||
_contentLength = len;
|
_contentLength = len;
|
||||||
if(!len)
|
if(!len)
|
||||||
_sendContentLength = false;
|
_sendContentLength = false;
|
||||||
_contentType = contentType;
|
_contentType = contentType;
|
||||||
|
_filledLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
|
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
|
* Chunked Response
|
||||||
* */
|
* */
|
||||||
|
|
||||||
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback){
|
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
|
||||||
_code = 200;
|
_code = 200;
|
||||||
_content = callback;
|
_content = callback;
|
||||||
_contentLength = 0;
|
_contentLength = 0;
|
||||||
_contentType = contentType;
|
_contentType = contentType;
|
||||||
_sendContentLength = false;
|
_sendContentLength = false;
|
||||||
_chunked = true;
|
_chunked = true;
|
||||||
|
_filledLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
|
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
|
* 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;
|
_code = code;
|
||||||
_content = content;
|
_content = content;
|
||||||
_contentType = contentType;
|
_contentType = contentType;
|
||||||
_contentLength = len;
|
_contentLength = len;
|
||||||
|
_readLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
|
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||||
size_t left = _contentLength - _sentLength;
|
size_t left = _contentLength - _readLength;
|
||||||
if (left > len) {
|
if (left > len) {
|
||||||
memcpy_P(data, _content + _sentLength, len);
|
memcpy_P(data, _content + _readLength, len);
|
||||||
return len;
|
_readLength += len;
|
||||||
}
|
return len;
|
||||||
memcpy_P(data, _content + _sentLength, left);
|
}
|
||||||
return left;
|
memcpy_P(data, _content + _readLength, left);
|
||||||
|
_readLength += left;
|
||||||
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user