ESPAsyncWebServer/src/WebResponses.cpp

875 lines
28 KiB
C++

/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebResponseImpl.h"
#include "cbuf.h"
using namespace asyncsrv;
// 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
*
*/
#ifndef ESP8266
const char* AsyncWebServerResponse::responseCodeToString(int code) {
switch (code) {
case 100:
return T_HTTP_CODE_100;
case 101:
return T_HTTP_CODE_101;
case 200:
return T_HTTP_CODE_200;
case 201:
return T_HTTP_CODE_201;
case 202:
return T_HTTP_CODE_202;
case 203:
return T_HTTP_CODE_203;
case 204:
return T_HTTP_CODE_204;
case 205:
return T_HTTP_CODE_205;
case 206:
return T_HTTP_CODE_206;
case 300:
return T_HTTP_CODE_300;
case 301:
return T_HTTP_CODE_301;
case 302:
return T_HTTP_CODE_302;
case 303:
return T_HTTP_CODE_303;
case 304:
return T_HTTP_CODE_304;
case 305:
return T_HTTP_CODE_305;
case 307:
return T_HTTP_CODE_307;
case 400:
return T_HTTP_CODE_400;
case 401:
return T_HTTP_CODE_401;
case 402:
return T_HTTP_CODE_402;
case 403:
return T_HTTP_CODE_403;
case 404:
return T_HTTP_CODE_404;
case 405:
return T_HTTP_CODE_405;
case 406:
return T_HTTP_CODE_406;
case 407:
return T_HTTP_CODE_407;
case 408:
return T_HTTP_CODE_408;
case 409:
return T_HTTP_CODE_409;
case 410:
return T_HTTP_CODE_410;
case 411:
return T_HTTP_CODE_411;
case 412:
return T_HTTP_CODE_412;
case 413:
return T_HTTP_CODE_413;
case 414:
return T_HTTP_CODE_414;
case 415:
return T_HTTP_CODE_415;
case 416:
return T_HTTP_CODE_416;
case 417:
return T_HTTP_CODE_417;
case 500:
return T_HTTP_CODE_500;
case 501:
return T_HTTP_CODE_501;
case 502:
return T_HTTP_CODE_502;
case 503:
return T_HTTP_CODE_503;
case 504:
return T_HTTP_CODE_504;
case 505:
return T_HTTP_CODE_505;
default:
return T_HTTP_CODE_ANY;
}
}
#else // ESP8266
const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code)
{
switch (code) {
case 100:
return FPSTR(T_HTTP_CODE_100);
case 101:
return FPSTR(T_HTTP_CODE_101);
case 200:
return FPSTR(T_HTTP_CODE_200);
case 201:
return FPSTR(T_HTTP_CODE_201);
case 202:
return FPSTR(T_HTTP_CODE_202);
case 203:
return FPSTR(T_HTTP_CODE_203);
case 204:
return FPSTR(T_HTTP_CODE_204);
case 205:
return FPSTR(T_HTTP_CODE_205);
case 206:
return FPSTR(T_HTTP_CODE_206);
case 300:
return FPSTR(T_HTTP_CODE_300);
case 301:
return FPSTR(T_HTTP_CODE_301);
case 302:
return FPSTR(T_HTTP_CODE_302);
case 303:
return FPSTR(T_HTTP_CODE_303);
case 304:
return FPSTR(T_HTTP_CODE_304);
case 305:
return FPSTR(T_HTTP_CODE_305);
case 307:
return FPSTR(T_HTTP_CODE_307);
case 400:
return FPSTR(T_HTTP_CODE_400);
case 401:
return FPSTR(T_HTTP_CODE_401);
case 402:
return FPSTR(T_HTTP_CODE_402);
case 403:
return FPSTR(T_HTTP_CODE_403);
case 404:
return FPSTR(T_HTTP_CODE_404);
case 405:
return FPSTR(T_HTTP_CODE_405);
case 406:
return FPSTR(T_HTTP_CODE_406);
case 407:
return FPSTR(T_HTTP_CODE_407);
case 408:
return FPSTR(T_HTTP_CODE_408);
case 409:
return FPSTR(T_HTTP_CODE_409);
case 410:
return FPSTR(T_HTTP_CODE_410);
case 411:
return FPSTR(T_HTTP_CODE_411);
case 412:
return FPSTR(T_HTTP_CODE_412);
case 413:
return FPSTR(T_HTTP_CODE_413);
case 414:
return FPSTR(T_HTTP_CODE_414);
case 415:
return FPSTR(T_HTTP_CODE_415);
case 416:
return FPSTR(T_HTTP_CODE_416);
case 417:
return FPSTR(T_HTTP_CODE_417);
case 500:
return FPSTR(T_HTTP_CODE_500);
case 501:
return FPSTR(T_HTTP_CODE_501);
case 502:
return FPSTR(T_HTTP_CODE_502);
case 503:
return FPSTR(T_HTTP_CODE_503);
case 504:
return FPSTR(T_HTTP_CODE_504);
case 505:
return FPSTR(T_HTTP_CODE_505);
default:
return FPSTR(T_HTTP_CODE_ANY);
}
}
#endif // ESP8266
AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) {
for (const auto& header : DefaultHeaders::Instance()) {
_headers.emplace_back(header);
}
}
AsyncWebServerResponse::~AsyncWebServerResponse() = default;
void AsyncWebServerResponse::setCode(int code) {
if (_state == RESPONSE_SETUP)
_code = code;
}
void AsyncWebServerResponse::setContentLength(size_t len) {
if (_state == RESPONSE_SETUP)
_contentLength = len;
}
void AsyncWebServerResponse::setContentType(const char* type) {
if (_state == RESPONSE_SETUP)
_contentType = type;
}
bool AsyncWebServerResponse::removeHeader(const char* name) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) {
_headers.erase(i);
return true;
}
}
return false;
}
bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool replaceExisting) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) {
// header already set
if (replaceExisting) {
// remove, break and add the new one
_headers.erase(i);
break;
} else {
// do not update
return false;
}
}
}
// header was not found found, or existing one was removed
_headers.emplace_back(name, value);
return true;
}
String AsyncWebServerResponse::_assembleHead(uint8_t version) {
if (version) {
addHeader(T_Accept_Ranges, T_none, false);
if (_chunked)
addHeader(T_Transfer_Encoding, T_chunked, false);
}
String out;
constexpr size_t bufSize = 300;
char buf[bufSize];
#ifndef ESP8266
snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, responseCodeToString(_code));
#else
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, String(responseCodeToString(_code)).c_str());
#endif
out.concat(buf);
if (_sendContentLength) {
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
out.concat(buf);
}
if (_contentType.length()) {
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
out.concat(buf);
}
for (const auto& header : _headers) {
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
out.concat(buf);
}
_headers.clear();
out.concat(T_rn);
_headLength = out.length();
return out;
}
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
bool AsyncWebServerResponse::_sourceValid() const { return false; }
void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) {
_state = RESPONSE_END;
request->client()->close();
}
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
(void)request;
(void)len;
(void)time;
return 0;
}
/*
* String/Code Response
* */
AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const char* content) {
_code = code;
_content = content;
_contentType = contentType;
if (_content.length()) {
_contentLength = _content.length();
if (!_contentType.length())
_contentType = T_text_plain;
}
addHeader(T_Connection, T_close, false);
}
void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
_state = RESPONSE_HEADERS;
String out = _assembleHead(request->version());
size_t outLen = out.length();
size_t space = request->client()->space();
if (!_contentLength && space >= outLen) {
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK;
} else if (_contentLength && space >= outLen + _contentLength) {
out += _content;
outLen += _contentLength;
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK;
} else if (space && space < outLen) {
String partial = out.substring(0, space);
_content = out.substring(space) + _content;
_contentLength += outLen - space;
_writtenLength += request->client()->write(partial.c_str(), partial.length());
_state = RESPONSE_CONTENT;
} else if (space > outLen && space < (outLen + _contentLength)) {
size_t shift = space - outLen;
outLen += shift;
_sentLength += shift;
out += _content.substring(0, shift);
_content = _content.substring(shift);
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_CONTENT;
} else {
_content = out + _content;
_contentLength += outLen;
_state = RESPONSE_CONTENT;
}
}
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
(void)time;
_ackedLength += len;
if (_state == RESPONSE_CONTENT) {
size_t available = _contentLength - _sentLength;
size_t space = request->client()->space();
// we can fit in this packet
if (space > available) {
_writtenLength += request->client()->write(_content.c_str(), available);
_content = emptyString;
_state = RESPONSE_WAIT_ACK;
return available;
}
// send some data, the rest on ack
String out = _content.substring(0, space);
_content = _content.substring(space);
_sentLength += space;
_writtenLength += request->client()->write(out.c_str(), space);
return space;
} else if (_state == RESPONSE_WAIT_ACK) {
if (_ackedLength >= _writtenLength) {
_state = RESPONSE_END;
}
}
return 0;
}
/*
* 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(T_Connection, T_close, false);
_head = _assembleHead(request->version());
_state = RESPONSE_HEADERS;
_ack(request, 0, 0);
}
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
(void)time;
if (!_sourceValid()) {
_state = RESPONSE_FAILED;
request->client()->close();
return 0;
}
_ackedLength += len;
size_t space = request->client()->space();
size_t headLen = _head.length();
if (_state == RESPONSE_HEADERS) {
if (space >= headLen) {
_state = RESPONSE_CONTENT;
space -= headLen;
} else {
String out = _head.substring(0, space);
_head = _head.substring(space);
_writtenLength += request->client()->write(out.c_str(), out.length());
return out.length();
}
}
if (_state == RESPONSE_CONTENT) {
size_t outLen;
if (_chunked) {
if (space <= 8) {
return 0;
}
outLen = space;
} else if (!_sendContentLength) {
outLen = space;
} else {
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
}
uint8_t* buf = (uint8_t*)malloc(outLen + headLen);
if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen);
return 0;
}
if (headLen) {
memcpy(buf, _head.c_str(), _head.length());
}
size_t readLen = 0;
if (_chunked) {
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
// See RFC2616 sections 2, 3.6.1.
readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8);
if (readLen == RESPONSE_TRY_AGAIN) {
free(buf);
return 0;
}
outLen = sprintf_P((char*)buf + headLen, PSTR("%x"), readLen) + headLen;
while (outLen < headLen + 4)
buf[outLen++] = ' ';
buf[outLen++] = '\r';
buf[outLen++] = '\n';
outLen += readLen;
buf[outLen++] = '\r';
buf[outLen++] = '\n';
} else {
readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
if (readLen == RESPONSE_TRY_AGAIN) {
free(buf);
return 0;
}
outLen = readLen + headLen;
}
if (headLen) {
_head = emptyString;
}
if (outLen) {
_writtenLength += request->client()->write((const char*)buf, outLen);
}
if (_chunked) {
_sentLength += readLen;
} else {
_sentLength += outLen - headLen;
}
free(buf);
if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) {
_state = RESPONSE_WAIT_ACK;
}
return outLen;
} else if (_state == RESPONSE_WAIT_ACK) {
if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END;
if (!_chunked && !_sendContentLength)
request->client()->close(true);
}
}
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;
// If closing placeholder is found:
if (pTemplateEnd) {
// prepare argument to callback
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(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
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
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.begin(), 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)) {
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
// 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);
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
} 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(_cache.begin(), 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;
}
/*
* File Response
* */
AsyncFileResponse::~AsyncFileResponse() {
if (_content)
_content.close();
}
void AsyncFileResponse::_setContentTypeFromPath(const String& path) {
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
#ifndef ESP8266
extern const char* getContentType(const String& path);
#else
extern const __FlashStringHelper* getContentType(const String& path);
#endif
_contentType = getContentType(path);
#else
if (path.endsWith(T__html))
_contentType = T_text_html;
else if (path.endsWith(T__htm))
_contentType = T_text_html;
else if (path.endsWith(T__css))
_contentType = T_text_css;
else if (path.endsWith(T__json))
_contentType = T_application_json;
else if (path.endsWith(T__js))
_contentType = T_application_javascript;
else if (path.endsWith(T__png))
_contentType = T_image_png;
else if (path.endsWith(T__gif))
_contentType = T_image_gif;
else if (path.endsWith(T__jpg))
_contentType = T_image_jpeg;
else if (path.endsWith(T__ico))
_contentType = T_image_x_icon;
else if (path.endsWith(T__svg))
_contentType = T_image_svg_xml;
else if (path.endsWith(T__eot))
_contentType = T_font_eot;
else if (path.endsWith(T__woff))
_contentType = T_font_woff;
else if (path.endsWith(T__woff2))
_contentType = T_font_woff2;
else if (path.endsWith(T__ttf))
_contentType = T_font_ttf;
else if (path.endsWith(T__xml))
_contentType = T_text_xml;
else if (path.endsWith(T__pdf))
_contentType = T_application_pdf;
else if (path.endsWith(T__zip))
_contentType = T_application_zip;
else if (path.endsWith(T__gz))
_contentType = T_application_x_gzip;
else
_contentType = T_text_plain;
#endif
}
AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
_path = _path + T__gz;
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
}
_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();
if (strlen(contentType) == 0)
_setContentTypeFromPath(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if (download) {
// set filename and force download
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
} else {
// set filename and force rendering
snprintf_P(buf, sizeof(buf), PSTR("inline"));
}
addHeader(T_Content_Disposition, buf, false);
}
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process gzipped templates
_sendContentLength = true;
_chunked = false;
}
_content = content;
_contentLength = _content.size();
if (strlen(contentType) == 0)
_setContentTypeFromPath(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if (download) {
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
} else {
snprintf_P(buf, sizeof(buf), PSTR("inline"));
}
addHeader(T_Content_Disposition, buf, false);
}
size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
return _content.read(data, len);
}
/*
* Stream Response
* */
AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = 200;
_content = &stream;
_contentLength = len;
_contentType = contentType;
}
size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t available = _content->available();
size_t outLen = (available > len) ? len : available;
size_t i;
for (i = 0; i < outLen; i++)
data[i] = _content->read();
return outLen;
}
/*
* Callback Response
* */
AsyncCallbackResponse::AsyncCallbackResponse(const char* 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) {
size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret;
}
return ret;
}
/*
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(const char* 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) {
size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret;
}
return ret;
}
/*
* Progmem Response
* */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* 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 - _readLength;
if (left > len) {
memcpy_P(data, _content + _readLength, len);
_readLength += len;
return len;
}
memcpy_P(data, _content + _readLength, left);
_readLength += left;
return left;
}
/*
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
* */
AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferSize) {
_code = 200;
_contentLength = 0;
_contentType = contentType;
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); // std::make_unique<cbuf>(bufferSize);
}
AsyncResponseStream::~AsyncResponseStream() = default;
size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
return _content->read((char*)buf, maxLen);
}
size_t AsyncResponseStream::write(const uint8_t* data, size_t len) {
if (_started())
return 0;
if (len > _content->room()) {
size_t needed = len - _content->room();
_content->resizeAdd(needed);
}
size_t written = _content->write((const char*)data, len);
_contentLength += written;
return written;
}
size_t AsyncResponseStream::write(uint8_t data) {
return write(&data, 1);
}