Aktualizace na verzi 3.0.6
This commit is contained in:
@ -18,43 +18,43 @@
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "Arduino.h"
|
||||
#include "AsyncEventSource.h"
|
||||
#ifndef ESP8266
|
||||
#if defined(ESP32)
|
||||
#include <rom/ets_sys.h>
|
||||
#endif
|
||||
#include "AsyncEventSource.h"
|
||||
|
||||
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||
String ev;
|
||||
|
||||
if(reconnect){
|
||||
if (reconnect) {
|
||||
ev += F("retry: ");
|
||||
ev += reconnect;
|
||||
ev += F("\r\n");
|
||||
}
|
||||
|
||||
if(id){
|
||||
if (id) {
|
||||
ev += F("id: ");
|
||||
ev += String(id);
|
||||
ev += F("\r\n");
|
||||
}
|
||||
|
||||
if(event != NULL){
|
||||
if (event != NULL) {
|
||||
ev += F("event: ");
|
||||
ev += String(event);
|
||||
ev += F("\r\n");
|
||||
}
|
||||
|
||||
if(message != NULL){
|
||||
if (message != NULL) {
|
||||
size_t messageLen = strlen(message);
|
||||
char * lineStart = (char *)message;
|
||||
char * lineEnd;
|
||||
char* lineStart = (char*)message;
|
||||
char* lineEnd;
|
||||
do {
|
||||
char * nextN = strchr(lineStart, '\n');
|
||||
char * nextR = strchr(lineStart, '\r');
|
||||
if(nextN == NULL && nextR == NULL){
|
||||
size_t llen = ((char *)message + messageLen) - lineStart;
|
||||
char * ldata = (char *)malloc(llen+1);
|
||||
if(ldata != NULL){
|
||||
char* nextN = strchr(lineStart, '\n');
|
||||
char* nextR = strchr(lineStart, '\r');
|
||||
if (nextN == NULL && nextR == NULL) {
|
||||
size_t llen = ((char*)message + messageLen) - lineStart;
|
||||
char* ldata = (char*)malloc(llen + 1);
|
||||
if (ldata != NULL) {
|
||||
memcpy(ldata, lineStart, llen);
|
||||
ldata[llen] = 0;
|
||||
ev += F("data: ");
|
||||
@ -62,24 +62,24 @@ static String generateEventMessage(const char *message, const char *event, uint3
|
||||
ev += F("\r\n\r\n");
|
||||
free(ldata);
|
||||
}
|
||||
lineStart = (char *)message + messageLen;
|
||||
lineStart = (char*)message + messageLen;
|
||||
} else {
|
||||
char * nextLine = NULL;
|
||||
if(nextN != NULL && nextR != NULL){
|
||||
if(nextR < nextN){
|
||||
char* nextLine = NULL;
|
||||
if (nextN != NULL && nextR != NULL) {
|
||||
if (nextR < nextN) {
|
||||
lineEnd = nextR;
|
||||
if(nextN == (nextR + 1))
|
||||
if (nextN == (nextR + 1))
|
||||
nextLine = nextN + 1;
|
||||
else
|
||||
nextLine = nextR + 1;
|
||||
} else {
|
||||
lineEnd = nextN;
|
||||
if(nextR == (nextN + 1))
|
||||
if (nextR == (nextN + 1))
|
||||
nextLine = nextR + 1;
|
||||
else
|
||||
nextLine = nextN + 1;
|
||||
}
|
||||
} else if(nextN != NULL){
|
||||
} else if (nextN != NULL) {
|
||||
lineEnd = nextN;
|
||||
nextLine = nextN + 1;
|
||||
} else {
|
||||
@ -88,8 +88,8 @@ static String generateEventMessage(const char *message, const char *event, uint3
|
||||
}
|
||||
|
||||
size_t llen = lineEnd - lineStart;
|
||||
char * ldata = (char *)malloc(llen+1);
|
||||
if(ldata != NULL){
|
||||
char* ldata = (char*)malloc(llen + 1);
|
||||
if (ldata != NULL) {
|
||||
memcpy(ldata, lineStart, llen);
|
||||
ldata[llen] = 0;
|
||||
ev += F("data: ");
|
||||
@ -98,10 +98,10 @@ static String generateEventMessage(const char *message, const char *event, uint3
|
||||
free(ldata);
|
||||
}
|
||||
lineStart = nextLine;
|
||||
if(lineStart == ((char *)message + messageLen))
|
||||
if (lineStart == ((char*)message + messageLen))
|
||||
ev += F("\r\n");
|
||||
}
|
||||
} while(lineStart < ((char *)message + messageLen));
|
||||
} while (lineStart < ((char*)message + messageLen));
|
||||
}
|
||||
|
||||
return ev;
|
||||
@ -109,11 +109,10 @@ static String generateEventMessage(const char *message, const char *event, uint3
|
||||
|
||||
// Message
|
||||
|
||||
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
||||
: _data(nullptr), _len(len), _sent(0), _acked(0)
|
||||
{
|
||||
_data = (uint8_t*)malloc(_len+1);
|
||||
if(_data == nullptr){
|
||||
AsyncEventSourceMessage::AsyncEventSourceMessage(const char* data, size_t len)
|
||||
: _data(nullptr), _len(len), _sent(0), _acked(0) {
|
||||
_data = (uint8_t*)malloc(_len + 1);
|
||||
if (_data == nullptr) {
|
||||
_len = 0;
|
||||
} else {
|
||||
memcpy(_data, data, len);
|
||||
@ -122,18 +121,18 @@ AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
||||
}
|
||||
|
||||
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
|
||||
if(_data != NULL)
|
||||
free(_data);
|
||||
if (_data != NULL)
|
||||
free(_data);
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
|
||||
(void)time;
|
||||
// If the whole message is now acked...
|
||||
if(_acked + len > _len){
|
||||
// Return the number of extra bytes acked (they will be carried on to the next message)
|
||||
const size_t extra = _acked + len - _len;
|
||||
_acked = _len;
|
||||
return extra;
|
||||
if (_acked + len > _len) {
|
||||
// Return the number of extra bytes acked (they will be carried on to the next message)
|
||||
const size_t extra = _acked + len - _len;
|
||||
_acked = _len;
|
||||
return extra;
|
||||
}
|
||||
// Return that no extra bytes left.
|
||||
_acked += len;
|
||||
@ -142,188 +141,173 @@ size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
|
||||
|
||||
// This could also return void as the return value is not used.
|
||||
// Leaving as-is for compatibility...
|
||||
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
|
||||
if (_sent >= _len) {
|
||||
return 0;
|
||||
}
|
||||
const size_t len_to_send = _len - _sent;
|
||||
auto position = reinterpret_cast<const char*>(_data + _sent);
|
||||
const size_t sent_now = client->write(position, len_to_send);
|
||||
_sent += sent_now;
|
||||
return sent_now;
|
||||
size_t AsyncEventSourceMessage::send(AsyncClient* client) {
|
||||
if (_sent >= _len) {
|
||||
return 0;
|
||||
}
|
||||
const size_t len_to_send = _len - _sent;
|
||||
auto position = reinterpret_cast<const char*>(_data + _sent);
|
||||
const size_t sent_now = client->write(position, len_to_send);
|
||||
_sent += sent_now;
|
||||
return sent_now;
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
|
||||
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
|
||||
{
|
||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server) {
|
||||
_client = request->client();
|
||||
_server = server;
|
||||
_lastId = 0;
|
||||
if(request->hasHeader(F("Last-Event-ID")))
|
||||
if (request->hasHeader(F("Last-Event-ID")))
|
||||
_lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str());
|
||||
|
||||
_client->setRxTimeout(0);
|
||||
_client->onError(NULL, NULL);
|
||||
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
|
||||
_client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
|
||||
_client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
|
||||
_client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
|
||||
_client->onData(NULL, NULL);
|
||||
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
||||
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
||||
_client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
||||
_client->onDisconnect([this](void* r, AsyncClient* c) { ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
||||
|
||||
_server->_addClient(this);
|
||||
delete request;
|
||||
}
|
||||
|
||||
AsyncEventSourceClient::~AsyncEventSourceClient(){
|
||||
_lockmq.lock();
|
||||
_messageQueue.free();
|
||||
_lockmq.unlock();
|
||||
AsyncEventSourceClient::~AsyncEventSourceClient() {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
#endif
|
||||
_messageQueue.clear();
|
||||
close();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
|
||||
if(dataMessage == NULL)
|
||||
return;
|
||||
if(!connected()){
|
||||
delete dataMessage;
|
||||
return;
|
||||
}
|
||||
//length() is not thread-safe, thus acquiring the lock before this call..
|
||||
_lockmq.lock();
|
||||
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
|
||||
void AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
|
||||
#ifdef ESP32
|
||||
// length() is not thread-safe, thus acquiring the lock before this call..
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
#endif
|
||||
|
||||
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
|
||||
#ifdef ESP8266
|
||||
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
|
||||
#else
|
||||
#elif defined(ESP32)
|
||||
log_e("Too many messages queued: deleting message");
|
||||
#endif
|
||||
delete dataMessage;
|
||||
} else {
|
||||
_messageQueue.add(dataMessage);
|
||||
// runqueue trigger when new messages added
|
||||
if(_client->canSend()) {
|
||||
_runQueue();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_lockmq.unlock();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
|
||||
// Same here, acquiring the lock early
|
||||
_lockmq.lock();
|
||||
while(len && !_messageQueue.isEmpty()){
|
||||
len = _messageQueue.front()->ack(len, time);
|
||||
if(_messageQueue.front()->finished())
|
||||
_messageQueue.remove(_messageQueue.front());
|
||||
}
|
||||
_runQueue();
|
||||
_lockmq.unlock();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onPoll(){
|
||||
_lockmq.lock();
|
||||
if(!_messageQueue.isEmpty()){
|
||||
_messageQueue.emplace_back(message, len);
|
||||
// runqueue trigger when new messages added
|
||||
if (_client->canSend()) {
|
||||
_runQueue();
|
||||
}
|
||||
_lockmq.unlock();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
|
||||
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) {
|
||||
#ifdef ESP32
|
||||
// Same here, acquiring the lock early
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
#endif
|
||||
while (len && _messageQueue.size()) {
|
||||
len = _messageQueue.front().ack(len, time);
|
||||
if (_messageQueue.front().finished())
|
||||
_messageQueue.pop_front();
|
||||
}
|
||||
_runQueue();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onPoll() {
|
||||
#ifdef ESP32
|
||||
// Same here, acquiring the lock early
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
#endif
|
||||
if (_messageQueue.size()) {
|
||||
_runQueue();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
|
||||
_client->close(true);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onDisconnect(){
|
||||
void AsyncEventSourceClient::_onDisconnect() {
|
||||
_client = NULL;
|
||||
_server->_handleDisconnect(this);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::close(){
|
||||
if(_client != NULL)
|
||||
void AsyncEventSourceClient::close() {
|
||||
if (_client != NULL)
|
||||
_client->close();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::write(const char * message, size_t len){
|
||||
_queueMessage(new AsyncEventSourceMessage(message, len));
|
||||
void AsyncEventSourceClient::write(const char* message, size_t len) {
|
||||
if (!connected())
|
||||
return;
|
||||
_queueMessage(message, len);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
void AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||
if (!connected())
|
||||
return;
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
|
||||
_queueMessage(ev.c_str(), ev.length());
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceClient::packetsWaiting() const {
|
||||
size_t len;
|
||||
_lockmq.lock();
|
||||
len = _messageQueue.length();
|
||||
_lockmq.unlock();
|
||||
return len;
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
#endif
|
||||
return _messageQueue.size();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_runQueue() {
|
||||
// Calls to this private method now already protected by _lockmq acquisition
|
||||
// so no extra call of _lockmq.lock() here..
|
||||
for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) {
|
||||
// If it crashes here, iterator (i) has been invalidated as _messageQueue
|
||||
// has been changed... (UL 2020-11-15: Not supposed to happen any more ;-) )
|
||||
if (!(*i)->sent()) {
|
||||
(*i)->send(_client);
|
||||
}
|
||||
for (auto& i : _messageQueue) {
|
||||
if (!i.sent())
|
||||
i.send(_client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handler
|
||||
|
||||
AsyncEventSource::AsyncEventSource(const String& url)
|
||||
: _url(url)
|
||||
, _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
|
||||
, _connectcb(NULL)
|
||||
{}
|
||||
|
||||
AsyncEventSource::~AsyncEventSource(){
|
||||
close();
|
||||
}
|
||||
|
||||
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
|
||||
void AsyncEventSource::onConnect(ArEventHandlerFunction cb) {
|
||||
_connectcb = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){
|
||||
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
|
||||
_authorizeConnectHandler = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
|
||||
/*char * temp = (char *)malloc(2054);
|
||||
if(temp != NULL){
|
||||
memset(temp+1,' ',2048);
|
||||
temp[0] = ':';
|
||||
temp[2049] = '\r';
|
||||
temp[2050] = '\n';
|
||||
temp[2051] = '\r';
|
||||
temp[2052] = '\n';
|
||||
temp[2053] = 0;
|
||||
client->write((const char *)temp, 2053);
|
||||
free(temp);
|
||||
}*/
|
||||
AsyncWebLockGuard l(_client_queue_lock);
|
||||
_clients.add(client);
|
||||
if(_connectcb)
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
|
||||
if (!client)
|
||||
return;
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
_clients.emplace_back(client);
|
||||
if (_connectcb)
|
||||
_connectcb(client);
|
||||
}
|
||||
|
||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
|
||||
AsyncWebLockGuard l(_client_queue_lock);
|
||||
_clients.remove(client);
|
||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
|
||||
if (i->get() == client)
|
||||
_clients.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncEventSource::close(){
|
||||
void AsyncEventSource::close() {
|
||||
// While the whole loop is not done, the linked list is locked and so the
|
||||
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
|
||||
// is called very early
|
||||
AsyncWebLockGuard l(_client_queue_lock);
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected())
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (const auto& c : _clients) {
|
||||
if (c->connected())
|
||||
c->close();
|
||||
}
|
||||
}
|
||||
@ -332,41 +316,48 @@ void AsyncEventSource::close(){
|
||||
size_t AsyncEventSource::avgPacketsWaiting() const {
|
||||
size_t aql = 0;
|
||||
uint32_t nConnectedClients = 0;
|
||||
AsyncWebLockGuard l(_client_queue_lock);
|
||||
if (_clients.isEmpty()) {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
if (!_clients.size())
|
||||
return 0;
|
||||
}
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected()) {
|
||||
|
||||
for (const auto& c : _clients) {
|
||||
if (c->connected()) {
|
||||
aql += c->packetsWaiting();
|
||||
++nConnectedClients;
|
||||
}
|
||||
}
|
||||
return ((aql) + (nConnectedClients/2)) / (nConnectedClients); // round up
|
||||
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
|
||||
}
|
||||
|
||||
void AsyncEventSource::send(
|
||||
const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
AsyncWebLockGuard l(_client_queue_lock);
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected()) {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (const auto& c : _clients) {
|
||||
if (c->connected()) {
|
||||
c->write(ev.c_str(), ev.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t AsyncEventSource::count() const {
|
||||
size_t n_clients;
|
||||
AsyncWebLockGuard l(_client_queue_lock);
|
||||
n_clients = _clients.count_if([](AsyncEventSourceClient *c){
|
||||
return c->connected();
|
||||
});
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
size_t n_clients{0};
|
||||
for (const auto& i : _clients)
|
||||
if (i->connected())
|
||||
++n_clients;
|
||||
|
||||
return n_clients;
|
||||
}
|
||||
|
||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
||||
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
|
||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) {
|
||||
if (request->method() != HTTP_GET || !request->url().equals(_url)) {
|
||||
return false;
|
||||
}
|
||||
request->addInterestingHeader(F("Last-Event-ID"));
|
||||
@ -374,12 +365,12 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
||||
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
|
||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
|
||||
if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
if(_authorizeConnectHandler != NULL){
|
||||
if(!_authorizeConnectHandler(request)){
|
||||
if (_authorizeConnectHandler != NULL) {
|
||||
if (!_authorizeConnectHandler(request)) {
|
||||
return request->send(401);
|
||||
}
|
||||
}
|
||||
@ -388,7 +379,7 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
||||
|
||||
// Response
|
||||
|
||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
|
||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
|
||||
_server = server;
|
||||
_code = 200;
|
||||
_contentType = F("text/event-stream");
|
||||
@ -397,16 +388,15 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
|
||||
addHeader(F("Connection"), F("keep-alive"));
|
||||
}
|
||||
|
||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
|
||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) {
|
||||
String out = _assembleHead(request->version());
|
||||
request->client()->write(out.c_str(), _headLength);
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
|
||||
if(len){
|
||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) {
|
||||
if (len) {
|
||||
new AsyncEventSourceClient(request, _server);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -21,129 +21,138 @@
|
||||
#define ASYNCEVENTSOURCE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <list>
|
||||
#ifdef ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#else
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#include <AsyncTCP.h>
|
||||
#include <mutex>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#elif defined(ESP8266)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#elif defined(TARGET_RP2040)
|
||||
#include <AsyncTCP_RP2040W.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "AsyncWebSynchronization.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_MAX_SSE_CLIENTS
|
||||
#ifdef ESP32
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 4
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 4
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class AsyncEventSource;
|
||||
class AsyncEventSourceResponse;
|
||||
class AsyncEventSourceClient;
|
||||
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
|
||||
typedef std::function<bool(AsyncWebServerRequest *request)> ArAuthorizeConnectHandler;
|
||||
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>;
|
||||
using ArAuthorizeConnectHandler = std::function<bool(AsyncWebServerRequest* request)>;
|
||||
|
||||
class AsyncEventSourceMessage {
|
||||
private:
|
||||
uint8_t * _data;
|
||||
uint8_t* _data;
|
||||
size_t _len;
|
||||
size_t _sent;
|
||||
//size_t _ack;
|
||||
// size_t _ack;
|
||||
size_t _acked;
|
||||
|
||||
public:
|
||||
AsyncEventSourceMessage(const char * data, size_t len);
|
||||
AsyncEventSourceMessage(const char* data, size_t len);
|
||||
~AsyncEventSourceMessage();
|
||||
size_t ack(size_t len, uint32_t time __attribute__((unused)));
|
||||
size_t send(AsyncClient *client);
|
||||
bool finished(){ return _acked == _len; }
|
||||
size_t send(AsyncClient* client);
|
||||
bool finished() { return _acked == _len; }
|
||||
bool sent() { return _sent == _len; }
|
||||
};
|
||||
|
||||
class AsyncEventSourceClient {
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
AsyncEventSource *_server;
|
||||
AsyncClient* _client;
|
||||
AsyncEventSource* _server;
|
||||
uint32_t _lastId;
|
||||
LinkedList<AsyncEventSourceMessage *> _messageQueue;
|
||||
// ArFi 2020-08-27 for protecting/serializing _messageQueue
|
||||
AsyncPlainLock _lockmq;
|
||||
void _queueMessage(AsyncEventSourceMessage *dataMessage);
|
||||
std::list<AsyncEventSourceMessage> _messageQueue;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lockmq;
|
||||
#endif
|
||||
void _queueMessage(const char* message, size_t len);
|
||||
void _runQueue();
|
||||
|
||||
public:
|
||||
|
||||
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
|
||||
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
|
||||
~AsyncEventSourceClient();
|
||||
|
||||
AsyncClient* client(){ return _client; }
|
||||
AsyncClient* client() { return _client; }
|
||||
void close();
|
||||
void write(const char * message, size_t len);
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
void write(const char* message, size_t len);
|
||||
void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
bool connected() const { return (_client != NULL) && _client->connected(); }
|
||||
uint32_t lastId() const { return _lastId; }
|
||||
size_t packetsWaiting() const;
|
||||
size_t packetsWaiting() const;
|
||||
|
||||
//system callbacks (do not call)
|
||||
// system callbacks (do not call)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
};
|
||||
|
||||
class AsyncEventSource: public AsyncWebHandler {
|
||||
class AsyncEventSource : public AsyncWebHandler {
|
||||
private:
|
||||
String _url;
|
||||
LinkedList<AsyncEventSourceClient *> _clients;
|
||||
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
|
||||
#ifdef ESP32
|
||||
// Same as for individual messages, protect mutations of _clients list
|
||||
// since simultaneous access from different tasks is possible
|
||||
AsyncWebLock _client_queue_lock;
|
||||
ArEventHandlerFunction _connectcb;
|
||||
ArAuthorizeConnectHandler _authorizeConnectHandler;
|
||||
public:
|
||||
AsyncEventSource(const String& url);
|
||||
~AsyncEventSource();
|
||||
mutable std::mutex _client_queue_lock;
|
||||
#endif
|
||||
ArEventHandlerFunction _connectcb{nullptr};
|
||||
ArAuthorizeConnectHandler _authorizeConnectHandler;
|
||||
|
||||
const char * url() const { return _url.c_str(); }
|
||||
public:
|
||||
AsyncEventSource(const String& url) : _url(url){};
|
||||
~AsyncEventSource() { close(); };
|
||||
|
||||
const char* url() const { return _url.c_str(); }
|
||||
void close();
|
||||
void onConnect(ArEventHandlerFunction cb);
|
||||
void authorizeConnect(ArAuthorizeConnectHandler cb);
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
// number of clients connected
|
||||
size_t count() const;
|
||||
size_t avgPacketsWaiting() const;
|
||||
size_t avgPacketsWaiting() const;
|
||||
|
||||
//system callbacks (do not call)
|
||||
void _addClient(AsyncEventSourceClient * client);
|
||||
void _handleDisconnect(AsyncEventSourceClient * client);
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
// system callbacks (do not call)
|
||||
void _addClient(AsyncEventSourceClient* client);
|
||||
void _handleDisconnect(AsyncEventSourceClient* client);
|
||||
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
};
|
||||
|
||||
class AsyncEventSourceResponse: public AsyncWebServerResponse {
|
||||
class AsyncEventSourceResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
AsyncEventSource *_server;
|
||||
AsyncEventSource* _server;
|
||||
|
||||
public:
|
||||
AsyncEventSourceResponse(AsyncEventSource *server);
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
AsyncEventSourceResponse(AsyncEventSource* server);
|
||||
void _respond(AsyncWebServerRequest* request);
|
||||
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return true; }
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCEVENTSOURCE_H_ */
|
||||
|
@ -39,9 +39,9 @@
|
||||
#include <Print.h>
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#endif
|
||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#endif
|
||||
#endif
|
||||
|
||||
constexpr const char* JSON_MIMETYPE = "application/json";
|
||||
@ -120,7 +120,6 @@ class AsyncJsonResponse : public AsyncAbstractResponse {
|
||||
}
|
||||
#endif
|
||||
|
||||
~AsyncJsonResponse() {}
|
||||
JsonVariant& getRoot() { return _root; }
|
||||
bool _sourceValid() const { return _isValid; }
|
||||
size_t setLength() {
|
||||
@ -230,7 +229,7 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final {
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if (_onRequest) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -23,37 +23,42 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#else
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#include <AsyncTCP.h>
|
||||
#include <mutex>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#elif defined(ESP8266)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#elif defined(TARGET_RP2040)
|
||||
#include <AsyncTCP_RP2040W.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "AsyncWebSynchronization.h"
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_MAX_WS_CLIENTS
|
||||
#ifdef ESP32
|
||||
#define DEFAULT_MAX_WS_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_WS_CLIENTS 4
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
#define DEFAULT_MAX_WS_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_WS_CLIENTS 4
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
|
||||
@ -87,20 +92,33 @@ typedef struct {
|
||||
uint64_t index;
|
||||
} AwsFrameInfo;
|
||||
|
||||
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
|
||||
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
|
||||
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
|
||||
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
|
||||
typedef enum { WS_DISCONNECTED,
|
||||
WS_CONNECTED,
|
||||
WS_DISCONNECTING } AwsClientStatus;
|
||||
typedef enum { WS_CONTINUATION,
|
||||
WS_TEXT,
|
||||
WS_BINARY,
|
||||
WS_DISCONNECT = 0x08,
|
||||
WS_PING,
|
||||
WS_PONG } AwsFrameType;
|
||||
typedef enum { WS_MSG_SENDING,
|
||||
WS_MSG_SENT,
|
||||
WS_MSG_ERROR } AwsMessageStatus;
|
||||
typedef enum { WS_EVT_CONNECT,
|
||||
WS_EVT_DISCONNECT,
|
||||
WS_EVT_PONG,
|
||||
WS_EVT_ERROR,
|
||||
WS_EVT_DATA } AwsEventType;
|
||||
|
||||
class AsyncWebSocketMessageBuffer {
|
||||
friend AsyncWebSocket;
|
||||
friend AsyncWebSocketClient;
|
||||
friend AsyncWebSocket;
|
||||
friend AsyncWebSocketClient;
|
||||
|
||||
private:
|
||||
AsyncWebSocketSharedBuffer _buffer;
|
||||
|
||||
public:
|
||||
AsyncWebSocketMessageBuffer(){}
|
||||
AsyncWebSocketMessageBuffer() {}
|
||||
explicit AsyncWebSocketMessageBuffer(size_t size);
|
||||
AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size);
|
||||
//~AsyncWebSocketMessageBuffer();
|
||||
@ -109,9 +127,8 @@ class AsyncWebSocketMessageBuffer {
|
||||
size_t length() const { return _buffer->size(); }
|
||||
};
|
||||
|
||||
class AsyncWebSocketMessage
|
||||
{
|
||||
private:
|
||||
class AsyncWebSocketMessage {
|
||||
private:
|
||||
AsyncWebSocketSharedBuffer _WSbuffer;
|
||||
uint8_t _opcode{WS_TEXT};
|
||||
bool _mask{false};
|
||||
@ -120,25 +137,25 @@ private:
|
||||
size_t _ack{};
|
||||
size_t _acked{};
|
||||
|
||||
public:
|
||||
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode=WS_TEXT, bool mask=false);
|
||||
public:
|
||||
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
|
||||
bool finished() const { return _status != WS_MSG_SENDING; }
|
||||
bool betweenFrames() const { return _acked == _ack; }
|
||||
|
||||
void ack(size_t len, uint32_t time);
|
||||
size_t send(AsyncClient *client);
|
||||
size_t send(AsyncClient* client);
|
||||
};
|
||||
|
||||
class AsyncWebSocketClient {
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
AsyncWebSocket *_server;
|
||||
AsyncClient* _client;
|
||||
AsyncWebSocket* _server;
|
||||
uint32_t _clientId;
|
||||
AwsClientStatus _status;
|
||||
|
||||
AsyncWebLock _lock;
|
||||
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lock;
|
||||
#endif
|
||||
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||
bool closeWhenFull = true;
|
||||
@ -149,223 +166,225 @@ class AsyncWebSocketClient {
|
||||
uint32_t _lastMessageTime;
|
||||
uint32_t _keepAlivePeriod;
|
||||
|
||||
void _queueControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false);
|
||||
void _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode=WS_TEXT, bool mask=false);
|
||||
void _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false);
|
||||
void _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
void _runQueue();
|
||||
void _clearQueue();
|
||||
|
||||
public:
|
||||
void *_tempObject;
|
||||
void* _tempObject;
|
||||
|
||||
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
|
||||
AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server);
|
||||
~AsyncWebSocketClient();
|
||||
|
||||
//client id increments for the given server
|
||||
// client id increments for the given server
|
||||
uint32_t id() const { return _clientId; }
|
||||
AwsClientStatus status() const { return _status; }
|
||||
AsyncClient* client() { return _client; }
|
||||
const AsyncClient* client() const { return _client; }
|
||||
AsyncWebSocket *server(){ return _server; }
|
||||
const AsyncWebSocket *server() const { return _server; }
|
||||
AwsFrameInfo const &pinfo() const { return _pinfo; }
|
||||
AsyncWebSocket* server() { return _server; }
|
||||
const AsyncWebSocket* server() const { return _server; }
|
||||
AwsFrameInfo const& pinfo() const { return _pinfo; }
|
||||
|
||||
// - If "true" (default), the connection will be closed if the message queue is full.
|
||||
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
|
||||
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
|
||||
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
|
||||
// and so on, causing a resource exhaustion.
|
||||
//
|
||||
//
|
||||
// - If "false", the incoming message will be discarded if the queue is full.
|
||||
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
|
||||
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
|
||||
//
|
||||
//
|
||||
// - In any case, when the queue is full, a message is logged.
|
||||
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
|
||||
//
|
||||
//
|
||||
// Usage:
|
||||
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
|
||||
//
|
||||
// Use cases:,
|
||||
//
|
||||
// Use cases:,
|
||||
// - if using websocket to send logging messages, maybe some loss is acceptable.
|
||||
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
|
||||
void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; }
|
||||
bool willCloseClientOnQueueFull() const { return closeWhenFull; }
|
||||
|
||||
IPAddress remoteIP() const;
|
||||
uint16_t remotePort() const;
|
||||
uint16_t remotePort() const;
|
||||
|
||||
bool shouldBeDeleted() const { return !_client; }
|
||||
|
||||
//control frames
|
||||
void close(uint16_t code=0, const char * message=NULL);
|
||||
void ping(const uint8_t *data=NULL, size_t len=0);
|
||||
// control frames
|
||||
void close(uint16_t code = 0, const char* message = NULL);
|
||||
void ping(const uint8_t* data = NULL, size_t len = 0);
|
||||
|
||||
//set auto-ping period in seconds. disabled if zero (default)
|
||||
void keepAlivePeriod(uint16_t seconds){
|
||||
// set auto-ping period in seconds. disabled if zero (default)
|
||||
void keepAlivePeriod(uint16_t seconds) {
|
||||
_keepAlivePeriod = seconds * 1000;
|
||||
}
|
||||
uint16_t keepAlivePeriod(){
|
||||
uint16_t keepAlivePeriod() {
|
||||
return (uint16_t)(_keepAlivePeriod / 1000);
|
||||
}
|
||||
|
||||
//data packets
|
||||
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode=WS_TEXT, bool mask=false) { _queueMessage(buffer, opcode, mask); }
|
||||
// data packets
|
||||
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); }
|
||||
bool queueIsFull() const;
|
||||
size_t queueLen() const;
|
||||
|
||||
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||
#ifndef ESP32
|
||||
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
#endif
|
||||
|
||||
void text(AsyncWebSocketSharedBuffer buffer);
|
||||
void text(const uint8_t *message, size_t len);
|
||||
void text(const char *message, size_t len);
|
||||
void text(const char *message);
|
||||
void text(const String &message);
|
||||
void text(const uint8_t* message, size_t len);
|
||||
void text(const char* message, size_t len);
|
||||
void text(const char* message);
|
||||
void text(const String& message);
|
||||
#ifndef ESP32
|
||||
void text(const __FlashStringHelper *message);
|
||||
#endif // ESP32
|
||||
void text(AsyncWebSocketMessageBuffer *buffer);
|
||||
void text(const __FlashStringHelper* message);
|
||||
#endif // ESP32
|
||||
void text(AsyncWebSocketMessageBuffer* buffer);
|
||||
|
||||
void binary(AsyncWebSocketSharedBuffer buffer);
|
||||
void binary(const uint8_t *message, size_t len);
|
||||
void binary(const char * message, size_t len);
|
||||
void binary(const char * message);
|
||||
void binary(const String &message);
|
||||
void binary(const uint8_t* message, size_t len);
|
||||
void binary(const char* message, size_t len);
|
||||
void binary(const char* message);
|
||||
void binary(const String& message);
|
||||
#ifndef ESP32
|
||||
void binary(const __FlashStringHelper *message, size_t len);
|
||||
#endif // ESP32
|
||||
void binary(AsyncWebSocketMessageBuffer *buffer);
|
||||
void binary(const __FlashStringHelper* message, size_t len);
|
||||
#endif // ESP32
|
||||
void binary(AsyncWebSocketMessageBuffer* buffer);
|
||||
|
||||
bool canSend() const;
|
||||
|
||||
//system callbacks (do not call)
|
||||
// system callbacks (do not call)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onError(int8_t);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
void _onData(void *pbuf, size_t plen);
|
||||
void _onData(void* pbuf, size_t plen);
|
||||
};
|
||||
|
||||
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest *request)>;
|
||||
using AwsEventHandler = std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)>;
|
||||
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest* request)>;
|
||||
using AwsEventHandler = std::function<void(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)>;
|
||||
|
||||
//WebServer Handler implementation that plays the role of a socket server
|
||||
class AsyncWebSocket: public AsyncWebHandler {
|
||||
// WebServer Handler implementation that plays the role of a socket server
|
||||
class AsyncWebSocket : public AsyncWebHandler {
|
||||
private:
|
||||
String _url;
|
||||
std::list<AsyncWebSocketClient> _clients;
|
||||
uint32_t _cNextId;
|
||||
AwsEventHandler _eventHandler{nullptr};
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
bool _enabled;
|
||||
AsyncWebLock _lock;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lock;
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit AsyncWebSocket(const char* url) : _url(url) ,_cNextId(1), _enabled(true) {}
|
||||
AsyncWebSocket(const String& url) :_url(url) ,_cNextId(1),_enabled(true) {}
|
||||
explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
~AsyncWebSocket(){};
|
||||
const char * url() const { return _url.c_str(); }
|
||||
void enable(bool e){ _enabled = e; }
|
||||
const char* url() const { return _url.c_str(); }
|
||||
void enable(bool e) { _enabled = e; }
|
||||
bool enabled() const { return _enabled; }
|
||||
bool availableForWriteAll();
|
||||
bool availableForWrite(uint32_t id);
|
||||
|
||||
size_t count() const;
|
||||
AsyncWebSocketClient * client(uint32_t id);
|
||||
bool hasClient(uint32_t id){ return client(id) != nullptr; }
|
||||
AsyncWebSocketClient* client(uint32_t id);
|
||||
bool hasClient(uint32_t id) { return client(id) != nullptr; }
|
||||
|
||||
void close(uint32_t id, uint16_t code=0, const char * message=NULL);
|
||||
void closeAll(uint16_t code=0, const char * message=NULL);
|
||||
void close(uint32_t id, uint16_t code = 0, const char* message = NULL);
|
||||
void closeAll(uint16_t code = 0, const char* message = NULL);
|
||||
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
||||
|
||||
void ping(uint32_t id, const uint8_t *data=NULL, size_t len=0);
|
||||
void pingAll(const uint8_t *data=NULL, size_t len=0); // done
|
||||
void ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0);
|
||||
void pingAll(const uint8_t* data = NULL, size_t len = 0); // done
|
||||
|
||||
void text(uint32_t id, const uint8_t * message, size_t len);
|
||||
void text(uint32_t id, const char *message, size_t len);
|
||||
void text(uint32_t id, const char *message);
|
||||
void text(uint32_t id, const String &message);
|
||||
void text(uint32_t id, const uint8_t* message, size_t len);
|
||||
void text(uint32_t id, const char* message, size_t len);
|
||||
void text(uint32_t id, const char* message);
|
||||
void text(uint32_t id, const String& message);
|
||||
#ifndef ESP32
|
||||
void text(uint32_t id, const __FlashStringHelper *message);
|
||||
#endif // ESP32
|
||||
void text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
|
||||
void text(uint32_t id, const __FlashStringHelper* message);
|
||||
#endif // ESP32
|
||||
void text(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
|
||||
void text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
void textAll(const uint8_t *message, size_t len);
|
||||
void textAll(const char * message, size_t len);
|
||||
void textAll(const char * message);
|
||||
void textAll(const String &message);
|
||||
void textAll(const uint8_t* message, size_t len);
|
||||
void textAll(const char* message, size_t len);
|
||||
void textAll(const char* message);
|
||||
void textAll(const String& message);
|
||||
#ifndef ESP32
|
||||
void textAll(const __FlashStringHelper *message);
|
||||
#endif // ESP32
|
||||
void textAll(AsyncWebSocketMessageBuffer *buffer);
|
||||
void textAll(const __FlashStringHelper* message);
|
||||
#endif // ESP32
|
||||
void textAll(AsyncWebSocketMessageBuffer* buffer);
|
||||
void textAll(AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
void binary(uint32_t id, const uint8_t *message, size_t len);
|
||||
void binary(uint32_t id, const char *message, size_t len);
|
||||
void binary(uint32_t id, const char *message);
|
||||
void binary(uint32_t id, const String &message);
|
||||
void binary(uint32_t id, const uint8_t* message, size_t len);
|
||||
void binary(uint32_t id, const char* message, size_t len);
|
||||
void binary(uint32_t id, const char* message);
|
||||
void binary(uint32_t id, const String& message);
|
||||
#ifndef ESP32
|
||||
void binary(uint32_t id, const __FlashStringHelper *message, size_t len);
|
||||
#endif // ESP32
|
||||
void binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
|
||||
void binary(uint32_t id, const __FlashStringHelper* message, size_t len);
|
||||
#endif // ESP32
|
||||
void binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
|
||||
void binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
void binaryAll(const uint8_t *message, size_t len);
|
||||
void binaryAll(const char *message, size_t len);
|
||||
void binaryAll(const char *message);
|
||||
void binaryAll(const String &message);
|
||||
void binaryAll(const uint8_t* message, size_t len);
|
||||
void binaryAll(const char* message, size_t len);
|
||||
void binaryAll(const char* message);
|
||||
void binaryAll(const String& message);
|
||||
#ifndef ESP32
|
||||
void binaryAll(const __FlashStringHelper *message, size_t len);
|
||||
#endif // ESP32
|
||||
void binaryAll(AsyncWebSocketMessageBuffer *buffer);
|
||||
void binaryAll(const __FlashStringHelper* message, size_t len);
|
||||
#endif // ESP32
|
||||
void binaryAll(AsyncWebSocketMessageBuffer* buffer);
|
||||
void binaryAll(AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
#ifndef ESP32
|
||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
#endif
|
||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4)));
|
||||
size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
//event listener
|
||||
void onEvent(AwsEventHandler handler){
|
||||
#ifndef ESP32
|
||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
|
||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
#endif
|
||||
|
||||
// event listener
|
||||
void onEvent(AwsEventHandler handler) {
|
||||
_eventHandler = handler;
|
||||
}
|
||||
|
||||
// Handshake Handler
|
||||
void handleHandshake(AwsHandshakeHandler handler){
|
||||
_handshakeHandler = handler;
|
||||
void handleHandshake(AwsHandshakeHandler handler) {
|
||||
_handshakeHandler = handler;
|
||||
}
|
||||
|
||||
//system callbacks (do not call)
|
||||
uint32_t _getNextId(){ return _cNextId++; }
|
||||
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
|
||||
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
|
||||
// system callbacks (do not call)
|
||||
uint32_t _getNextId() { return _cNextId++; }
|
||||
AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request);
|
||||
void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
|
||||
// messagebuffer functions/objects.
|
||||
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
|
||||
AsyncWebSocketMessageBuffer * makeBuffer(const uint8_t * data, size_t size);
|
||||
// messagebuffer functions/objects.
|
||||
AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0);
|
||||
AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size);
|
||||
|
||||
const std::list<AsyncWebSocketClient> &getClients() const { return _clients; }
|
||||
const std::list<AsyncWebSocketClient>& getClients() const { return _clients; }
|
||||
};
|
||||
|
||||
//WebServer response to authenticate the socket and detach the tcp client from the web server request
|
||||
class AsyncWebSocketResponse: public AsyncWebServerResponse {
|
||||
// WebServer response to authenticate the socket and detach the tcp client from the web server request
|
||||
class AsyncWebSocketResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
AsyncWebSocket *_server;
|
||||
AsyncWebSocket* _server;
|
||||
|
||||
public:
|
||||
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server);
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
AsyncWebSocketResponse(const String& key, AsyncWebSocket* server);
|
||||
void _respond(AsyncWebServerRequest* request);
|
||||
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return true; }
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCWEBSOCKET_H_ */
|
||||
|
@ -1,134 +0,0 @@
|
||||
#ifndef ASYNCWEBSYNCHRONIZATION_H_
|
||||
#define ASYNCWEBSYNCHRONIZATION_H_
|
||||
|
||||
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
||||
// Modified 'AsyncWebLock' to just only use mutex since pxCurrentTCB is not
|
||||
// always available. According to example by Arjan Filius, changed name,
|
||||
// added unimplemented version for ESP8266
|
||||
class AsyncPlainLock
|
||||
{
|
||||
private:
|
||||
SemaphoreHandle_t _lock;
|
||||
|
||||
public:
|
||||
AsyncPlainLock() {
|
||||
_lock = xSemaphoreCreateBinary();
|
||||
// In this fails, the system is likely that much out of memory that
|
||||
// we should abort anyways. If assertions are disabled, nothing is lost..
|
||||
assert(_lock);
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
|
||||
~AsyncPlainLock() {
|
||||
vSemaphoreDelete(_lock);
|
||||
}
|
||||
|
||||
bool lock() const {
|
||||
xSemaphoreTake(_lock, portMAX_DELAY);
|
||||
return true;
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
};
|
||||
|
||||
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
||||
class AsyncWebLock
|
||||
{
|
||||
private:
|
||||
SemaphoreHandle_t _lock;
|
||||
mutable TaskHandle_t _lockedBy{};
|
||||
|
||||
public:
|
||||
AsyncWebLock()
|
||||
{
|
||||
_lock = xSemaphoreCreateBinary();
|
||||
// In this fails, the system is likely that much out of memory that
|
||||
// we should abort anyways. If assertions are disabled, nothing is lost..
|
||||
assert(_lock);
|
||||
_lockedBy = NULL;
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
|
||||
~AsyncWebLock() {
|
||||
vSemaphoreDelete(_lock);
|
||||
}
|
||||
|
||||
bool lock() const {
|
||||
const auto currentTask = xTaskGetCurrentTaskHandle();
|
||||
if (_lockedBy != currentTask) {
|
||||
xSemaphoreTake(_lock, portMAX_DELAY);
|
||||
_lockedBy = currentTask;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
_lockedBy = NULL;
|
||||
xSemaphoreGive(_lock);
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// This is the 8266 version of the Sync Lock which is currently unimplemented
|
||||
class AsyncWebLock
|
||||
{
|
||||
|
||||
public:
|
||||
AsyncWebLock() {
|
||||
}
|
||||
|
||||
~AsyncWebLock() {
|
||||
}
|
||||
|
||||
bool lock() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
}
|
||||
};
|
||||
|
||||
// Same for AsyncPlainLock, for ESP8266 this is just the unimplemented version above.
|
||||
using AsyncPlainLock = AsyncWebLock;
|
||||
|
||||
#endif
|
||||
|
||||
class AsyncWebLockGuard
|
||||
{
|
||||
private:
|
||||
const AsyncWebLock *_lock;
|
||||
|
||||
public:
|
||||
AsyncWebLockGuard(const AsyncWebLock &l) {
|
||||
if (l.lock()) {
|
||||
_lock = &l;
|
||||
} else {
|
||||
_lock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
~AsyncWebLockGuard() {
|
||||
if (_lock) {
|
||||
_lock->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
if (_lock) {
|
||||
_lock->unlock();
|
||||
_lock = NULL;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ASYNCWEBSYNCHRONIZATION_H_
|
@ -23,33 +23,36 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "FS.h"
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include "FS.h"
|
||||
|
||||
#include "StringArray.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#elif defined(TARGET_RP2040)
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP_RP2040W.h>
|
||||
#include <http_parser.h>
|
||||
#include <HTTP_Method.h>
|
||||
#else
|
||||
#error Platform not supported
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
#define ASYNCWEBSERVER_VERSION "2.10.8"
|
||||
#define ASYNCWEBSERVER_VERSION_MAJOR 2
|
||||
#define ASYNCWEBSERVER_VERSION_MINOR 10
|
||||
#define ASYNCWEBSERVER_VERSION_REVISION 8
|
||||
#define ASYNCWEBSERVER_VERSION "3.0.6"
|
||||
#define ASYNCWEBSERVER_VERSION_MAJOR 3
|
||||
#define ASYNCWEBSERVER_VERSION_MINOR 0
|
||||
#define ASYNCWEBSERVER_VERSION_REVISION 6
|
||||
#define ASYNCWEBSERVER_FORK_mathieucarbou
|
||||
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
|
||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
|
||||
#else
|
||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
|
||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
|
||||
#endif
|
||||
|
||||
class AsyncWebServer;
|
||||
@ -63,33 +66,37 @@ class AsyncStaticWebHandler;
|
||||
class AsyncCallbackWebHandler;
|
||||
class AsyncResponseStream;
|
||||
|
||||
#ifndef WEBSERVER_H
|
||||
typedef enum {
|
||||
HTTP_GET = 0b00000001,
|
||||
HTTP_POST = 0b00000010,
|
||||
HTTP_DELETE = 0b00000100,
|
||||
HTTP_PUT = 0b00001000,
|
||||
HTTP_PATCH = 0b00010000,
|
||||
HTTP_HEAD = 0b00100000,
|
||||
HTTP_OPTIONS = 0b01000000,
|
||||
HTTP_ANY = 0b01111111,
|
||||
} WebRequestMethod;
|
||||
#if defined (TARGET_RP2040)
|
||||
typedef enum http_method WebRequestMethod;
|
||||
#else
|
||||
#ifndef WEBSERVER_H
|
||||
typedef enum {
|
||||
HTTP_GET = 0b00000001,
|
||||
HTTP_POST = 0b00000010,
|
||||
HTTP_DELETE = 0b00000100,
|
||||
HTTP_PUT = 0b00001000,
|
||||
HTTP_PATCH = 0b00010000,
|
||||
HTTP_HEAD = 0b00100000,
|
||||
HTTP_OPTIONS = 0b01000000,
|
||||
HTTP_ANY = 0b01111111,
|
||||
} WebRequestMethod;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_FS_FILE_OPEN_MODE
|
||||
namespace fs {
|
||||
class FileOpenMode {
|
||||
class FileOpenMode {
|
||||
public:
|
||||
static const char *read;
|
||||
static const char *write;
|
||||
static const char *append;
|
||||
};
|
||||
static const char* read;
|
||||
static const char* write;
|
||||
static const char* append;
|
||||
};
|
||||
};
|
||||
#else
|
||||
#include "FileOpenMode.h"
|
||||
#include "FileOpenMode.h"
|
||||
#endif
|
||||
|
||||
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again
|
||||
// if this value is returned when asked for data, packet will not be sent and you will be asked for data again
|
||||
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
|
||||
|
||||
typedef uint8_t WebRequestMethodComposite;
|
||||
@ -108,8 +115,7 @@ class AsyncWebParameter {
|
||||
bool _isFile;
|
||||
|
||||
public:
|
||||
|
||||
AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){}
|
||||
AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
|
||||
const String& name() const { return _name; }
|
||||
const String& value() const { return _value; }
|
||||
size_t size() const { return _size; }
|
||||
@ -128,18 +134,20 @@ class AsyncWebHeader {
|
||||
|
||||
public:
|
||||
AsyncWebHeader() = default;
|
||||
AsyncWebHeader(const AsyncWebHeader &) = default;
|
||||
AsyncWebHeader(const AsyncWebHeader&) = default;
|
||||
|
||||
AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){}
|
||||
AsyncWebHeader(const String& data): _name(), _value(){
|
||||
if(!data) return;
|
||||
AsyncWebHeader(const String& name, const String& value) : _name(name), _value(value) {}
|
||||
AsyncWebHeader(const String& data) {
|
||||
if (!data)
|
||||
return;
|
||||
int index = data.indexOf(':');
|
||||
if (index < 0) return;
|
||||
if (index < 0)
|
||||
return;
|
||||
_name = data.substring(0, index);
|
||||
_value = data.substring(index + 2);
|
||||
}
|
||||
|
||||
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
|
||||
AsyncWebHeader& operator=(const AsyncWebHeader&) = default;
|
||||
|
||||
const String& name() const { return _name; }
|
||||
const String& value() const { return _value; }
|
||||
@ -150,16 +158,22 @@ class AsyncWebHeader {
|
||||
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
|
||||
* */
|
||||
|
||||
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<String(const String&)> AwsTemplateProcessor;
|
||||
|
||||
class AsyncWebServerRequest {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
friend class AsyncWebServer;
|
||||
friend class AsyncCallbackWebHandler;
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
friend class AsyncWebServer;
|
||||
friend class AsyncCallbackWebHandler;
|
||||
|
||||
private:
|
||||
AsyncClient* _client;
|
||||
AsyncWebServer* _server;
|
||||
@ -188,7 +202,7 @@ class AsyncWebServerRequest {
|
||||
size_t _parsedLength;
|
||||
|
||||
std::list<AsyncWebHeader> _headers;
|
||||
LinkedList<AsyncWebParameter *> _params;
|
||||
std::list<AsyncWebParameter> _params;
|
||||
std::vector<String> _pathParams;
|
||||
|
||||
uint8_t _multiParseState;
|
||||
@ -199,7 +213,7 @@ class AsyncWebServerRequest {
|
||||
String _itemFilename;
|
||||
String _itemType;
|
||||
String _itemValue;
|
||||
uint8_t *_itemBuffer;
|
||||
uint8_t* _itemBuffer;
|
||||
size_t _itemBufferIndex;
|
||||
bool _itemIsFile;
|
||||
|
||||
@ -208,10 +222,9 @@ class AsyncWebServerRequest {
|
||||
void _onError(int8_t error);
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
void _onData(void *buf, size_t len);
|
||||
void _onData(void* buf, size_t len);
|
||||
|
||||
void _addParam(AsyncWebParameter*);
|
||||
void _addPathParam(const char *param);
|
||||
void _addPathParam(const char* param);
|
||||
|
||||
bool _parseReqHead();
|
||||
bool _parseReqHeader();
|
||||
@ -226,12 +239,12 @@ class AsyncWebServerRequest {
|
||||
|
||||
public:
|
||||
File _tempFile;
|
||||
void *_tempObject;
|
||||
void* _tempObject;
|
||||
|
||||
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
|
||||
~AsyncWebServerRequest();
|
||||
|
||||
AsyncClient* client(){ return _client; }
|
||||
AsyncClient* client() { return _client; }
|
||||
uint8_t version() const { return _version; }
|
||||
WebRequestMethodComposite method() const { return _method; }
|
||||
const String& url() const { return _url; }
|
||||
@ -239,77 +252,156 @@ class AsyncWebServerRequest {
|
||||
const String& contentType() const { return _contentType; }
|
||||
size_t contentLength() const { return _contentLength; }
|
||||
bool multipart() const { return _isMultipart; }
|
||||
const __FlashStringHelper *methodToString() const;
|
||||
const __FlashStringHelper *requestedConnTypeToString() const;
|
||||
|
||||
#ifndef ESP8266
|
||||
const char* methodToString() const;
|
||||
const char* requestedConnTypeToString() const;
|
||||
#else
|
||||
const __FlashStringHelper* methodToString() const;
|
||||
const __FlashStringHelper* requestedConnTypeToString() const;
|
||||
#endif
|
||||
|
||||
RequestedConnectionType requestedConnType() const { return _reqconntype; }
|
||||
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
|
||||
void onDisconnect (ArDisconnectHandler fn);
|
||||
void onDisconnect(ArDisconnectHandler fn);
|
||||
|
||||
//hash is the string representation of:
|
||||
// base64(user:pass) for basic or
|
||||
// user:realm:md5(user:realm:pass) for digest
|
||||
bool authenticate(const char * hash);
|
||||
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false);
|
||||
void requestAuthentication(const char * realm = NULL, bool isDigest = true);
|
||||
// hash is the string representation of:
|
||||
// base64(user:pass) for basic or
|
||||
// user:realm:md5(user:realm:pass) for digest
|
||||
bool authenticate(const char* hash);
|
||||
bool authenticate(const char* username, const char* password, const char* realm = NULL, bool passwordIsHash = false);
|
||||
void requestAuthentication(const char* realm = NULL, bool isDigest = true);
|
||||
|
||||
void setHandler(AsyncWebHandler *handler){ _handler = handler; }
|
||||
void addInterestingHeader(const String& name);
|
||||
void setHandler(AsyncWebHandler* handler) { _handler = handler; }
|
||||
|
||||
void redirect(const String& url);
|
||||
/**
|
||||
* @brief add header to collect from a response
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
void addInterestingHeader(const char* name);
|
||||
void addInterestingHeader(const String& name) { return addInterestingHeader(name.c_str()); };
|
||||
|
||||
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, 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);
|
||||
/**
|
||||
* @brief issue 302 redirect response
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
void redirect(const char* url);
|
||||
void redirect(const String& url) { return redirect(url.c_str()); };
|
||||
|
||||
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, 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, AwsTemplateProcessor callback=nullptr);
|
||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
||||
void send(AsyncWebServerResponse* response);
|
||||
void send(int code, const String& contentType = String(), const String& content = String());
|
||||
void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
void send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
|
||||
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);
|
||||
|
||||
size_t headers() const; // get header count
|
||||
bool hasHeader(const String& name) const; // check if header exists
|
||||
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
|
||||
[[deprecated("Replaced by send(...)")]]
|
||||
void send_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
|
||||
send(code, contentType, content, len, callback);
|
||||
}
|
||||
[[deprecated("Replaced by send(...)")]]
|
||||
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
|
||||
send(code, contentType, content, callback);
|
||||
}
|
||||
|
||||
AsyncWebHeader* getHeader(const String& name);
|
||||
const AsyncWebHeader* getHeader(const String& name) const;
|
||||
AsyncWebHeader* getHeader(const __FlashStringHelper * data);
|
||||
const AsyncWebHeader* getHeader(const __FlashStringHelper * data) const;
|
||||
AsyncWebHeader* getHeader(size_t num);
|
||||
AsyncWebServerResponse* beginResponse(int code, const String& contentType = String(), const String& content = String());
|
||||
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncWebServerResponse* beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
|
||||
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);
|
||||
|
||||
|
||||
[[deprecated("Replaced by beginResponse(...)")]]
|
||||
AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
|
||||
return beginResponse(code, contentType, content, len, callback);
|
||||
}
|
||||
[[deprecated("Replaced by beginResponse(...)")]]
|
||||
AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
|
||||
return beginResponse(code, contentType, content, callback);
|
||||
}
|
||||
|
||||
size_t headers() const; // get header count
|
||||
|
||||
// check if header exists
|
||||
bool hasHeader(const char* name) const;
|
||||
bool hasHeader(const String& name) const { return hasHeader(name.c_str()); };
|
||||
#ifdef ESP8266
|
||||
bool hasHeader(const __FlashStringHelper* data) const; // check if header exists
|
||||
#endif
|
||||
|
||||
const AsyncWebHeader* getHeader(const char* name) const;
|
||||
const AsyncWebHeader* getHeader(const String& name) const { return getHeader(name.c_str()); };
|
||||
#ifdef ESP8266
|
||||
const AsyncWebHeader* getHeader(const __FlashStringHelper* data) const;
|
||||
#endif
|
||||
const AsyncWebHeader* getHeader(size_t num) const;
|
||||
|
||||
size_t params() const; // get arguments count
|
||||
bool hasParam(const String& name, bool post=false, bool file=false) const;
|
||||
bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const;
|
||||
size_t params() const; // get arguments count
|
||||
bool hasParam(const String& name, bool post = false, bool file = false) const;
|
||||
bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const;
|
||||
|
||||
AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const;
|
||||
AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const;
|
||||
AsyncWebParameter* getParam(size_t num) const;
|
||||
/**
|
||||
* @brief Get the Request parameter by name
|
||||
*
|
||||
* @param name
|
||||
* @param post
|
||||
* @param file
|
||||
* @return const AsyncWebParameter*
|
||||
*/
|
||||
const AsyncWebParameter* getParam(const char* name, bool post = false, bool file = false) const;
|
||||
|
||||
size_t args() const { return params(); } // get arguments count
|
||||
const String& arg(const String& name) const; // get request argument value by name
|
||||
const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
|
||||
const String& arg(size_t i) const; // get request argument value by number
|
||||
const String& argName(size_t i) const; // get request argument name by number
|
||||
bool hasArg(const char* name) const; // check if argument exists
|
||||
bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists
|
||||
const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); };
|
||||
#ifdef ESP8266
|
||||
const AsyncWebParameter* getParam(const __FlashStringHelper* data, bool post, bool file) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get request parameter by number
|
||||
* i.e., n-th parameter
|
||||
* @param num
|
||||
* @return const AsyncWebParameter*
|
||||
*/
|
||||
const AsyncWebParameter* getParam(size_t num) const;
|
||||
|
||||
size_t args() const { return params(); } // get arguments count
|
||||
|
||||
// get request argument value by name
|
||||
const String& arg(const char* name) const;
|
||||
// get request argument value by name
|
||||
const String& arg(const String& name) const { return arg(name.c_str()); };
|
||||
#ifdef ESP8266
|
||||
const String& arg(const __FlashStringHelper* data) const; // get request argument value by F(name)
|
||||
#endif
|
||||
const String& arg(size_t i) const; // get request argument value by number
|
||||
const String& argName(size_t i) const; // get request argument name by number
|
||||
bool hasArg(const char* name) const; // check if argument exists
|
||||
bool hasArg(const String& name) const { return hasArg(name.c_str()); };
|
||||
#ifdef ESP8266
|
||||
bool hasArg(const __FlashStringHelper* data) const; // check if F(argument) exists
|
||||
#endif
|
||||
|
||||
const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
||||
|
||||
const String& header(const char* name) const;// get request header value by name
|
||||
const String& header(const __FlashStringHelper * data) const;// get request header value by F(name)
|
||||
const String& header(size_t i) const; // get request header value by number
|
||||
const String& headerName(size_t i) const; // get request header name by number
|
||||
// get request header value by name
|
||||
const String& header(const char* name) const;
|
||||
const String& header(const String& name) const { return header(name.c_str()); };
|
||||
|
||||
#ifdef ESP8266
|
||||
const String& header(const __FlashStringHelper* data) const; // get request header value by F(name)
|
||||
#endif
|
||||
|
||||
const String& header(size_t i) const; // get request header value by number
|
||||
const String& headerName(size_t i) const; // get request header name by number
|
||||
|
||||
String urlDecode(const String& text) const;
|
||||
};
|
||||
|
||||
@ -317,11 +409,11 @@ class AsyncWebServerRequest {
|
||||
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
|
||||
* */
|
||||
|
||||
typedef std::function<bool(AsyncWebServerRequest *request)> ArRequestFilterFunction;
|
||||
using ArRequestFilterFunction = std::function<bool(AsyncWebServerRequest* request)>;
|
||||
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest *request);
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest* request);
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest *request);
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest* request);
|
||||
|
||||
/*
|
||||
* REWRITE :: One instance can be handle any Request (done by the Server)
|
||||
@ -332,22 +424,26 @@ class AsyncWebRewrite {
|
||||
String _from;
|
||||
String _toUrl;
|
||||
String _params;
|
||||
ArRequestFilterFunction _filter;
|
||||
ArRequestFilterFunction _filter{nullptr};
|
||||
|
||||
public:
|
||||
AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){
|
||||
AsyncWebRewrite(const char* from, const char* to) : _from(from), _toUrl(to) {
|
||||
int index = _toUrl.indexOf('?');
|
||||
if (index > 0) {
|
||||
_params = _toUrl.substring(index +1);
|
||||
_params = _toUrl.substring(index + 1);
|
||||
_toUrl = _toUrl.substring(0, index);
|
||||
}
|
||||
}
|
||||
virtual ~AsyncWebRewrite(){}
|
||||
AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
|
||||
bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); }
|
||||
virtual ~AsyncWebRewrite() {}
|
||||
AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return *this;
|
||||
}
|
||||
bool filter(AsyncWebServerRequest* request) const { return _filter == NULL || _filter(request); }
|
||||
const String& from(void) const { return _from; }
|
||||
const String& toUrl(void) const { return _toUrl; }
|
||||
const String& params(void) const { return _params; }
|
||||
virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); }
|
||||
virtual bool match(AsyncWebServerRequest* request) { return from() == request->url() && filter(request); }
|
||||
};
|
||||
|
||||
/*
|
||||
@ -356,23 +452,35 @@ class AsyncWebRewrite {
|
||||
|
||||
class AsyncWebHandler {
|
||||
protected:
|
||||
ArRequestFilterFunction _filter;
|
||||
ArRequestFilterFunction _filter{nullptr};
|
||||
String _username;
|
||||
String _password;
|
||||
|
||||
public:
|
||||
AsyncWebHandler():_username(""), _password(""){}
|
||||
AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
|
||||
AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; };
|
||||
AsyncWebHandler& setAuthentication(const String& username, const String& password){ _username = username;_password = password; return *this; };
|
||||
bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); }
|
||||
virtual ~AsyncWebHandler(){}
|
||||
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){
|
||||
AsyncWebHandler() {}
|
||||
AsyncWebHandler& setFilter(ArRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return *this;
|
||||
}
|
||||
AsyncWebHandler& setAuthentication(const char* username, const char* password) {
|
||||
_username = username;
|
||||
_password = password;
|
||||
return *this;
|
||||
};
|
||||
AsyncWebHandler& setAuthentication(const String& username, const String& password) {
|
||||
_username = username;
|
||||
_password = password;
|
||||
return *this;
|
||||
};
|
||||
bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); }
|
||||
virtual ~AsyncWebHandler() {}
|
||||
virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) {
|
||||
return false;
|
||||
}
|
||||
virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){}
|
||||
virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){}
|
||||
virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){}
|
||||
virtual bool isRequestHandlerTrivial(){return true;}
|
||||
virtual void handleRequest(AsyncWebServerRequest* request __attribute__((unused))) {}
|
||||
virtual void handleUpload(AsyncWebServerRequest* request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t* data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))) {}
|
||||
virtual void handleBody(AsyncWebServerRequest* request __attribute__((unused)), uint8_t* data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))) {}
|
||||
virtual bool isRequestHandlerTrivial() { return true; }
|
||||
};
|
||||
|
||||
/*
|
||||
@ -380,7 +488,12 @@ class AsyncWebHandler {
|
||||
* */
|
||||
|
||||
typedef enum {
|
||||
RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED
|
||||
RESPONSE_SETUP,
|
||||
RESPONSE_HEADERS,
|
||||
RESPONSE_CONTENT,
|
||||
RESPONSE_WAIT_ACK,
|
||||
RESPONSE_END,
|
||||
RESPONSE_FAILED
|
||||
} WebResponseState;
|
||||
|
||||
class AsyncWebServerResponse {
|
||||
@ -397,8 +510,9 @@ class AsyncWebServerResponse {
|
||||
size_t _writtenLength;
|
||||
WebResponseState _state;
|
||||
const char* _responseCodeToString(int code);
|
||||
public:
|
||||
static const __FlashStringHelper *responseCodeToString(int code);
|
||||
|
||||
public:
|
||||
static const __FlashStringHelper* responseCodeToString(int code);
|
||||
|
||||
public:
|
||||
AsyncWebServerResponse();
|
||||
@ -412,23 +526,23 @@ public:
|
||||
virtual bool _finished() const;
|
||||
virtual bool _failed() const;
|
||||
virtual bool _sourceValid() const;
|
||||
virtual void _respond(AsyncWebServerRequest *request);
|
||||
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
virtual void _respond(AsyncWebServerRequest* request);
|
||||
virtual size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||
};
|
||||
|
||||
/*
|
||||
* SERVER :: One instance
|
||||
* */
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)> ArUploadHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest* request)> ArRequestHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final)> ArUploadHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
|
||||
|
||||
class AsyncWebServer {
|
||||
protected:
|
||||
AsyncServer _server;
|
||||
LinkedList<AsyncWebRewrite*> _rewrites;
|
||||
LinkedList<AsyncWebHandler*> _handlers;
|
||||
std::list<std::shared_ptr<AsyncWebRewrite>> _rewrites;
|
||||
std::list<std::unique_ptr<AsyncWebHandler>> _handlers;
|
||||
AsyncCallbackWebHandler* _catchAllHandler;
|
||||
|
||||
public:
|
||||
@ -440,13 +554,50 @@ class AsyncWebServer {
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void onSslFileRequest(AcSSlFileHandler cb, void* arg);
|
||||
void beginSecure(const char *cert, const char *private_key_file, const char *password);
|
||||
void beginSecure(const char* cert, const char* private_key_file, const char* password);
|
||||
#endif
|
||||
|
||||
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
|
||||
bool removeRewrite(AsyncWebRewrite* rewrite);
|
||||
|
||||
/**
|
||||
* @brief (compat) Add url rewrite rule by pointer
|
||||
* a deep copy of the pounter object will be created,
|
||||
* it is up to user to manage further lifetime of the object in argument
|
||||
*
|
||||
* @param rewrite pointer to rewrite object to copy setting from
|
||||
* @return AsyncWebRewrite& reference to a newly created rewrite rule
|
||||
*/
|
||||
AsyncWebRewrite& addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite);
|
||||
|
||||
/**
|
||||
* @brief add url rewrite rule
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
* @return AsyncWebRewrite&
|
||||
*/
|
||||
AsyncWebRewrite& rewrite(const char* from, const char* to);
|
||||
|
||||
/**
|
||||
* @brief (compat) remove rewrite rule via referenced object
|
||||
* this will NOT deallocate pointed object itself, internal rule with same from/to urls will be removed if any
|
||||
* it's a compat method, better use `removeRewrite(const char* from, const char* to)`
|
||||
* @param rewrite
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool removeRewrite(AsyncWebRewrite* rewrite);
|
||||
|
||||
/**
|
||||
* @brief remove rewrite rule
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool removeRewrite(const char* from, const char* to);
|
||||
|
||||
AsyncWebHandler& addHandler(AsyncWebHandler* handler);
|
||||
bool removeHandler(AsyncWebHandler* handler);
|
||||
|
||||
@ -457,45 +608,45 @@ class AsyncWebServer {
|
||||
|
||||
AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
|
||||
|
||||
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
|
||||
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
|
||||
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
|
||||
void onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned
|
||||
void onFileUpload(ArUploadHandlerFunction fn); // handle file uploads
|
||||
void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request)
|
||||
|
||||
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
|
||||
void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
|
||||
|
||||
void _handleDisconnect(AsyncWebServerRequest *request);
|
||||
void _attachHandler(AsyncWebServerRequest *request);
|
||||
void _rewriteRequest(AsyncWebServerRequest *request);
|
||||
void _handleDisconnect(AsyncWebServerRequest* request);
|
||||
void _attachHandler(AsyncWebServerRequest* request);
|
||||
void _rewriteRequest(AsyncWebServerRequest* request);
|
||||
};
|
||||
|
||||
class DefaultHeaders {
|
||||
using headers_t = std::list<AsyncWebHeader>;
|
||||
headers_t _headers;
|
||||
using headers_t = std::list<AsyncWebHeader>;
|
||||
headers_t _headers;
|
||||
|
||||
public:
|
||||
DefaultHeaders() = default;
|
||||
public:
|
||||
DefaultHeaders() = default;
|
||||
|
||||
using ConstIterator = headers_t::const_iterator;
|
||||
using ConstIterator = headers_t::const_iterator;
|
||||
|
||||
void addHeader(const String& name, const String& value){
|
||||
_headers.emplace_back(name, value);
|
||||
}
|
||||
void addHeader(const String& name, const String& value) {
|
||||
_headers.emplace_back(name, value);
|
||||
}
|
||||
|
||||
ConstIterator begin() const { return _headers.begin(); }
|
||||
ConstIterator end() const { return _headers.end(); }
|
||||
ConstIterator begin() const { return _headers.begin(); }
|
||||
ConstIterator end() const { return _headers.end(); }
|
||||
|
||||
DefaultHeaders(DefaultHeaders const &) = delete;
|
||||
DefaultHeaders &operator=(DefaultHeaders const &) = delete;
|
||||
DefaultHeaders(DefaultHeaders const&) = delete;
|
||||
DefaultHeaders& operator=(DefaultHeaders const&) = delete;
|
||||
|
||||
static DefaultHeaders &Instance() {
|
||||
static DefaultHeaders instance;
|
||||
return instance;
|
||||
}
|
||||
static DefaultHeaders& Instance() {
|
||||
static DefaultHeaders instance;
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
#include "WebResponseImpl.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
#include "AsyncWebSocket.h"
|
||||
#include "AsyncEventSource.h"
|
||||
#include "AsyncWebSocket.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
#include "WebResponseImpl.h"
|
||||
|
||||
#endif /* _AsyncWebServer_H_ */
|
||||
|
@ -1,174 +0,0 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#ifndef STRINGARRAY_H_
|
||||
#define STRINGARRAY_H_
|
||||
|
||||
#include "stddef.h"
|
||||
#include "WString.h"
|
||||
|
||||
template <typename T>
|
||||
class LinkedListNode {
|
||||
T _value;
|
||||
public:
|
||||
LinkedListNode<T>* next;
|
||||
LinkedListNode(const T val): _value(val), next(nullptr) {}
|
||||
~LinkedListNode(){}
|
||||
const T& value() const { return _value; };
|
||||
T& value(){ return _value; }
|
||||
};
|
||||
|
||||
template <typename T, template<typename> class Item = LinkedListNode>
|
||||
class LinkedList {
|
||||
public:
|
||||
typedef Item<T> ItemType;
|
||||
typedef std::function<void(const T&)> OnRemove;
|
||||
typedef std::function<bool(const T&)> Predicate;
|
||||
private:
|
||||
ItemType* _root;
|
||||
OnRemove _onRemove;
|
||||
|
||||
class Iterator {
|
||||
ItemType* _node;
|
||||
public:
|
||||
Iterator(ItemType* current = nullptr) : _node(current) {}
|
||||
Iterator(const Iterator& i) : _node(i._node) {}
|
||||
Iterator& operator ++() { _node = _node->next; return *this; }
|
||||
bool operator != (const Iterator& i) const { return _node != i._node; }
|
||||
const T& operator * () const { return _node->value(); }
|
||||
const T* operator -> () const { return &_node->value(); }
|
||||
};
|
||||
|
||||
public:
|
||||
typedef const Iterator ConstIterator;
|
||||
ConstIterator begin() const { return ConstIterator(_root); }
|
||||
ConstIterator end() const { return ConstIterator(nullptr); }
|
||||
|
||||
LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {}
|
||||
~LinkedList(){}
|
||||
void add(const T& t){
|
||||
auto it = new ItemType(t);
|
||||
if(!_root){
|
||||
_root = it;
|
||||
} else {
|
||||
auto i = _root;
|
||||
while(i->next) i = i->next;
|
||||
i->next = it;
|
||||
}
|
||||
}
|
||||
T& front() const {
|
||||
return _root->value();
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return _root == nullptr;
|
||||
}
|
||||
size_t length() const {
|
||||
size_t i = 0;
|
||||
auto it = _root;
|
||||
while(it){
|
||||
i++;
|
||||
it = it->next;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
size_t count_if(Predicate predicate) const {
|
||||
size_t i = 0;
|
||||
auto it = _root;
|
||||
while(it){
|
||||
if (!predicate){
|
||||
i++;
|
||||
}
|
||||
else if (predicate(it->value())) {
|
||||
i++;
|
||||
}
|
||||
it = it->next;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
const T* nth(size_t N) const {
|
||||
size_t i = 0;
|
||||
auto it = _root;
|
||||
while(it){
|
||||
if(i++ == N)
|
||||
return &(it->value());
|
||||
it = it->next;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool remove(const T& t){
|
||||
auto it = _root;
|
||||
auto pit = _root;
|
||||
while(it){
|
||||
if(it->value() == t){
|
||||
if(it == _root){
|
||||
_root = _root->next;
|
||||
} else {
|
||||
pit->next = it->next;
|
||||
}
|
||||
|
||||
if (_onRemove) {
|
||||
_onRemove(it->value());
|
||||
}
|
||||
|
||||
delete it;
|
||||
return true;
|
||||
}
|
||||
pit = it;
|
||||
it = it->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool remove_first(Predicate predicate){
|
||||
auto it = _root;
|
||||
auto pit = _root;
|
||||
while(it){
|
||||
if(predicate(it->value())){
|
||||
if(it == _root){
|
||||
_root = _root->next;
|
||||
} else {
|
||||
pit->next = it->next;
|
||||
}
|
||||
if (_onRemove) {
|
||||
_onRemove(it->value());
|
||||
}
|
||||
delete it;
|
||||
return true;
|
||||
}
|
||||
pit = it;
|
||||
it = it->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void free(){
|
||||
while(_root != nullptr){
|
||||
auto it = _root;
|
||||
_root = _root->next;
|
||||
if (_onRemove) {
|
||||
_onRemove(it->value());
|
||||
}
|
||||
delete it;
|
||||
}
|
||||
_root = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* STRINGARRAY_H_ */
|
@ -20,41 +20,40 @@
|
||||
*/
|
||||
#include "WebAuthentication.h"
|
||||
#include <libb64/cencode.h>
|
||||
#ifdef ESP32
|
||||
#include <MD5Builder.h>
|
||||
#if defined(ESP32) || defined(TARGET_RP2040)
|
||||
#include <MD5Builder.h>
|
||||
#else
|
||||
#include "md5.h"
|
||||
#include "md5.h"
|
||||
#endif
|
||||
|
||||
|
||||
// Basic Auth hash = base64("username:password")
|
||||
|
||||
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){
|
||||
if(username == NULL || password == NULL || hash == NULL)
|
||||
bool checkBasicAuthentication(const char* hash, const char* username, const char* password) {
|
||||
if (username == NULL || password == NULL || hash == NULL)
|
||||
return false;
|
||||
|
||||
size_t toencodeLen = strlen(username)+strlen(password)+1;
|
||||
size_t toencodeLen = strlen(username) + strlen(password) + 1;
|
||||
size_t encodedLen = base64_encode_expected_len(toencodeLen);
|
||||
if(strlen(hash) != encodedLen)
|
||||
if (strlen(hash) != encodedLen)
|
||||
// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if(strlen(hash) != encodedLen)
|
||||
if (strlen(hash) != encodedLen)
|
||||
#else
|
||||
if (strlen(hash) != encodedLen - 1)
|
||||
#endif
|
||||
return false;
|
||||
return false;
|
||||
|
||||
char *toencode = new char[toencodeLen+1];
|
||||
if(toencode == NULL){
|
||||
char* toencode = new char[toencodeLen + 1];
|
||||
if (toencode == NULL) {
|
||||
return false;
|
||||
}
|
||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
||||
if(encoded == NULL){
|
||||
char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||
if (encoded == NULL) {
|
||||
delete[] toencode;
|
||||
return false;
|
||||
}
|
||||
sprintf_P(toencode, PSTR("%s:%s"), username, password);
|
||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){
|
||||
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0) {
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
return true;
|
||||
@ -64,8 +63,8 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
|
||||
#ifdef ESP32
|
||||
static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more
|
||||
#if defined(ESP32) || defined(TARGET_RP2040)
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.add(data, len);
|
||||
@ -73,9 +72,9 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
|
||||
md5.getChars(output);
|
||||
#else
|
||||
md5_context_t _ctx;
|
||||
|
||||
uint8_t * _buf = (uint8_t*)malloc(16);
|
||||
if(_buf == NULL)
|
||||
|
||||
uint8_t* _buf = (uint8_t*)malloc(16);
|
||||
if (_buf == NULL)
|
||||
return false;
|
||||
memset(_buf, 0x00, 16);
|
||||
|
||||
@ -83,7 +82,7 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
|
||||
MD5Update(&_ctx, data, len);
|
||||
MD5Final(_buf, &_ctx);
|
||||
|
||||
for(uint8_t i = 0; i < 16; i++) {
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
|
||||
}
|
||||
|
||||
@ -92,50 +91,50 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
|
||||
return true;
|
||||
}
|
||||
|
||||
static String genRandomMD5(){
|
||||
static String genRandomMD5() {
|
||||
#ifdef ESP8266
|
||||
uint32_t r = RANDOM_REG32;
|
||||
#else
|
||||
uint32_t r = rand();
|
||||
#endif
|
||||
char * out = (char*)malloc(33);
|
||||
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out))
|
||||
char* out = (char*)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t*)(&r), 4, out))
|
||||
return emptyString;
|
||||
String res = String(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
static String stringMD5(const String& in){
|
||||
char * out = (char*)malloc(33);
|
||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||
static String stringMD5(const String& in) {
|
||||
char* out = (char*)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||
return emptyString;
|
||||
String res = String(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
String generateDigestHash(const char * username, const char * password, const char * realm){
|
||||
if(username == NULL || password == NULL || realm == NULL){
|
||||
String generateDigestHash(const char* username, const char* password, const char* realm) {
|
||||
if (username == NULL || password == NULL || realm == NULL) {
|
||||
return emptyString;
|
||||
}
|
||||
char * out = (char*)malloc(33);
|
||||
char* out = (char*)malloc(33);
|
||||
String res = String(username);
|
||||
res += ':';
|
||||
res.concat(realm);
|
||||
res += ':';
|
||||
String in = res;
|
||||
in.concat(password);
|
||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||
return emptyString;
|
||||
res.concat(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
String requestDigestAuthentication(const char * realm){
|
||||
String requestDigestAuthentication(const char* realm) {
|
||||
String header = F("realm=\"");
|
||||
if(realm == NULL)
|
||||
if (realm == NULL)
|
||||
header.concat(F("asyncesp"));
|
||||
else
|
||||
header.concat(realm);
|
||||
@ -147,95 +146,100 @@ String requestDigestAuthentication(const char * realm){
|
||||
return header;
|
||||
}
|
||||
|
||||
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){
|
||||
if(username == NULL || password == NULL || header == NULL || method == NULL){
|
||||
//os_printf("AUTH FAIL: missing requred fields\n");
|
||||
#ifndef ESP8266
|
||||
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
|
||||
#else
|
||||
bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
|
||||
#endif
|
||||
{
|
||||
if (username == NULL || password == NULL || header == NULL || method == NULL) {
|
||||
// os_printf("AUTH FAIL: missing requred fields\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
String myHeader = String(header);
|
||||
String myHeader(header);
|
||||
int nextBreak = myHeader.indexOf(',');
|
||||
if(nextBreak < 0){
|
||||
//os_printf("AUTH FAIL: no variables\n");
|
||||
if (nextBreak < 0) {
|
||||
// os_printf("AUTH FAIL: no variables\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
String myUsername = String();
|
||||
String myRealm = String();
|
||||
String myNonce = String();
|
||||
String myUri = String();
|
||||
String myResponse = String();
|
||||
String myQop = String();
|
||||
String myNc = String();
|
||||
String myCnonce = String();
|
||||
String myUsername;
|
||||
String myRealm;
|
||||
String myNonce;
|
||||
String myUri;
|
||||
String myResponse;
|
||||
String myQop;
|
||||
String myNc;
|
||||
String myCnonce;
|
||||
|
||||
myHeader += F(", ");
|
||||
do {
|
||||
String avLine = myHeader.substring(0, nextBreak);
|
||||
String avLine(myHeader.substring(0, nextBreak));
|
||||
avLine.trim();
|
||||
myHeader = myHeader.substring(nextBreak+1);
|
||||
myHeader = myHeader.substring(nextBreak + 1);
|
||||
nextBreak = myHeader.indexOf(',');
|
||||
|
||||
int eqSign = avLine.indexOf('=');
|
||||
if(eqSign < 0){
|
||||
//os_printf("AUTH FAIL: no = sign\n");
|
||||
if (eqSign < 0) {
|
||||
// os_printf("AUTH FAIL: no = sign\n");
|
||||
return false;
|
||||
}
|
||||
String varName = avLine.substring(0, eqSign);
|
||||
String varName(avLine.substring(0, eqSign));
|
||||
avLine = avLine.substring(eqSign + 1);
|
||||
if(avLine.startsWith(String('"'))){
|
||||
if (avLine.startsWith(String('"'))) {
|
||||
avLine = avLine.substring(1, avLine.length() - 1);
|
||||
}
|
||||
|
||||
if(varName.equals(F("username"))){
|
||||
if(!avLine.equals(username)){
|
||||
//os_printf("AUTH FAIL: username\n");
|
||||
if (varName.equals(F("username"))) {
|
||||
if (!avLine.equals(username)) {
|
||||
// os_printf("AUTH FAIL: username\n");
|
||||
return false;
|
||||
}
|
||||
myUsername = avLine;
|
||||
} else if(varName.equals(F("realm"))){
|
||||
if(realm != NULL && !avLine.equals(realm)){
|
||||
//os_printf("AUTH FAIL: realm\n");
|
||||
} else if (varName.equals(F("realm"))) {
|
||||
if (realm != NULL && !avLine.equals(realm)) {
|
||||
// os_printf("AUTH FAIL: realm\n");
|
||||
return false;
|
||||
}
|
||||
myRealm = avLine;
|
||||
} else if(varName.equals(F("nonce"))){
|
||||
if(nonce != NULL && !avLine.equals(nonce)){
|
||||
//os_printf("AUTH FAIL: nonce\n");
|
||||
} else if (varName.equals(F("nonce"))) {
|
||||
if (nonce != NULL && !avLine.equals(nonce)) {
|
||||
// os_printf("AUTH FAIL: nonce\n");
|
||||
return false;
|
||||
}
|
||||
myNonce = avLine;
|
||||
} else if(varName.equals(F("opaque"))){
|
||||
if(opaque != NULL && !avLine.equals(opaque)){
|
||||
//os_printf("AUTH FAIL: opaque\n");
|
||||
} else if (varName.equals(F("opaque"))) {
|
||||
if (opaque != NULL && !avLine.equals(opaque)) {
|
||||
// os_printf("AUTH FAIL: opaque\n");
|
||||
return false;
|
||||
}
|
||||
} else if(varName.equals(F("uri"))){
|
||||
if(uri != NULL && !avLine.equals(uri)){
|
||||
//os_printf("AUTH FAIL: uri\n");
|
||||
} else if (varName.equals(F("uri"))) {
|
||||
if (uri != NULL && !avLine.equals(uri)) {
|
||||
// os_printf("AUTH FAIL: uri\n");
|
||||
return false;
|
||||
}
|
||||
myUri = avLine;
|
||||
} else if(varName.equals(F("response"))){
|
||||
} else if (varName.equals(F("response"))) {
|
||||
myResponse = avLine;
|
||||
} else if(varName.equals(F("qop"))){
|
||||
} else if (varName.equals(F("qop"))) {
|
||||
myQop = avLine;
|
||||
} else if(varName.equals(F("nc"))){
|
||||
} else if (varName.equals(F("nc"))) {
|
||||
myNc = avLine;
|
||||
} else if(varName.equals(F("cnonce"))){
|
||||
} else if (varName.equals(F("cnonce"))) {
|
||||
myCnonce = avLine;
|
||||
}
|
||||
} while(nextBreak > 0);
|
||||
} while (nextBreak > 0);
|
||||
|
||||
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password));
|
||||
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + password);
|
||||
String ha2 = String(method) + ':' + myUri;
|
||||
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
|
||||
|
||||
if(myResponse.equals(stringMD5(response))){
|
||||
//os_printf("AUTH SUCCESS\n");
|
||||
if (myResponse.equals(stringMD5(response))) {
|
||||
// os_printf("AUTH SUCCESS\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
//os_printf("AUTH FAIL: password\n");
|
||||
// os_printf("AUTH FAIL: password\n");
|
||||
return false;
|
||||
}
|
||||
|
@ -24,11 +24,16 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
bool checkBasicAuthentication(const char * header, const char * username, const char * password);
|
||||
String requestDigestAuthentication(const char * realm);
|
||||
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri);
|
||||
bool checkBasicAuthentication(const char* header, const char* username, const char* password);
|
||||
String requestDigestAuthentication(const char* realm);
|
||||
|
||||
//for storing hashed versions on the device that can be authenticated against
|
||||
String generateDigestHash(const char * username, const char * password, const char * realm);
|
||||
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
|
||||
|
||||
#ifdef ESP8266
|
||||
bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
|
||||
#endif
|
||||
|
||||
// for storing hashed versions on the device that can be authenticated against
|
||||
String generateDigestHash(const char* username, const char* password, const char* realm);
|
||||
|
||||
#endif
|
||||
|
@ -23,19 +23,21 @@
|
||||
|
||||
#include <string>
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
#include <regex>
|
||||
#include <regex>
|
||||
#endif
|
||||
|
||||
#include "stddef.h"
|
||||
#include <time.h>
|
||||
|
||||
class AsyncStaticWebHandler: public AsyncWebHandler {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
class AsyncStaticWebHandler : public AsyncWebHandler {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
|
||||
private:
|
||||
bool _getFile(AsyncWebServerRequest *request);
|
||||
bool _fileExists(AsyncWebServerRequest *request, const String& path);
|
||||
bool _getFile(AsyncWebServerRequest* request);
|
||||
bool _fileExists(AsyncWebServerRequest* request, const String& path);
|
||||
uint8_t _countBits(const uint8_t value) const;
|
||||
|
||||
protected:
|
||||
FS _fs;
|
||||
String _uri;
|
||||
@ -47,23 +49,27 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
|
||||
bool _isDir;
|
||||
bool _gzipFirst;
|
||||
uint8_t _gzipStats;
|
||||
|
||||
public:
|
||||
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
AsyncStaticWebHandler& setIsDir(bool isDir);
|
||||
AsyncStaticWebHandler& setDefaultFile(const char* filename);
|
||||
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
|
||||
AsyncStaticWebHandler& setLastModified(const char* last_modified);
|
||||
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
|
||||
#ifdef ESP8266
|
||||
#ifdef ESP8266
|
||||
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;}
|
||||
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 {
|
||||
class AsyncCallbackWebHandler : public AsyncWebHandler {
|
||||
private:
|
||||
protected:
|
||||
String _uri;
|
||||
@ -72,23 +78,24 @@ class AsyncCallbackWebHandler: public AsyncWebHandler {
|
||||
ArUploadHandlerFunction _onUpload;
|
||||
ArBodyHandlerFunction _onBody;
|
||||
bool _isRegex;
|
||||
|
||||
public:
|
||||
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
||||
void setUri(const String& uri){
|
||||
_uri = uri;
|
||||
void setUri(const String& uri) {
|
||||
_uri = uri;
|
||||
_isRegex = uri.startsWith("^") && uri.endsWith("$");
|
||||
}
|
||||
void setMethod(WebRequestMethodComposite method){ _method = method; }
|
||||
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; }
|
||||
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; }
|
||||
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; }
|
||||
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||
void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
|
||||
void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
|
||||
void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
|
||||
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{
|
||||
virtual bool canHandle(AsyncWebServerRequest* request) override final {
|
||||
|
||||
if(!_onRequest)
|
||||
if (!_onRequest)
|
||||
return false;
|
||||
|
||||
if(!(_method & request->method()))
|
||||
if (!(_method & request->method()))
|
||||
return false;
|
||||
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
@ -96,56 +103,53 @@ class AsyncCallbackWebHandler: public AsyncWebHandler {
|
||||
std::regex pattern(_uri.c_str());
|
||||
std::smatch matches;
|
||||
std::string s(request->url().c_str());
|
||||
if(std::regex_search(s, matches, pattern)) {
|
||||
if (std::regex_search(s, matches, pattern)) {
|
||||
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
|
||||
request->_addPathParam(matches[i].str().c_str());
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
} else
|
||||
#endif
|
||||
if (_uri.length() && _uri.startsWith("/*.")) {
|
||||
String uriTemplate = String (_uri);
|
||||
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
|
||||
if (!request->url().endsWith(uriTemplate))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
if (_uri.length() && _uri.endsWith("*")) {
|
||||
if (_uri.length() && _uri.startsWith("/*.")) {
|
||||
String uriTemplate = String(_uri);
|
||||
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
|
||||
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
|
||||
if (!request->url().endsWith(uriTemplate))
|
||||
return false;
|
||||
} else if (_uri.length() && _uri.endsWith("*")) {
|
||||
String uriTemplate = String(_uri);
|
||||
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
|
||||
if (!request->url().startsWith(uriTemplate))
|
||||
return false;
|
||||
}
|
||||
else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
|
||||
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||
return false;
|
||||
|
||||
request->addInterestingHeader("ANY");
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final {
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if(_onRequest)
|
||||
if (_onRequest)
|
||||
_onRequest(request);
|
||||
else
|
||||
request->send(500);
|
||||
}
|
||||
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
virtual void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if(_onUpload)
|
||||
if (_onUpload)
|
||||
_onUpload(request, filename, index, data, len, final);
|
||||
}
|
||||
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if(_onBody)
|
||||
if (_onBody)
|
||||
_onBody(request, data, len, index, total);
|
||||
}
|
||||
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
|
||||
virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; }
|
||||
};
|
||||
|
||||
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
|
||||
|
@ -22,73 +22,73 @@
|
||||
#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(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr)
|
||||
{
|
||||
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
|
||||
// Ensure leading '/'
|
||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri;
|
||||
if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path;
|
||||
if (_uri.length() == 0 || _uri[0] != '/')
|
||||
_uri = String('/') + _uri;
|
||||
if (_path.length() == 0 || _path[0] != '/')
|
||||
_path = String('/') + _path;
|
||||
|
||||
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
||||
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
||||
_isDir = _path[_path.length()-1] == '/';
|
||||
_isDir = _path[_path.length() - 1] == '/';
|
||||
|
||||
// Remove the trailing '/' so we can handle default file
|
||||
// Notice that root will be "" not "/"
|
||||
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
|
||||
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
|
||||
if (_uri[_uri.length() - 1] == '/')
|
||||
_uri = _uri.substring(0, _uri.length() - 1);
|
||||
if (_path[_path.length() - 1] == '/')
|
||||
_path = _path.substring(0, _path.length() - 1);
|
||||
|
||||
// Reset stats
|
||||
_gzipFirst = false;
|
||||
_gzipStats = 0xF8;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) {
|
||||
_isDir = isDir;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) {
|
||||
_default_file = String(filename);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) {
|
||||
_cache_control = String(cache_control);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) {
|
||||
_last_modified = last_modified;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) {
|
||||
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
|
||||
char format[strlen_P(formatP) + 1];
|
||||
strcpy_P(format, formatP);
|
||||
|
||||
char result[30];
|
||||
strftime(result, sizeof(result), format, last_modified);
|
||||
return setLastModified((const char *)result);
|
||||
return setLastModified((const char*)result);
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){
|
||||
return setLastModified((struct tm *)gmtime(&last_modified));
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) {
|
||||
return setLastModified((struct tm*)gmtime(&last_modified));
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() {
|
||||
time_t last_modified;
|
||||
if(time(&last_modified) == 0) //time is not yet set
|
||||
if (time(&last_modified) == 0) // time is not yet set
|
||||
return *this;
|
||||
return setLastModified(last_modified);
|
||||
}
|
||||
#endif
|
||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
|
||||
if(request->method() != HTTP_GET
|
||||
|| !request->url().startsWith(_uri)
|
||||
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)
|
||||
){
|
||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) {
|
||||
if (request->method() != HTTP_GET || !request->url().startsWith(_uri) || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)) {
|
||||
return false;
|
||||
}
|
||||
if (_getFile(request)) {
|
||||
@ -96,7 +96,7 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
|
||||
if (_last_modified.length())
|
||||
request->addInterestingHeader(F("If-Modified-Since"));
|
||||
|
||||
if(_cache_control.length())
|
||||
if (_cache_control.length())
|
||||
request->addInterestingHeader(F("If-None-Match"));
|
||||
|
||||
return true;
|
||||
@ -105,13 +105,12 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
||||
{
|
||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) {
|
||||
// Remove the found uri
|
||||
String path = request->url().substring(_uri.length());
|
||||
|
||||
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
||||
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
|
||||
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/');
|
||||
|
||||
path = _path + path;
|
||||
|
||||
@ -124,7 +123,7 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
||||
return false;
|
||||
|
||||
// Try to add default file, ensure there is a trailing '/' ot the path.
|
||||
if (path.length() == 0 || path[path.length()-1] != '/')
|
||||
if (path.length() == 0 || path[path.length() - 1] != '/')
|
||||
path += String('/');
|
||||
path += _default_file;
|
||||
|
||||
@ -132,39 +131,38 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||
#else
|
||||
#define FILE_IS_REAL(f) (f == true)
|
||||
#define FILE_IS_REAL(f) (f == true)
|
||||
#endif
|
||||
|
||||
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path)
|
||||
{
|
||||
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest* request, const String& path) {
|
||||
bool fileFound = false;
|
||||
bool gzipFound = false;
|
||||
|
||||
String gzip = path + F(".gz");
|
||||
|
||||
if (_gzipFirst) {
|
||||
if (_fs.exists(gzip)) {
|
||||
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
if (!gzipFound){
|
||||
if (_fs.exists(path)) {
|
||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_fs.exists(path)) {
|
||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
if (!fileFound){
|
||||
if (_fs.exists(gzip)) {
|
||||
if (_fs.exists(gzip)) {
|
||||
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
}
|
||||
if (!gzipFound) {
|
||||
if (_fs.exists(path)) {
|
||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_fs.exists(path)) {
|
||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
if (!fileFound) {
|
||||
if (_fs.exists(gzip)) {
|
||||
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,55 +171,71 @@ bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const St
|
||||
if (found) {
|
||||
// Extract the file name from the path and keep it in _tempObject
|
||||
size_t pathLen = path.length();
|
||||
char * _tempPath = (char*)malloc(pathLen+1);
|
||||
snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str());
|
||||
char* _tempPath = (char*)malloc(pathLen + 1);
|
||||
snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
|
||||
request->_tempObject = (void*)_tempPath;
|
||||
|
||||
// Calculate gzip statistic
|
||||
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
||||
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
||||
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
||||
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
||||
if (_gzipStats == 0x00)
|
||||
_gzipFirst = false; // All files are not gzip
|
||||
else if (_gzipStats == 0xFF)
|
||||
_gzipFirst = true; // All files are gzip
|
||||
else
|
||||
_gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const
|
||||
{
|
||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
|
||||
uint8_t w = value;
|
||||
uint8_t n;
|
||||
for (n=0; w!=0; n++) w&=w-1;
|
||||
for (n = 0; w != 0; n++)
|
||||
w &= w - 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
||||
{
|
||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||
// Get the filename from request->_tempObject and free it
|
||||
String filename = String((char*)request->_tempObject);
|
||||
free(request->_tempObject);
|
||||
request->_tempObject = NULL;
|
||||
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
|
||||
if (request->_tempFile == true) {
|
||||
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||
if (lw) setLastModified(gmtime(&lw));
|
||||
String etag(lw ? lw : request->_tempFile.size()); // set etag to lastmod timestamp if available, otherwise to size
|
||||
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||
// set etag to lastmod timestamp if available, otherwise to size
|
||||
String etag;
|
||||
if (lw) {
|
||||
setLastModified(gmtime(&lw));
|
||||
#if defined(TARGET_RP2040)
|
||||
// time_t == long long int
|
||||
const size_t len = 1 + 8 * sizeof(time_t);
|
||||
char buf[len];
|
||||
char* ret = lltoa(lw, buf, len, 10);
|
||||
etag = ret ? String(ret) : String(request->_tempFile.size());
|
||||
#else
|
||||
etag = String(lw);
|
||||
#endif
|
||||
} else {
|
||||
etag = String(request->_tempFile.size());
|
||||
}
|
||||
if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) {
|
||||
request->_tempFile.close();
|
||||
request->send(304); // Not modified
|
||||
} else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) {
|
||||
request->_tempFile.close();
|
||||
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
|
||||
AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified
|
||||
response->addHeader(F("Cache-Control"), _cache_control);
|
||||
response->addHeader(F("ETag"), etag);
|
||||
request->send(response);
|
||||
} else {
|
||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
||||
AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
||||
if (_last_modified.length())
|
||||
response->addHeader(F("Last-Modified"), _last_modified);
|
||||
if (_cache_control.length()){
|
||||
if (_cache_control.length()) {
|
||||
response->addHeader(F("Cache-Control"), _cache_control);
|
||||
response->addHeader(F("ETag"), etag);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -22,115 +22,125 @@
|
||||
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||
|
||||
#ifdef Arduino_h
|
||||
// arduino is not compatible with std::vector
|
||||
#undef min
|
||||
#undef max
|
||||
// arduino is not compatible with std::vector
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#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:
|
||||
String _content;
|
||||
|
||||
public:
|
||||
AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String());
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
AsyncBasicResponse(int code, const String& contentType = String(), const String& content = String());
|
||||
void _respond(AsyncWebServerRequest* request);
|
||||
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return true; }
|
||||
};
|
||||
|
||||
class AsyncAbstractResponse: public AsyncWebServerResponse {
|
||||
class AsyncAbstractResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _head;
|
||||
// Data is inserted into cache at begin().
|
||||
// This is inefficient with vector, but if we use some other container,
|
||||
// Data is inserted into cache at begin().
|
||||
// This is inefficient with vector, but if we use some other container,
|
||||
// we won't be able to access it as contiguous array of bytes when reading from it,
|
||||
// so by gaining performance in one place, we'll lose it in another.
|
||||
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);
|
||||
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; }
|
||||
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
||||
};
|
||||
|
||||
#ifndef TEMPLATE_PLACEHOLDER
|
||||
#define TEMPLATE_PLACEHOLDER '%'
|
||||
#define TEMPLATE_PLACEHOLDER '%'
|
||||
#endif
|
||||
|
||||
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
||||
class AsyncFileResponse: public AsyncAbstractResponse {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
class AsyncFileResponse : public AsyncAbstractResponse {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
|
||||
private:
|
||||
File _content;
|
||||
String _path;
|
||||
void _setContentType(const String& path);
|
||||
|
||||
public:
|
||||
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(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;
|
||||
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncStreamResponse: public AsyncAbstractResponse {
|
||||
class AsyncStreamResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
Stream *_content;
|
||||
Stream* _content;
|
||||
|
||||
public:
|
||||
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
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;
|
||||
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncCallbackResponse: public AsyncAbstractResponse {
|
||||
class AsyncCallbackResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
|
||||
public:
|
||||
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
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;
|
||||
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncChunkedResponse: public AsyncAbstractResponse {
|
||||
class AsyncChunkedResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
|
||||
public:
|
||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||
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;
|
||||
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class AsyncProgmemResponse: public AsyncAbstractResponse {
|
||||
class AsyncProgmemResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
const uint8_t * _content;
|
||||
const uint8_t* _content;
|
||||
size_t _readLength;
|
||||
|
||||
public:
|
||||
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||
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;
|
||||
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||
};
|
||||
|
||||
class cbuf;
|
||||
|
||||
class AsyncResponseStream: public AsyncAbstractResponse, public Print {
|
||||
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
||||
private:
|
||||
std::unique_ptr<cbuf> _content;
|
||||
|
||||
public:
|
||||
AsyncResponseStream(const String& contentType, size_t bufferSize);
|
||||
~AsyncResponseStream();
|
||||
bool _sourceValid() const { return (_state < RESPONSE_END); }
|
||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||
size_t write(const uint8_t *data, size_t len);
|
||||
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||
size_t write(const uint8_t* data, size_t len);
|
||||
size_t write(uint8_t data);
|
||||
using Print::write;
|
||||
};
|
||||
|
@ -23,111 +23,140 @@
|
||||
#include "cbuf.h"
|
||||
|
||||
// Since ESP8266 does not link memchr by default, here's its implementation.
|
||||
void* memchr(void* ptr, int ch, size_t count)
|
||||
{
|
||||
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))
|
||||
while (count--)
|
||||
if (*p++ == static_cast<unsigned char>(ch))
|
||||
return --p;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Abstract Response
|
||||
* */
|
||||
const char* AsyncWebServerResponse::_responseCodeToString(int code) {
|
||||
return reinterpret_cast<const char *>(responseCodeToString(code));
|
||||
return reinterpret_cast<const char*>(responseCodeToString(code));
|
||||
}
|
||||
|
||||
const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) {
|
||||
const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code) {
|
||||
switch (code) {
|
||||
case 100: return F("Continue");
|
||||
case 101: return F("Switching Protocols");
|
||||
case 200: return F("OK");
|
||||
case 201: return F("Created");
|
||||
case 202: return F("Accepted");
|
||||
case 203: return F("Non-Authoritative Information");
|
||||
case 204: return F("No Content");
|
||||
case 205: return F("Reset Content");
|
||||
case 206: return F("Partial Content");
|
||||
case 300: return F("Multiple Choices");
|
||||
case 301: return F("Moved Permanently");
|
||||
case 302: return F("Found");
|
||||
case 303: return F("See Other");
|
||||
case 304: return F("Not Modified");
|
||||
case 305: return F("Use Proxy");
|
||||
case 307: return F("Temporary Redirect");
|
||||
case 400: return F("Bad Request");
|
||||
case 401: return F("Unauthorized");
|
||||
case 402: return F("Payment Required");
|
||||
case 403: return F("Forbidden");
|
||||
case 404: return F("Not Found");
|
||||
case 405: return F("Method Not Allowed");
|
||||
case 406: return F("Not Acceptable");
|
||||
case 407: return F("Proxy Authentication Required");
|
||||
case 408: return F("Request Time-out");
|
||||
case 409: return F("Conflict");
|
||||
case 410: return F("Gone");
|
||||
case 411: return F("Length Required");
|
||||
case 412: return F("Precondition Failed");
|
||||
case 413: return F("Request Entity Too Large");
|
||||
case 414: return F("Request-URI Too Large");
|
||||
case 415: return F("Unsupported Media Type");
|
||||
case 416: return F("Requested range not satisfiable");
|
||||
case 417: return F("Expectation Failed");
|
||||
case 500: return F("Internal Server Error");
|
||||
case 501: return F("Not Implemented");
|
||||
case 502: return F("Bad Gateway");
|
||||
case 503: return F("Service Unavailable");
|
||||
case 504: return F("Gateway Time-out");
|
||||
case 505: return F("HTTP Version not supported");
|
||||
default: return F("");
|
||||
case 100:
|
||||
return F("Continue");
|
||||
case 101:
|
||||
return F("Switching Protocols");
|
||||
case 200:
|
||||
return F("OK");
|
||||
case 201:
|
||||
return F("Created");
|
||||
case 202:
|
||||
return F("Accepted");
|
||||
case 203:
|
||||
return F("Non-Authoritative Information");
|
||||
case 204:
|
||||
return F("No Content");
|
||||
case 205:
|
||||
return F("Reset Content");
|
||||
case 206:
|
||||
return F("Partial Content");
|
||||
case 300:
|
||||
return F("Multiple Choices");
|
||||
case 301:
|
||||
return F("Moved Permanently");
|
||||
case 302:
|
||||
return F("Found");
|
||||
case 303:
|
||||
return F("See Other");
|
||||
case 304:
|
||||
return F("Not Modified");
|
||||
case 305:
|
||||
return F("Use Proxy");
|
||||
case 307:
|
||||
return F("Temporary Redirect");
|
||||
case 400:
|
||||
return F("Bad Request");
|
||||
case 401:
|
||||
return F("Unauthorized");
|
||||
case 402:
|
||||
return F("Payment Required");
|
||||
case 403:
|
||||
return F("Forbidden");
|
||||
case 404:
|
||||
return F("Not Found");
|
||||
case 405:
|
||||
return F("Method Not Allowed");
|
||||
case 406:
|
||||
return F("Not Acceptable");
|
||||
case 407:
|
||||
return F("Proxy Authentication Required");
|
||||
case 408:
|
||||
return F("Request Time-out");
|
||||
case 409:
|
||||
return F("Conflict");
|
||||
case 410:
|
||||
return F("Gone");
|
||||
case 411:
|
||||
return F("Length Required");
|
||||
case 412:
|
||||
return F("Precondition Failed");
|
||||
case 413:
|
||||
return F("Request Entity Too Large");
|
||||
case 414:
|
||||
return F("Request-URI Too Large");
|
||||
case 415:
|
||||
return F("Unsupported Media Type");
|
||||
case 416:
|
||||
return F("Requested range not satisfiable");
|
||||
case 417:
|
||||
return F("Expectation Failed");
|
||||
case 500:
|
||||
return F("Internal Server Error");
|
||||
case 501:
|
||||
return F("Not Implemented");
|
||||
case 502:
|
||||
return F("Bad Gateway");
|
||||
case 503:
|
||||
return F("Service Unavailable");
|
||||
case 504:
|
||||
return F("Gateway Time-out");
|
||||
case 505:
|
||||
return F("HTTP Version not supported");
|
||||
default:
|
||||
return F("");
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
: _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)
|
||||
void AsyncWebServerResponse::setCode(int code) {
|
||||
if (_state == RESPONSE_SETUP)
|
||||
_code = code;
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::setContentLength(size_t len){
|
||||
if(_state == RESPONSE_SETUP)
|
||||
void AsyncWebServerResponse::setContentLength(size_t len) {
|
||||
if (_state == RESPONSE_SETUP)
|
||||
_contentLength = len;
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::setContentType(const String& type){
|
||||
if(_state == RESPONSE_SETUP)
|
||||
void AsyncWebServerResponse::setContentType(const String& type) {
|
||||
if (_state == RESPONSE_SETUP)
|
||||
_contentType = type;
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::addHeader(const String& name, const String& value){
|
||||
void AsyncWebServerResponse::addHeader(const String& name, const String& value) {
|
||||
_headers.emplace_back(name, value);
|
||||
}
|
||||
|
||||
String AsyncWebServerResponse::_assembleHead(uint8_t version){
|
||||
if(version){
|
||||
String AsyncWebServerResponse::_assembleHead(uint8_t version) {
|
||||
if (version) {
|
||||
addHeader(F("Accept-Ranges"), F("none"));
|
||||
if(_chunked)
|
||||
if (_chunked)
|
||||
addHeader(F("Transfer-Encoding"), F("chunked"));
|
||||
}
|
||||
String out = String();
|
||||
@ -137,16 +166,16 @@ String AsyncWebServerResponse::_assembleHead(uint8_t version){
|
||||
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code));
|
||||
out.concat(buf);
|
||||
|
||||
if(_sendContentLength) {
|
||||
if (_sendContentLength) {
|
||||
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
|
||||
out.concat(buf);
|
||||
}
|
||||
if(_contentType.length()) {
|
||||
if (_contentType.length()) {
|
||||
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
|
||||
out.concat(buf);
|
||||
}
|
||||
|
||||
for(const auto& header: _headers){
|
||||
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);
|
||||
}
|
||||
@ -161,44 +190,52 @@ 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; }
|
||||
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 String& contentType, const String& content){
|
||||
AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content) {
|
||||
_code = code;
|
||||
_content = content;
|
||||
_contentType = contentType;
|
||||
if(_content.length()){
|
||||
if (_content.length()) {
|
||||
_contentLength = _content.length();
|
||||
if(!_contentType.length())
|
||||
if (!_contentType.length())
|
||||
_contentType = F("text/plain");
|
||||
}
|
||||
addHeader(F("Connection"), F("close"));
|
||||
}
|
||||
|
||||
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
|
||||
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){
|
||||
if (!_contentLength && space >= outLen) {
|
||||
_writtenLength += request->client()->write(out.c_str(), outLen);
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
} else if(_contentLength && space >= outLen + _contentLength){
|
||||
} 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){
|
||||
} 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)){
|
||||
} else if (space > outLen && space < (outLen + _contentLength)) {
|
||||
size_t shift = space - outLen;
|
||||
outLen += shift;
|
||||
_sentLength += shift;
|
||||
@ -213,58 +250,56 @@ void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
|
||||
}
|
||||
}
|
||||
|
||||
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
|
||||
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
|
||||
(void)time;
|
||||
_ackedLength += len;
|
||||
if(_state == RESPONSE_CONTENT){
|
||||
if (_state == RESPONSE_CONTENT) {
|
||||
size_t available = _contentLength - _sentLength;
|
||||
size_t space = request->client()->space();
|
||||
//we can fit in this packet
|
||||
if(space > available){
|
||||
// we can fit in this packet
|
||||
if (space > available) {
|
||||
_writtenLength += request->client()->write(_content.c_str(), available);
|
||||
_content = String();
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
return available;
|
||||
}
|
||||
//send some data, the rest on ack
|
||||
// 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){
|
||||
} else if (_state == RESPONSE_WAIT_ACK) {
|
||||
if (_ackedLength >= _writtenLength) {
|
||||
_state = RESPONSE_END;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Abstract Response
|
||||
* */
|
||||
|
||||
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback)
|
||||
{
|
||||
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _callback(callback) {
|
||||
// In case of template processing, we're unable to determine real response size
|
||||
if(callback) {
|
||||
if (callback) {
|
||||
_contentLength = 0;
|
||||
_sendContentLength = false;
|
||||
_chunked = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
|
||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) {
|
||||
addHeader(F("Connection"), F("close"));
|
||||
_head = _assembleHead(request->version());
|
||||
_state = RESPONSE_HEADERS;
|
||||
_ack(request, 0, 0);
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
|
||||
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
|
||||
(void)time;
|
||||
if(!_sourceValid()){
|
||||
if (!_sourceValid()) {
|
||||
_state = RESPONSE_FAILED;
|
||||
request->client()->close();
|
||||
return 0;
|
||||
@ -273,8 +308,8 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
||||
size_t space = request->client()->space();
|
||||
|
||||
size_t headLen = _head.length();
|
||||
if(_state == RESPONSE_HEADERS){
|
||||
if(space >= headLen){
|
||||
if (_state == RESPONSE_HEADERS) {
|
||||
if (space >= headLen) {
|
||||
_state = RESPONSE_CONTENT;
|
||||
space -= headLen;
|
||||
} else {
|
||||
@ -285,103 +320,102 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
|
||||
}
|
||||
}
|
||||
|
||||
if(_state == RESPONSE_CONTENT){
|
||||
if (_state == RESPONSE_CONTENT) {
|
||||
size_t outLen;
|
||||
if(_chunked){
|
||||
if(space <= 8){
|
||||
if (_chunked) {
|
||||
if (space <= 8) {
|
||||
return 0;
|
||||
}
|
||||
outLen = space;
|
||||
} else if(!_sendContentLength){
|
||||
} else if (!_sendContentLength) {
|
||||
outLen = space;
|
||||
} else {
|
||||
outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength);
|
||||
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t *)malloc(outLen+headLen);
|
||||
uint8_t* buf = (uint8_t*)malloc(outLen + headLen);
|
||||
if (!buf) {
|
||||
// os_printf("_ack malloc %d failed\n", outLen+headLen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(headLen){
|
||||
if (headLen) {
|
||||
memcpy(buf, _head.c_str(), _head.length());
|
||||
}
|
||||
|
||||
size_t readLen = 0;
|
||||
|
||||
if(_chunked){
|
||||
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;
|
||||
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++] = ' ';
|
||||
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;
|
||||
readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
|
||||
if (readLen == RESPONSE_TRY_AGAIN) {
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
outLen = readLen + headLen;
|
||||
}
|
||||
|
||||
if(headLen){
|
||||
_head = String();
|
||||
if (headLen) {
|
||||
_head = String();
|
||||
}
|
||||
|
||||
if(outLen){
|
||||
_writtenLength += request->client()->write((const char*)buf, outLen);
|
||||
if (outLen) {
|
||||
_writtenLength += request->client()->write((const char*)buf, outLen);
|
||||
}
|
||||
|
||||
if(_chunked){
|
||||
_sentLength += readLen;
|
||||
if (_chunked) {
|
||||
_sentLength += readLen;
|
||||
} else {
|
||||
_sentLength += outLen - headLen;
|
||||
_sentLength += outLen - headLen;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){
|
||||
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){
|
||||
} else if (_state == RESPONSE_WAIT_ACK) {
|
||||
if (!_sendContentLength || _ackedLength >= _writtenLength) {
|
||||
_state = RESPONSE_END;
|
||||
if(!_chunked && !_sendContentLength)
|
||||
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::_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)
|
||||
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) {
|
||||
if (!_callback)
|
||||
return _fillBuffer(data, len);
|
||||
|
||||
const size_t originalLen = len;
|
||||
@ -389,16 +423,16 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
||||
// 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]
|
||||
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) {
|
||||
if (pTemplateEnd) {
|
||||
// prepare argument to callback
|
||||
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
|
||||
if(paramNameLength) {
|
||||
if (paramNameLength) {
|
||||
memcpy(buf, pTemplateStart + 1, paramNameLength);
|
||||
buf[paramNameLength] = 0;
|
||||
paramName = String(reinterpret_cast<char*>(buf));
|
||||
@ -408,32 +442,29 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
||||
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
|
||||
} 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) {
|
||||
if (readFromCacheOrContent) {
|
||||
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
||||
if(pTemplateEnd) {
|
||||
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
|
||||
} 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
|
||||
} 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
|
||||
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
++pTemplateStart;
|
||||
if(paramName.length()) {
|
||||
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.
|
||||
@ -445,21 +476,21 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
||||
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)) {
|
||||
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
|
||||
// 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
|
||||
} 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) {
|
||||
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...
|
||||
} 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;
|
||||
@ -473,48 +504,66 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* File Response
|
||||
* */
|
||||
|
||||
AsyncFileResponse::~AsyncFileResponse(){
|
||||
if(_content)
|
||||
AsyncFileResponse::~AsyncFileResponse() {
|
||||
if (_content)
|
||||
_content.close();
|
||||
}
|
||||
|
||||
void AsyncFileResponse::_setContentType(const String& path){
|
||||
void AsyncFileResponse::_setContentType(const String& path) {
|
||||
#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION
|
||||
extern const __FlashStringHelper *getContentType(const String &path);
|
||||
extern const __FlashStringHelper* getContentType(const String& path);
|
||||
_contentType = getContentType(path);
|
||||
#else
|
||||
if (path.endsWith(F(".html"))) _contentType = F("text/html");
|
||||
else if (path.endsWith(F(".htm"))) _contentType = F("text/html");
|
||||
else if (path.endsWith(F(".css"))) _contentType = F("text/css");
|
||||
else if (path.endsWith(F(".json"))) _contentType = F("application/json");
|
||||
else if (path.endsWith(F(".js"))) _contentType = F("application/javascript");
|
||||
else if (path.endsWith(F(".png"))) _contentType = F("image/png");
|
||||
else if (path.endsWith(F(".gif"))) _contentType = F("image/gif");
|
||||
else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg");
|
||||
else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon");
|
||||
else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml");
|
||||
else if (path.endsWith(F(".eot"))) _contentType = F("font/eot");
|
||||
else if (path.endsWith(F(".woff"))) _contentType = F("font/woff");
|
||||
else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2");
|
||||
else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf");
|
||||
else if (path.endsWith(F(".xml"))) _contentType = F("text/xml");
|
||||
else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf");
|
||||
else if (path.endsWith(F(".zip"))) _contentType = F("application/zip");
|
||||
else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip");
|
||||
else _contentType = F("text/plain");
|
||||
if (path.endsWith(F(".html")))
|
||||
_contentType = F("text/html");
|
||||
else if (path.endsWith(F(".htm")))
|
||||
_contentType = F("text/html");
|
||||
else if (path.endsWith(F(".css")))
|
||||
_contentType = F("text/css");
|
||||
else if (path.endsWith(F(".json")))
|
||||
_contentType = F("application/json");
|
||||
else if (path.endsWith(F(".js")))
|
||||
_contentType = F("application/javascript");
|
||||
else if (path.endsWith(F(".png")))
|
||||
_contentType = F("image/png");
|
||||
else if (path.endsWith(F(".gif")))
|
||||
_contentType = F("image/gif");
|
||||
else if (path.endsWith(F(".jpg")))
|
||||
_contentType = F("image/jpeg");
|
||||
else if (path.endsWith(F(".ico")))
|
||||
_contentType = F("image/x-icon");
|
||||
else if (path.endsWith(F(".svg")))
|
||||
_contentType = F("image/svg+xml");
|
||||
else if (path.endsWith(F(".eot")))
|
||||
_contentType = F("font/eot");
|
||||
else if (path.endsWith(F(".woff")))
|
||||
_contentType = F("font/woff");
|
||||
else if (path.endsWith(F(".woff2")))
|
||||
_contentType = F("font/woff2");
|
||||
else if (path.endsWith(F(".ttf")))
|
||||
_contentType = F("font/ttf");
|
||||
else if (path.endsWith(F(".xml")))
|
||||
_contentType = F("text/xml");
|
||||
else if (path.endsWith(F(".pdf")))
|
||||
_contentType = F("application/pdf");
|
||||
else if (path.endsWith(F(".zip")))
|
||||
_contentType = F("application/zip");
|
||||
else if (path.endsWith(F(".gz")))
|
||||
_contentType = F("application/x-gzip");
|
||||
else
|
||||
_contentType = F("text/plain");
|
||||
#endif
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
|
||||
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 + F(".gz"))){
|
||||
if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) {
|
||||
_path = _path + F(".gz");
|
||||
addHeader(F("Content-Encoding"), F("gzip"));
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
@ -525,30 +574,30 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& c
|
||||
_content = fs.open(_path, fs::FileOpenMode::read);
|
||||
_contentLength = _content.size();
|
||||
|
||||
if(contentType.length() == 0)
|
||||
if (contentType.length() == 0)
|
||||
_setContentType(path);
|
||||
else
|
||||
_contentType = contentType;
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26+path.length()-filenameStart];
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char* filename = (char*)path.c_str() + filenameStart;
|
||||
|
||||
if(download) {
|
||||
if (download) {
|
||||
// set filename and force download
|
||||
snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||
} else {
|
||||
// set filename and force rendering
|
||||
snprintf_P(buf, sizeof (buf), PSTR("inline"));
|
||||
snprintf_P(buf, sizeof(buf), PSTR("inline"));
|
||||
}
|
||||
addHeader(F("Content-Disposition"), buf);
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
|
||||
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(F(".gz")) && !path.endsWith(F(".gz"))){
|
||||
if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) {
|
||||
addHeader(F("Content-Encoding"), F("gzip"));
|
||||
_callback = nullptr; // Unable to process gzipped templates
|
||||
_sendContentLength = true;
|
||||
@ -558,24 +607,24 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const Str
|
||||
_content = content;
|
||||
_contentLength = _content.size();
|
||||
|
||||
if(contentType.length() == 0)
|
||||
if (contentType.length() == 0)
|
||||
_setContentType(path);
|
||||
else
|
||||
_contentType = contentType;
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26+path.length()-filenameStart];
|
||||
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);
|
||||
if (download) {
|
||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||
} else {
|
||||
snprintf_P(buf, sizeof (buf), PSTR("inline"));
|
||||
snprintf_P(buf, sizeof(buf), PSTR("inline"));
|
||||
}
|
||||
addHeader(F("Content-Disposition"), buf);
|
||||
}
|
||||
|
||||
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
return _content.read(data, len);
|
||||
}
|
||||
|
||||
@ -583,18 +632,18 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
* Stream Response
|
||||
* */
|
||||
|
||||
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
|
||||
AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const String& 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 AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t available = _content->available();
|
||||
size_t outLen = (available > len)?len:available;
|
||||
size_t outLen = (available > len) ? len : available;
|
||||
size_t i;
|
||||
for(i=0;i<outLen;i++)
|
||||
for (i = 0; i < outLen; i++)
|
||||
data[i] = _content->read();
|
||||
return outLen;
|
||||
}
|
||||
@ -603,20 +652,20 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
* Callback Response
|
||||
* */
|
||||
|
||||
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) {
|
||||
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = len;
|
||||
if(!len)
|
||||
if (!len)
|
||||
_sendContentLength = false;
|
||||
_contentType = contentType;
|
||||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
if(ret != RESPONSE_TRY_AGAIN){
|
||||
_filledLength += ret;
|
||||
if (ret != RESPONSE_TRY_AGAIN) {
|
||||
_filledLength += ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -625,7 +674,7 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
* Chunked Response
|
||||
* */
|
||||
|
||||
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
|
||||
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = 0;
|
||||
@ -635,10 +684,10 @@ AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsRespons
|
||||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
if(ret != RESPONSE_TRY_AGAIN){
|
||||
_filledLength += ret;
|
||||
if (ret != RESPONSE_TRY_AGAIN) {
|
||||
_filledLength += ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -647,7 +696,7 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
* Progmem Response
|
||||
* */
|
||||
|
||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
|
||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
||||
_code = code;
|
||||
_content = content;
|
||||
_contentType = contentType;
|
||||
@ -655,7 +704,7 @@ AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType,
|
||||
_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 - _readLength;
|
||||
if (left > len) {
|
||||
memcpy_P(data, _content + _readLength, len);
|
||||
@ -667,30 +716,28 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||
return left;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
|
||||
* */
|
||||
|
||||
AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize)
|
||||
{
|
||||
AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize) {
|
||||
_code = 200;
|
||||
_contentLength = 0;
|
||||
_contentType = contentType;
|
||||
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); //std::make_unique<cbuf>(bufferSize);
|
||||
_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){
|
||||
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())
|
||||
size_t AsyncResponseStream::write(const uint8_t* data, size_t len) {
|
||||
if (_started())
|
||||
return 0;
|
||||
|
||||
if(len > _content->room()){
|
||||
if (len > _content->room()) {
|
||||
size_t needed = len - _content->room();
|
||||
_content->resizeAdd(needed);
|
||||
}
|
||||
@ -699,6 +746,6 @@ size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
|
||||
return written;
|
||||
}
|
||||
|
||||
size_t AsyncResponseStream::write(uint8_t data){
|
||||
size_t AsyncResponseStream::write(uint8_t data) {
|
||||
return write(&data, 1);
|
||||
}
|
||||
|
@ -21,105 +21,126 @@
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest* request) {
|
||||
return WiFi.localIP() == request->client()->localIP();
|
||||
}
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest* request) {
|
||||
return WiFi.localIP() != request->client()->localIP();
|
||||
}
|
||||
|
||||
#ifndef HAVE_FS_FILE_OPEN_MODE
|
||||
const char *fs::FileOpenMode::read = "r";
|
||||
const char *fs::FileOpenMode::write = "w";
|
||||
const char *fs::FileOpenMode::append = "a";
|
||||
const char* fs::FileOpenMode::read = "r";
|
||||
const char* fs::FileOpenMode::write = "w";
|
||||
const char* fs::FileOpenMode::append = "a";
|
||||
#endif
|
||||
|
||||
AsyncWebServer::AsyncWebServer(uint16_t port)
|
||||
: _server(port)
|
||||
, _rewrites(LinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; }))
|
||||
, _handlers(LinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; }))
|
||||
{
|
||||
: _server(port) {
|
||||
_catchAllHandler = new AsyncCallbackWebHandler();
|
||||
if(_catchAllHandler == NULL)
|
||||
if (_catchAllHandler == NULL)
|
||||
return;
|
||||
_server.onClient([](void *s, AsyncClient* c){
|
||||
if(c == NULL)
|
||||
_server.onClient([](void* s, AsyncClient* c) {
|
||||
if (c == NULL)
|
||||
return;
|
||||
c->setRxTimeout(3);
|
||||
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
|
||||
if(r == NULL){
|
||||
AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
|
||||
if (r == NULL) {
|
||||
c->close(true);
|
||||
c->free();
|
||||
delete c;
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
this);
|
||||
}
|
||||
|
||||
AsyncWebServer::~AsyncWebServer(){
|
||||
AsyncWebServer::~AsyncWebServer() {
|
||||
reset();
|
||||
end();
|
||||
if(_catchAllHandler) delete _catchAllHandler;
|
||||
if (_catchAllHandler)
|
||||
delete _catchAllHandler;
|
||||
}
|
||||
|
||||
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){
|
||||
_rewrites.add(rewrite);
|
||||
return *rewrite;
|
||||
AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
|
||||
_rewrites.emplace_back(rewrite);
|
||||
return *_rewrites.back().get();
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){
|
||||
return _rewrites.remove(rewrite);
|
||||
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) {
|
||||
_rewrites.emplace_back(rewrite);
|
||||
return *_rewrites.back().get();
|
||||
}
|
||||
|
||||
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
|
||||
return addRewrite(new AsyncWebRewrite(from, to));
|
||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) {
|
||||
return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
|
||||
}
|
||||
|
||||
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
|
||||
_handlers.add(handler);
|
||||
return *handler;
|
||||
bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
|
||||
for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
|
||||
if (r->get()->from() == from && r->get()->toUrl() == to) {
|
||||
_rewrites.erase(r);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){
|
||||
return _handlers.remove(handler);
|
||||
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) {
|
||||
_rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
|
||||
return *_rewrites.back().get();
|
||||
}
|
||||
|
||||
void AsyncWebServer::begin(){
|
||||
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) {
|
||||
_handlers.emplace_back(handler);
|
||||
return *(_handlers.back().get());
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) {
|
||||
for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
|
||||
if (i->get() == handler) {
|
||||
_handlers.erase(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AsyncWebServer::begin() {
|
||||
_server.setNoDelay(true);
|
||||
_server.begin();
|
||||
}
|
||||
|
||||
void AsyncWebServer::end(){
|
||||
void AsyncWebServer::end() {
|
||||
_server.end();
|
||||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){
|
||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) {
|
||||
_server.onSslFileRequest(cb, arg);
|
||||
}
|
||||
|
||||
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){
|
||||
void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) {
|
||||
_server.beginSecure(cert, key, password);
|
||||
}
|
||||
#endif
|
||||
|
||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){
|
||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
|
||||
delete request;
|
||||
}
|
||||
|
||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){
|
||||
for(const auto& r: _rewrites){
|
||||
if (r->match(request)){
|
||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
|
||||
for (const auto& r : _rewrites) {
|
||||
if (r->match(request)) {
|
||||
request->_url = r->toUrl();
|
||||
request->_addGetParams(r->params());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){
|
||||
for(const auto& h: _handlers){
|
||||
if (h->filter(request) && h->canHandle(request)){
|
||||
request->setHandler(h);
|
||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
|
||||
for (auto& h : _handlers) {
|
||||
if (h->filter(request) && h->canHandle(request)) {
|
||||
request->setHandler(h.get());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -128,8 +149,7 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){
|
||||
request->setHandler(_catchAllHandler);
|
||||
}
|
||||
|
||||
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
|
||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->setMethod(method);
|
||||
@ -140,7 +160,7 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
|
||||
return *handler;
|
||||
}
|
||||
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) {
|
||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->setMethod(method);
|
||||
@ -150,7 +170,7 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
|
||||
return *handler;
|
||||
}
|
||||
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) {
|
||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->setMethod(method);
|
||||
@ -159,7 +179,7 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
|
||||
return *handler;
|
||||
}
|
||||
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest) {
|
||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->onRequest(onRequest);
|
||||
@ -167,32 +187,31 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFun
|
||||
return *handler;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){
|
||||
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) {
|
||||
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
|
||||
addHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
||||
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){
|
||||
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) {
|
||||
_catchAllHandler->onRequest(fn);
|
||||
}
|
||||
|
||||
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){
|
||||
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn) {
|
||||
_catchAllHandler->onUpload(fn);
|
||||
}
|
||||
|
||||
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){
|
||||
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
|
||||
_catchAllHandler->onBody(fn);
|
||||
}
|
||||
|
||||
void AsyncWebServer::reset(){
|
||||
_rewrites.free();
|
||||
_handlers.free();
|
||||
void AsyncWebServer::reset() {
|
||||
_rewrites.clear();
|
||||
_handlers.clear();
|
||||
|
||||
if (_catchAllHandler != NULL){
|
||||
if (_catchAllHandler != NULL) {
|
||||
_catchAllHandler->onRequest(NULL);
|
||||
_catchAllHandler->onUpload(NULL);
|
||||
_catchAllHandler->onBody(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user