update libraries

This commit is contained in:
Proddy
2024-02-04 14:49:11 +01:00
parent afc34fc387
commit a580998f25
31 changed files with 5155 additions and 6896 deletions

View File

@@ -0,0 +1,405 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#include "AsyncEventSource.h"
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
String ev;
if(reconnect){
ev += F("retry: ");
ev += reconnect;
ev += F("\r\n");
}
if(id){
ev += F("id: ");
ev += String(id);
ev += F("\r\n");
}
if(event != NULL){
ev += F("event: ");
ev += String(event);
ev += F("\r\n");
}
if(message != NULL){
size_t messageLen = strlen(message);
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){
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += F("data: ");
ev += ldata;
ev += F("\r\n\r\n");
free(ldata);
}
lineStart = (char *)message + messageLen;
} else {
char * nextLine = NULL;
if(nextN != NULL && nextR != NULL){
if(nextR < nextN){
lineEnd = nextR;
if(nextN == (nextR + 1))
nextLine = nextN + 1;
else
nextLine = nextR + 1;
} else {
lineEnd = nextN;
if(nextR == (nextN + 1))
nextLine = nextR + 1;
else
nextLine = nextN + 1;
}
} else if(nextN != NULL){
lineEnd = nextN;
nextLine = nextN + 1;
} else {
lineEnd = nextR;
nextLine = nextR + 1;
}
size_t llen = lineEnd - lineStart;
char * ldata = (char *)malloc(llen+1);
if(ldata != NULL){
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += F("data: ");
ev += ldata;
ev += F("\r\n");
free(ldata);
}
lineStart = nextLine;
if(lineStart == ((char *)message + messageLen))
ev += F("\r\n");
}
} while(lineStart < ((char *)message + messageLen));
}
return ev;
}
// 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){
_len = 0;
} else {
memcpy(_data, data, len);
_data[_len] = 0;
}
}
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
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;
}
// Return that no extra bytes left.
_acked += len;
return 0;
}
// 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;
}
// Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
{
_client = request->client();
_server = server;
_lastId = 0;
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->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);
_server->_addClient(this);
delete request;
}
AsyncEventSourceClient::~AsyncEventSourceClient(){
_lockmq.lock();
_messageQueue.free();
_lockmq.unlock();
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){
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
delete dataMessage;
} else {
_messageQueue.add(dataMessage);
// runqueue trigger when new messages added
if(_client->canSend()) {
_runQueue();
}
}
_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()){
_runQueue();
}
_lockmq.unlock();
}
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
_client->close(true);
}
void AsyncEventSourceClient::_onDisconnect(){
_client = NULL;
_server->_handleDisconnect(this);
}
void AsyncEventSourceClient::close(){
if(_client != NULL)
_client->close();
}
void AsyncEventSourceClient::write(const char * message, size_t len){
_queueMessage(new AsyncEventSourceMessage(message, len));
}
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
String ev = generateEventMessage(message, event, id, reconnect);
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
}
size_t AsyncEventSourceClient::packetsWaiting() const {
size_t len;
_lockmq.lock();
len = _messageQueue.length();
_lockmq.unlock();
return len;
}
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);
}
}
}
// 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){
_connectcb = 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)
_connectcb(client);
}
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
AsyncWebLockGuard l(_client_queue_lock);
_clients.remove(client);
}
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())
c->close();
}
}
// pmb fix
size_t AsyncEventSource::avgPacketsWaiting() const {
size_t aql = 0;
uint32_t nConnectedClients = 0;
AsyncWebLockGuard l(_client_queue_lock);
if (_clients.isEmpty()) {
return 0;
}
for(const auto &c: _clients){
if(c->connected()) {
aql += c->packetsWaiting();
++nConnectedClients;
}
}
return ((aql) + (nConnectedClients/2)) / (nConnectedClients); // round up
}
void AsyncEventSource::send(
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()) {
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();
});
return n_clients;
}
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
return false;
}
request->addInterestingHeader(F("Last-Event-ID"));
request->addInterestingHeader("Cookie");
return true;
}
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)){
return request->send(401);
}
}
request->send(new AsyncEventSourceResponse(this));
}
// Response
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
_server = server;
_code = 200;
_contentType = F("text/event-stream");
_sendContentLength = false;
addHeader(F("Cache-Control"), F("no-cache"));
addHeader(F("Connection"), F("keep-alive"));
}
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){
new AsyncEventSourceClient(request, _server);
}
return 0;
}

View File

@@ -0,0 +1,147 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
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 ASYNCEVENTSOURCE_H_
#define ASYNCEVENTSOURCE_H_
#include <Arduino.h>
#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
#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
#endif
#ifdef ESP32
#define DEFAULT_MAX_SSE_CLIENTS 8
#else
#define DEFAULT_MAX_SSE_CLIENTS 4
#endif
class AsyncEventSource;
class AsyncEventSourceResponse;
class AsyncEventSourceClient;
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
typedef std::function<bool(AsyncWebServerRequest *request)> ArAuthorizeConnectHandler;
class AsyncEventSourceMessage {
private:
uint8_t * _data;
size_t _len;
size_t _sent;
//size_t _ack;
size_t _acked;
public:
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; }
bool sent() { return _sent == _len; }
};
class AsyncEventSourceClient {
private:
AsyncClient *_client;
AsyncEventSource *_server;
uint32_t _lastId;
LinkedList<AsyncEventSourceMessage *> _messageQueue;
// ArFi 2020-08-27 for protecting/serializing _messageQueue
AsyncPlainLock _lockmq;
void _queueMessage(AsyncEventSourceMessage *dataMessage);
void _runQueue();
public:
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
~AsyncEventSourceClient();
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);
bool connected() const { return (_client != NULL) && _client->connected(); }
uint32_t lastId() const { return _lastId; }
size_t packetsWaiting() const;
//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 {
private:
String _url;
LinkedList<AsyncEventSourceClient *> _clients;
// 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();
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);
// number of clients connected
size_t count() 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;
};
class AsyncEventSourceResponse: public AsyncWebServerResponse {
private:
String _content;
AsyncEventSource *_server;
public:
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_ */

View File

@@ -0,0 +1,351 @@
// AsyncJson.h
/*
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
Example of callback in use
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
root["key1"] = "key number one";
JsonObject& nested = root.createNestedObject("nested");
nested["key1"] = "key number one";
response->setLength();
request->send(response);
});
--------------------
Async Request to use with ArduinoJson and AsyncWebServer
Written by Arsène von Wyss (avonwyss)
Example
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject& jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);
*/
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <Print.h>
#if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif
#endif
constexpr const char * JSON_MIMETYPE = "application/json";
/*
* Json Response
* */
class ChunkPrint : public Print {
private:
uint8_t * _destination;
size_t _to_skip;
size_t _to_write;
size_t _pos;
public:
ChunkPrint(uint8_t * destination, size_t from, size_t len)
: _destination(destination)
, _to_skip(from)
, _to_write(len)
, _pos{0} {
}
virtual ~ChunkPrint() {
}
size_t write(uint8_t c) {
if (_to_skip > 0) {
_to_skip--;
return 1;
} else if (_to_write > 0) {
_to_write--;
_destination[_pos++] = c;
return 1;
}
return 0;
}
size_t write(const uint8_t * buffer, size_t size) {
return this->Print::write(buffer, size);
}
};
// added by Proddy
class MsgpackAsyncJsonResponse : public AsyncAbstractResponse {
protected:
JsonDocument _jsonBuffer;
JsonVariant _root;
bool _isValid;
public:
MsgpackAsyncJsonResponse(bool isArray = false)
: _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if (isArray)
_root = _jsonBuffer.to<JsonArray>();
else
_root = _jsonBuffer.add<JsonObject>();
}
~MsgpackAsyncJsonResponse() {
}
JsonVariant getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength() {
_contentLength = measureMsgPack(_root);
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t getSize() {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t * data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
serializeMsgPack(_root, dest);
return len;
}
};
class AsyncJsonResponse : public AsyncAbstractResponse {
protected:
#if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer _jsonBuffer;
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer;
#else
JsonDocument _jsonBuffer;
#endif
JsonVariant _root;
bool _isValid;
public:
#if ARDUINOJSON_VERSION_MAJOR == 5
AsyncJsonResponse(bool isArray = false)
: _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if (isArray)
_root = _jsonBuffer.createArray();
else
_root = _jsonBuffer.createObject();
}
#elif ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: _jsonBuffer(maxJsonBufferSize)
, _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if (isArray)
_root = _jsonBuffer.createNestedArray();
else
_root = _jsonBuffer.createNestedObject();
}
#else
AsyncJsonResponse(bool isArray = false)
: _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if (isArray)
_root = _jsonBuffer.add<JsonArray>();
else
_root = _jsonBuffer.add<JsonObject>();
}
#endif
~AsyncJsonResponse() {
}
JsonVariant & getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measureLength();
#else
_contentLength = measureJson(_root);
#endif
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t getSize() {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t * data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5
_root.printTo(dest);
#else
serializeJson(_root, dest);
#endif
return len;
}
};
class PrettyAsyncJsonResponse : public AsyncJsonResponse {
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: AsyncJsonResponse{isArray, maxJsonBufferSize} {
}
#else
PrettyAsyncJsonResponse(bool isArray = false)
: AsyncJsonResponse{isArray} {
}
#endif
size_t setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measurePrettyLength();
#else
_contentLength = measureJsonPretty(_root);
#endif
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t _fillBuffer(uint8_t * data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5
_root.prettyPrintTo(dest);
#else
serializeJsonPretty(_root, dest);
#endif
return len;
}
};
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant & json)> ArJsonRequestHandlerFunction;
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
private:
protected:
const String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
const size_t maxJsonBufferSize;
#endif
size_t _maxContentLength;
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: _uri(uri)
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
, _onRequest(onRequest)
, maxJsonBufferSize(maxJsonBufferSize)
, _maxContentLength(16384) {
}
#else
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest)
: _uri(uri)
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
, _onRequest(onRequest)
, _maxContentLength(16384) {
}
#endif
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
virtual bool canHandle(AsyncWebServerRequest * request) override final {
if (!_onRequest)
return false;
if (!(_method & request->method()))
return false;
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
return false;
if (!request->contentType().equalsIgnoreCase(JSON_MIMETYPE))
return false;
request->addInterestingHeader("ANY");
return true;
}
virtual void handleRequest(AsyncWebServerRequest * request) override final {
if (_onRequest) {
if (request->_tempObject != NULL) {
#if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
if (json.success()) {
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#else
JsonDocument jsonBuffer;
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
_onRequest(request, json);
return;
}
}
request->send(_contentLength > _maxContentLength ? 413 : 400);
} 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 {
}
virtual void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override final {
if (_onRequest) {
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
}
if (request->_tempObject != NULL) {
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
}
}
}
virtual bool isRequestHandlerTrivial() override final {
return _onRequest ? false : true;
}
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
/*
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 ASYNCWEBSOCKET_H_
#define ASYNCWEBSOCKET_H_
#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
#endif
#include <ESPAsyncWebServer.h>
#include "AsyncWebSynchronization.h"
#include <list>
#include <deque>
#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
#endif
#ifdef ESP32
#define DEFAULT_MAX_WS_CLIENTS 8
#else
#define DEFAULT_MAX_WS_CLIENTS 4
#endif
class AsyncWebSocket;
class AsyncWebSocketResponse;
class AsyncWebSocketClient;
class AsyncWebSocketControl;
typedef struct {
/** Message type as defined by enum AwsFrameType.
* Note: Applications will only see WS_TEXT and WS_BINARY.
* All other types are handled by the library. */
uint8_t message_opcode;
/** Frame number of a fragmented message. */
uint32_t num;
/** Is this the last frame in a fragmented message ?*/
uint8_t final;
/** Is this frame masked? */
uint8_t masked;
/** Message type as defined by enum AwsFrameType.
* This value is the same as message_opcode for non-fragmented
* messages, but may also be WS_CONTINUATION in a fragmented message. */
uint8_t opcode;
/** Length of the current frame.
* This equals the total length of the message if num == 0 && final == true */
uint64_t len;
/** Mask key */
uint8_t mask[4];
/** Offset of the data inside the current frame. */
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;
class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket;
friend AsyncWebSocketClient;
private:
std::shared_ptr<std::vector<uint8_t>> _buffer;
public:
AsyncWebSocketMessageBuffer();
AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(uint8_t* data, size_t size);
~AsyncWebSocketMessageBuffer();
bool reserve(size_t size);
uint8_t* get() { return _buffer->data(); }
size_t length() const { return _buffer->size(); }
};
class AsyncWebSocketMessage
{
private:
std::shared_ptr<std::vector<uint8_t>> _WSbuffer;
uint8_t _opcode{WS_TEXT};
bool _mask{false};
AwsMessageStatus _status{WS_MSG_ERROR};
size_t _sent{};
size_t _ack{};
size_t _acked{};
public:
AsyncWebSocketMessage(std::shared_ptr<std::vector<uint8_t>> 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);
};
class AsyncWebSocketClient {
private:
AsyncClient *_client;
AsyncWebSocket *_server;
uint32_t _clientId;
AwsClientStatus _status;
AsyncWebLock _lock;
std::deque<AsyncWebSocketControl> _controlQueue;
std::deque<AsyncWebSocketMessage> _messageQueue;
uint8_t _pstate;
AwsFrameInfo _pinfo;
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(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode=WS_TEXT, bool mask=false);
void _runQueue();
void _clearQueue();
public:
void *_tempObject;
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
~AsyncWebSocketClient();
//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; }
IPAddress remoteIP() 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);
//set auto-ping period in seconds. disabled if zero (default)
void keepAlivePeriod(uint16_t seconds){
_keepAlivePeriod = seconds * 1000;
}
uint16_t keepAlivePeriod(){
return (uint16_t)(_keepAlivePeriod / 1000);
}
//data packets
void message(std::shared_ptr<std::vector<uint8_t>> 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)));
#ifndef ESP32
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
#endif
void text(std::shared_ptr<std::vector<uint8_t>> 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 __FlashStringHelper *message);
void text(AsyncWebSocketMessageBuffer *buffer);
void binary(std::shared_ptr<std::vector<uint8_t>> 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 __FlashStringHelper *message, size_t len);
void binary(AsyncWebSocketMessageBuffer *buffer);
bool canSend() const;
//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);
};
typedef std::function<bool(AsyncWebServerRequest *request)> AwsHandshakeHandler;
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;
//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;
AwsHandshakeHandler _handshakeHandler;
bool _enabled;
AsyncWebLock _lock;
public:
AsyncWebSocket(const String& url);
~AsyncWebSocket();
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) != 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 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 __FlashStringHelper *message);
void textAll(std::shared_ptr<std::vector<uint8_t>> 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 __FlashStringHelper *message); // need to convert
void textAll(AsyncWebSocketMessageBuffer *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 __FlashStringHelper *message, size_t len);
void binaryAll(std::shared_ptr<std::vector<uint8_t>> 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 __FlashStringHelper *message, size_t len);
void binaryAll(AsyncWebSocketMessageBuffer *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)));
//event listener
void onEvent(AwsEventHandler handler){
_eventHandler = handler;
}
// Handshake 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;
// messagebuffer functions/objects.
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
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 {
private:
String _content;
AsyncWebSocket *_server;
public:
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_ */

View File

@@ -0,0 +1,134 @@
#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_

View File

@@ -0,0 +1,500 @@
/*
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 _ESPAsyncWebServer_H_
#define _ESPAsyncWebServer_H_
#include "Arduino.h"
#include <functional>
#include <list>
#include <vector>
#include "FS.h"
#include "StringArray.h"
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#else
#error Platform not supported
#endif
#define ASYNCWEBSERVER_VERSION "2.6.1"
#define ASYNCWEBSERVER_VERSION_MAJOR 2
#define ASYNCWEBSERVER_VERSION_MINOR 6
#define ASYNCWEBSERVER_VERSION_REVISION 1
#define ASYNCWEBSERVER_FORK_mathieucarbou
#ifdef ASYNCWEBSERVER_REGEX
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
#else
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
#endif
class AsyncWebServer;
class AsyncWebServerRequest;
class AsyncWebServerResponse;
class AsyncWebHeader;
class AsyncWebParameter;
class AsyncWebRewrite;
class AsyncWebHandler;
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;
#endif
#ifndef HAVE_FS_FILE_OPEN_MODE
namespace fs {
class FileOpenMode {
public:
static const char *read;
static const char *write;
static const char *append;
};
};
#else
#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
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
typedef uint8_t WebRequestMethodComposite;
typedef std::function<void(void)> ArDisconnectHandler;
/*
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
* */
class AsyncWebParameter {
private:
String _name;
String _value;
size_t _size;
bool _isForm;
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){}
const String& name() const { return _name; }
const String& value() const { return _value; }
size_t size() const { return _size; }
bool isPost() const { return _isForm; }
bool isFile() const { return _isFile; }
};
/*
* HEADER :: Chainable object to hold the headers
* */
class AsyncWebHeader {
private:
String _name;
String _value;
public:
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;
int index = data.indexOf(':');
if (index < 0) return;
_name = data.substring(0, index);
_value = data.substring(index + 2);
}
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
const String& name() const { return _name; }
const String& value() const { return _value; }
String toString() const { return _name + F(": ") + _value + F("\r\n"); }
};
/*
* 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 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;
private:
AsyncClient* _client;
AsyncWebServer* _server;
AsyncWebHandler* _handler;
AsyncWebServerResponse* _response;
std::vector<String> _interestingHeaders;
ArDisconnectHandler _onDisconnectfn;
String _temp;
uint8_t _parseState;
uint8_t _version;
WebRequestMethodComposite _method;
String _url;
String _host;
String _contentType;
String _boundary;
String _authorization;
RequestedConnectionType _reqconntype;
void _removeNotInterestingHeaders();
bool _isDigest;
bool _isMultipart;
bool _isPlainPost;
bool _expectingContinue;
size_t _contentLength;
size_t _parsedLength;
std::list<AsyncWebHeader> _headers;
LinkedList<AsyncWebParameter *> _params;
std::vector<String> _pathParams;
uint8_t _multiParseState;
uint8_t _boundaryPosition;
size_t _itemStartIndex;
size_t _itemSize;
String _itemName;
String _itemFilename;
String _itemType;
String _itemValue;
uint8_t *_itemBuffer;
size_t _itemBufferIndex;
bool _itemIsFile;
void _onPoll();
void _onAck(size_t len, uint32_t time);
void _onError(int8_t error);
void _onTimeout(uint32_t time);
void _onDisconnect();
void _onData(void *buf, size_t len);
void _addParam(AsyncWebParameter*);
void _addPathParam(const char *param);
bool _parseReqHead();
bool _parseReqHeader();
void _parseLine();
void _parsePlainPostChar(uint8_t data);
void _parseMultipartPostByte(uint8_t data, bool last);
void _addGetParams(const String& params);
void _handleUploadStart();
void _handleUploadByte(uint8_t data, bool last);
void _handleUploadEnd();
public:
File _tempFile;
void *_tempObject;
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
~AsyncWebServerRequest();
AsyncClient* client(){ return _client; }
uint8_t version() const { return _version; }
WebRequestMethodComposite method() const { return _method; }
const String& url() const { return _url; }
const String& host() const { return _host; }
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;
RequestedConnectionType requestedConnType() const { return _reqconntype; }
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
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);
void setHandler(AsyncWebHandler *handler){ _handler = handler; }
void addInterestingHeader(const String& name);
void redirect(const String& url);
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);
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);
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
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);
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;
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;
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 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
String urlDecode(const String& text) const;
};
/*
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
* */
typedef std::function<bool(AsyncWebServerRequest *request)> ArRequestFilterFunction;
bool ON_STA_FILTER(AsyncWebServerRequest *request);
bool ON_AP_FILTER(AsyncWebServerRequest *request);
/*
* REWRITE :: One instance can be handle any Request (done by the Server)
* */
class AsyncWebRewrite {
protected:
String _from;
String _toUrl;
String _params;
ArRequestFilterFunction _filter;
public:
AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){
int index = _toUrl.indexOf('?');
if (index > 0) {
_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); }
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); }
};
/*
* HANDLER :: One instance can be attached to any Request (done by the Server)
* */
class AsyncWebHandler {
protected:
ArRequestFilterFunction _filter;
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; };
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;}
};
/*
* RESPONSE :: One instance is created for each Request (attached by the Handler)
* */
typedef enum {
RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED
} WebResponseState;
class AsyncWebServerResponse {
protected:
int _code;
std::list<AsyncWebHeader> _headers;
String _contentType;
size_t _contentLength;
bool _sendContentLength;
bool _chunked;
size_t _headLength;
size_t _sentLength;
size_t _ackedLength;
size_t _writtenLength;
WebResponseState _state;
const char* _responseCodeToString(int code);
public:
static const __FlashStringHelper *responseCodeToString(int code);
public:
AsyncWebServerResponse();
virtual ~AsyncWebServerResponse();
virtual void setCode(int code);
virtual void setContentLength(size_t len);
virtual void setContentType(const String& type);
virtual void addHeader(const String& name, const String& value);
virtual String _assembleHead(uint8_t version);
virtual bool _started() const;
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);
};
/*
* 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;
class AsyncWebServer {
protected:
AsyncServer _server;
LinkedList<AsyncWebRewrite*> _rewrites;
LinkedList<AsyncWebHandler*> _handlers;
AsyncCallbackWebHandler* _catchAllHandler;
public:
AsyncWebServer(uint16_t port);
~AsyncWebServer();
void begin();
void end();
#if ASYNC_TCP_SSL_ENABLED
void onSslFileRequest(AcSSlFileHandler cb, void* arg);
void beginSecure(const char *cert, const char *private_key_file, const char *password);
#endif
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
bool removeRewrite(AsyncWebRewrite* rewrite);
AsyncWebRewrite& rewrite(const char* from, const char* to);
AsyncWebHandler& addHandler(AsyncWebHandler* handler);
bool removeHandler(AsyncWebHandler* handler);
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
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 reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
void _handleDisconnect(AsyncWebServerRequest *request);
void _attachHandler(AsyncWebServerRequest *request);
void _rewriteRequest(AsyncWebServerRequest *request);
};
class DefaultHeaders {
using headers_t = std::list<AsyncWebHeader>;
headers_t _headers;
public:
DefaultHeaders() = default;
using ConstIterator = headers_t::const_iterator;
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(); }
DefaultHeaders(DefaultHeaders const &) = delete;
DefaultHeaders &operator=(DefaultHeaders const &) = delete;
static DefaultHeaders &Instance() {
static DefaultHeaders instance;
return instance;
}
};
#include "WebResponseImpl.h"
#include "WebHandlerImpl.h"
#include "AsyncWebSocket.h"
#include "AsyncEventSource.h"
#endif /* _AsyncWebServer_H_ */

View File

@@ -0,0 +1,2 @@
// to please Arduino Lint
#include "ESPAsyncWebServer.h"

View File

@@ -0,0 +1,174 @@
/*
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_ */

View File

@@ -0,0 +1,241 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "WebAuthentication.h"
#include <libb64/cencode.h>
#ifdef ESP32
#include "mbedtls/md5.h"
#else
#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)
return false;
size_t toencodeLen = strlen(username)+strlen(password)+1;
size_t encodedLen = base64_encode_expected_len(toencodeLen);
if(strlen(hash) != encodedLen)
// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667
#ifdef ARDUINO_ARCH_ESP32
if(strlen(hash) != encodedLen)
#else
if (strlen(hash) != encodedLen - 1)
#endif
return false;
char *toencode = new char[toencodeLen+1];
if(toencode == NULL){
return false;
}
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){
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
return false;
}
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
#ifdef ESP32
mbedtls_md5_context _ctx;
#else
md5_context_t _ctx;
#endif
uint8_t i;
uint8_t * _buf = (uint8_t*)malloc(16);
if(_buf == NULL)
return false;
memset(_buf, 0x00, 16);
#ifdef ESP32
mbedtls_md5_init(&_ctx);
mbedtls_md5_starts_ret(&_ctx);
mbedtls_md5_update_ret(&_ctx, data, len);
mbedtls_md5_finish_ret(&_ctx, _buf);
#else
MD5Init(&_ctx);
MD5Update(&_ctx, data, len);
MD5Final(_buf, &_ctx);
#endif
for(i = 0; i < 16; i++) {
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
}
free(_buf);
return true;
}
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))
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))
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){
return emptyString;
}
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))
return emptyString;
res.concat(out);
free(out);
return res;
}
String requestDigestAuthentication(const char * realm){
String header = F("realm=\"");
if(realm == NULL)
header.concat(F("asyncesp"));
else
header.concat(realm);
header.concat(F("\", qop=\"auth\", nonce=\""));
header.concat(genRandomMD5());
header.concat(F("\", opaque=\""));
header.concat(genRandomMD5());
header += '"';
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");
return false;
}
String myHeader = String(header);
int nextBreak = myHeader.indexOf(',');
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();
myHeader += F(", ");
do {
String avLine = myHeader.substring(0, nextBreak);
avLine.trim();
myHeader = myHeader.substring(nextBreak+1);
nextBreak = myHeader.indexOf(',');
int eqSign = avLine.indexOf('=');
if(eqSign < 0){
//os_printf("AUTH FAIL: no = sign\n");
return false;
}
String varName = avLine.substring(0, eqSign);
avLine = avLine.substring(eqSign + 1);
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");
return false;
}
myUsername = avLine;
} 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");
return false;
}
myNonce = avLine;
} 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");
return false;
}
myUri = avLine;
} else if(varName.equals(F("response"))){
myResponse = avLine;
} else if(varName.equals(F("qop"))){
myQop = avLine;
} else if(varName.equals(F("nc"))){
myNc = avLine;
} else if(varName.equals(F("cnonce"))){
myCnonce = avLine;
}
} while(nextBreak > 0);
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(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");
return true;
}
//os_printf("AUTH FAIL: password\n");
return false;
}

View File

@@ -0,0 +1,34 @@
/*
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 WEB_AUTHENTICATION_H_
#define WEB_AUTHENTICATION_H_
#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);
//for storing hashed versions on the device that can be authenticated against
String generateDigestHash(const char * username, const char * password, const char * realm);
#endif

View File

@@ -0,0 +1,151 @@
/*
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 ASYNCWEBSERVERHANDLERIMPL_H_
#define ASYNCWEBSERVERHANDLERIMPL_H_
#include <string>
#ifdef ASYNCWEBSERVER_REGEX
#include <regex>
#endif
#include "stddef.h"
#include <time.h>
class AsyncStaticWebHandler: public AsyncWebHandler {
using File = fs::File;
using FS = fs::FS;
private:
bool _getFile(AsyncWebServerRequest *request);
bool _fileExists(AsyncWebServerRequest *request, const String& path);
uint8_t _countBits(const uint8_t value) const;
protected:
FS _fs;
String _uri;
String _path;
String _default_file;
String _cache_control;
String _last_modified;
AwsTemplateProcessor _callback;
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;
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
AsyncStaticWebHandler& setLastModified(time_t last_modified);
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
#endif
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
};
class AsyncCallbackWebHandler: public AsyncWebHandler {
private:
protected:
String _uri;
WebRequestMethodComposite _method;
ArRequestHandlerFunction _onRequest;
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;
_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; }
virtual bool canHandle(AsyncWebServerRequest *request) override final{
if(!_onRequest)
return false;
if(!(_method & request->method()))
return false;
#ifdef ASYNCWEBSERVER_REGEX
if (_isRegex) {
std::regex pattern(_uri.c_str());
std::smatch matches;
std::string s(request->url().c_str());
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
#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("*")) {
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+"/")))
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()))
return request->requestAuthentication();
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()))
return request->requestAuthentication();
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()))
return request->requestAuthentication();
if(_onBody)
_onBody(request, data, len, index, total);
}
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
};
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */

View File

@@ -0,0 +1,233 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "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)
{
// Ensure leading '/'
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] == '/';
// 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);
// Reset stats
_gzipFirst = false;
_gzipStats = 0xF8;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
_isDir = isDir;
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){
_default_file = String(filename);
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){
_cache_control = String(cache_control);
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){
_last_modified = last_modified;
return *this;
}
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);
}
#ifdef ESP8266
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){
return setLastModified((struct tm *)gmtime(&last_modified));
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){
time_t last_modified;
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)
){
return false;
}
if (_getFile(request)) {
// We interested in "If-Modified-Since" header to check if file was modified
if (_last_modified.length())
request->addInterestingHeader(F("If-Modified-Since"));
if(_cache_control.length())
request->addInterestingHeader(F("If-None-Match"));
return true;
}
return false;
}
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] == '/');
path = _path + path;
// Do we have a file or .gz file
if (!canSkipFileCheck && _fileExists(request, path))
return true;
// Can't handle if not default file
if (_default_file.length() == 0)
return false;
// Try to add default file, ensure there is a trailing '/' ot the path.
if (path.length() == 0 || path[path.length()-1] != '/')
path += String('/');
path += _default_file;
return _fileExists(request, path);
}
#ifdef ESP32
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
#else
#define FILE_IS_REAL(f) (f == true)
#endif
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)) {
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
gzipFound = FILE_IS_REAL(request->_tempFile);
}
}
}
bool found = fileFound || gzipFound;
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());
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
}
return found;
}
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;
return n;
}
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 (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
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
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);
if (_last_modified.length())
response->addHeader(F("Last-Modified"), _last_modified);
if (_cache_control.length()){
response->addHeader(F("Cache-Control"), _cache_control);
response->addHeader(F("ETag"), etag);
}
request->send(response);
}
} else {
request->send(404);
}
}

View File

@@ -0,0 +1,993 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebResponseImpl.h"
#include "WebAuthentication.h"
#ifndef ESP8266
#define os_strlen strlen
#endif
#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL };
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
: _client(c)
, _server(s)
, _handler(NULL)
, _response(NULL)
, _temp()
, _parseState(0)
, _version(0)
, _method(HTTP_ANY)
, _url()
, _host()
, _contentType()
, _boundary()
, _authorization()
, _reqconntype(RCT_HTTP)
, _isDigest(false)
, _isMultipart(false)
, _isPlainPost(false)
, _expectingContinue(false)
, _contentLength(0)
, _parsedLength(0)
, _params(LinkedList<AsyncWebParameter *>([](AsyncWebParameter *p){ delete p; }))
, _multiParseState(0)
, _boundaryPosition(0)
, _itemStartIndex(0)
, _itemSize(0)
, _itemName()
, _itemFilename()
, _itemType()
, _itemValue()
, _itemBuffer(0)
, _itemBufferIndex(0)
, _itemIsFile(false)
, _tempObject(NULL)
{
c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this);
c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this);
c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this);
c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this);
c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this);
}
AsyncWebServerRequest::~AsyncWebServerRequest(){
_headers.clear();
_params.free();
_pathParams.clear();
_interestingHeaders.clear();
if(_response != NULL){
delete _response;
}
if(_tempObject != NULL){
free(_tempObject);
}
if(_tempFile){
_tempFile.close();
}
if(_itemBuffer){
free(_itemBuffer);
}
}
void AsyncWebServerRequest::_onData(void *buf, size_t len){
size_t i = 0;
while (true) {
if(_parseState < PARSE_REQ_BODY){
// Find new line in buf
char *str = (char*)buf;
for (i = 0; i < len; i++) {
if (str[i] == '\n') {
break;
}
}
if (i == len) { // No new line, just add the buffer in _temp
char ch = str[len-1];
str[len-1] = 0;
_temp.reserve(_temp.length()+len);
_temp.concat(str);
_temp.concat(ch);
} else { // Found new line - extract it and parse
str[i] = 0; // Terminate the string at the end of the line.
_temp.concat(str);
_temp.trim();
_parseLine();
if (++i < len) {
// Still have more buffer to process
buf = str+i;
len-= i;
continue;
}
}
} else if(_parseState == PARSE_REQ_BODY){
// A handler should be already attached at this point in _parseLine function.
// If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
if(_isMultipart){
if(needParse){
size_t i;
for(i=0; i<len; i++){
_parseMultipartPostByte(((uint8_t*)buf)[i], i == len - 1);
_parsedLength++;
}
} else
_parsedLength += len;
} else {
if(_parsedLength == 0){
if(_contentType.startsWith(F("application/x-www-form-urlencoded"))){
_isPlainPost = true;
} else if(_contentType == F("text/plain") && __is_param_char(((char*)buf)[0])){
size_t i = 0;
while (i<len && __is_param_char(((char*)buf)[i++]));
if(i < len && ((char*)buf)[i-1] == '='){
_isPlainPost = true;
}
}
}
if(!_isPlainPost) {
//check if authenticated before calling the body
if(_handler) _handler->handleBody(this, (uint8_t*)buf, len, _parsedLength, _contentLength);
_parsedLength += len;
} else if(needParse) {
size_t i;
for(i=0; i<len; i++){
_parsedLength++;
_parsePlainPostChar(((uint8_t*)buf)[i]);
}
} else {
_parsedLength += len;
}
}
if(_parsedLength == _contentLength){
_parseState = PARSE_REQ_END;
//check if authenticated before calling handleRequest and request auth instead
if(_handler) _handler->handleRequest(this);
else send(501);
}
}
break;
}
}
void AsyncWebServerRequest::_removeNotInterestingHeaders(){
if (std::any_of(std::begin(_interestingHeaders), std::end(_interestingHeaders),
[](const String &str){ return str.equalsIgnoreCase(F("ANY")); }))
return; // nothing to do
for(auto iter = std::begin(_headers); iter != std::end(_headers); )
{
const auto name = iter->name();
if (std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders),
[&name](const String &str){ return str.equalsIgnoreCase(name); }))
iter = _headers.erase(iter);
else
iter++;
}
}
void AsyncWebServerRequest::_onPoll(){
//os_printf("p\n");
if(_response != NULL && _client != NULL && _client->canSend()){
if(!_response->_finished()){
_response->_ack(this, 0, 0);
} else {
AsyncWebServerResponse* r = _response;
_response = NULL;
delete r;
_client->close();
}
}
}
void AsyncWebServerRequest::_onAck(size_t len, uint32_t time){
//os_printf("a:%u:%u\n", len, time);
if(_response != NULL){
if(!_response->_finished()){
_response->_ack(this, len, time);
} else if(_response->_finished()){
AsyncWebServerResponse* r = _response;
_response = NULL;
delete r;
_client->close();
}
}
}
void AsyncWebServerRequest::_onError(int8_t error){
(void)error;
}
void AsyncWebServerRequest::_onTimeout(uint32_t time){
(void)time;
//os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
_client->close();
}
void AsyncWebServerRequest::onDisconnect (ArDisconnectHandler fn){
_onDisconnectfn=fn;
}
void AsyncWebServerRequest::_onDisconnect(){
//os_printf("d\n");
if(_onDisconnectfn) {
_onDisconnectfn();
}
_server->_handleDisconnect(this);
}
void AsyncWebServerRequest::_addParam(AsyncWebParameter *p){
_params.add(p);
}
void AsyncWebServerRequest::_addPathParam(const char *p){
_pathParams.emplace_back(p);
}
void AsyncWebServerRequest::_addGetParams(const String& params){
size_t start = 0;
while (start < params.length()){
int end = params.indexOf('&', start);
if (end < 0) end = params.length();
int equal = params.indexOf('=', start);
if (equal < 0 || equal > end) equal = end;
String name = params.substring(start, equal);
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value)));
start = end + 1;
}
}
bool AsyncWebServerRequest::_parseReqHead(){
// Split the head into method, url and version
int index = _temp.indexOf(' ');
String m = _temp.substring(0, index);
index = _temp.indexOf(' ', index+1);
String u = _temp.substring(m.length()+1, index);
_temp = _temp.substring(index+1);
if(m == F("GET")){
_method = HTTP_GET;
} else if(m == F("POST")){
_method = HTTP_POST;
} else if(m == F("DELETE")){
_method = HTTP_DELETE;
} else if(m == F("PUT")){
_method = HTTP_PUT;
} else if(m == F("PATCH")){
_method = HTTP_PATCH;
} else if(m == F("HEAD")){
_method = HTTP_HEAD;
} else if(m == F("OPTIONS")){
_method = HTTP_OPTIONS;
}
String g;
index = u.indexOf('?');
if(index > 0){
g = u.substring(index +1);
u = u.substring(0, index);
}
_url = urlDecode(u);
_addGetParams(g);
if(!_temp.startsWith(F("HTTP/1.0")))
_version = 1;
_temp = String();
return true;
}
bool strContains(const String &src, const String &find, bool mindcase = true) {
int pos=0, i=0;
const int slen = src.length();
const int flen = find.length();
if (slen < flen) return false;
while (pos <= (slen - flen)) {
for (i=0; i < flen; i++) {
if (mindcase) {
if (src[pos+i] != find[i]) i = flen + 1; // no match
}
else if (tolower(src[pos+i]) != tolower(find[i])) {
i = flen + 1; // no match
}
}
if (i == flen) return true;
pos++;
}
return false;
}
bool AsyncWebServerRequest::_parseReqHeader(){
int index = _temp.indexOf(':');
if(index){
String name = _temp.substring(0, index);
String value = _temp.substring(index + 2);
if(name.equalsIgnoreCase("Host")){
_host = value;
} else if(name.equalsIgnoreCase(F("Content-Type"))){
_contentType = value.substring(0, value.indexOf(';'));
if (value.startsWith(F("multipart/"))){
_boundary = value.substring(value.indexOf('=')+1);
_boundary.replace(String('"'), String());
_isMultipart = true;
}
} else if(name.equalsIgnoreCase(F("Content-Length"))){
_contentLength = atoi(value.c_str());
} else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){
_expectingContinue = true;
} else if(name.equalsIgnoreCase(F("Authorization"))){
if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){
_authorization = value.substring(6);
} else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){
_isDigest = true;
_authorization = value.substring(7);
}
} else {
if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){
// WebSocket request can be uniquely identified by header: [Upgrade: websocket]
_reqconntype = RCT_WS;
} else {
if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){
// WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
_reqconntype = RCT_EVENT;
}
}
}
_headers.emplace_back(name, value);
}
_temp = String();
return true;
}
void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){
if(data && (char)data != '&')
_temp += (char)data;
if(!data || (char)data == '&' || _parsedLength == _contentLength){
String name = F("body");
String value = _temp;
if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){
name = _temp.substring(0, _temp.indexOf('='));
value = _temp.substring(_temp.indexOf('=') + 1);
}
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true));
_temp = String();
}
}
void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){
_itemBuffer[_itemBufferIndex++] = data;
if(last || _itemBufferIndex == 1460){
//check if authenticated before calling the upload
if(_handler)
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
_itemBufferIndex = 0;
}
}
enum {
EXPECT_BOUNDARY,
PARSE_HEADERS,
WAIT_FOR_RETURN1,
EXPECT_FEED1,
EXPECT_DASH1,
EXPECT_DASH2,
BOUNDARY_OR_DATA,
DASH3_OR_RETURN2,
EXPECT_FEED2,
PARSING_FINISHED,
PARSE_ERROR
};
void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){
#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0)
if(!_parsedLength){
_multiParseState = EXPECT_BOUNDARY;
_temp = String();
_itemName = String();
_itemFilename = String();
_itemType = String();
}
if(_multiParseState == WAIT_FOR_RETURN1){
if(data != '\r'){
itemWriteByte(data);
} else {
_multiParseState = EXPECT_FEED1;
}
} else if(_multiParseState == EXPECT_BOUNDARY){
if(_parsedLength < 2 && data != '-'){
_multiParseState = PARSE_ERROR;
return;
} else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){
_multiParseState = PARSE_ERROR;
return;
} else if(_parsedLength - 2 == _boundary.length() && data != '\r'){
_multiParseState = PARSE_ERROR;
return;
} else if(_parsedLength - 3 == _boundary.length()){
if(data != '\n'){
_multiParseState = PARSE_ERROR;
return;
}
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
}
} else if(_multiParseState == PARSE_HEADERS){
if((char)data != '\r' && (char)data != '\n')
_temp += (char)data;
if((char)data == '\n'){
if(_temp.length()){
if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){
_itemType = _temp.substring(14);
_itemIsFile = true;
} else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){
_temp = _temp.substring(_temp.indexOf(';') + 2);
while(_temp.indexOf(';') > 0){
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
if(name == F("name")){
_itemName = nameVal;
} else if(name == F("filename")){
_itemFilename = nameVal;
_itemIsFile = true;
}
_temp = _temp.substring(_temp.indexOf(';') + 2);
}
String name = _temp.substring(0, _temp.indexOf('='));
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
if(name == F("name")){
_itemName = nameVal;
} else if(name == F("filename")){
_itemFilename = nameVal;
_itemIsFile = true;
}
}
_temp = String();
} else {
_multiParseState = WAIT_FOR_RETURN1;
//value starts from here
_itemSize = 0;
_itemStartIndex = _parsedLength;
_itemValue = String();
if(_itemIsFile){
if(_itemBuffer)
free(_itemBuffer);
_itemBuffer = (uint8_t*)malloc(1460);
if(_itemBuffer == NULL){
_multiParseState = PARSE_ERROR;
return;
}
_itemBufferIndex = 0;
}
}
}
} else if(_multiParseState == EXPECT_FEED1){
if(data != '\n'){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
} else {
_multiParseState = EXPECT_DASH1;
}
} else if(_multiParseState == EXPECT_DASH1){
if(data != '-'){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last);
} else {
_multiParseState = EXPECT_DASH2;
}
} else if(_multiParseState == EXPECT_DASH2){
if(data != '-'){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last);
} else {
_multiParseState = BOUNDARY_OR_DATA;
_boundaryPosition = 0;
}
} else if(_multiParseState == BOUNDARY_OR_DATA){
if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
uint8_t i;
for(i=0; i<_boundaryPosition; i++)
itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
} else if(_boundaryPosition == _boundary.length() - 1){
_multiParseState = DASH3_OR_RETURN2;
if(!_itemIsFile){
_addParam(new AsyncWebParameter(_itemName, _itemValue, true));
} else {
if(_itemSize){
//check if authenticated before calling the upload
if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
_itemBufferIndex = 0;
_addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize));
}
free(_itemBuffer);
_itemBuffer = NULL;
}
} else {
_boundaryPosition++;
}
} else if(_multiParseState == DASH3_OR_RETURN2){
if(data == '-' && (_contentLength - _parsedLength - 4) != 0){
//os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4);
_contentLength = _parsedLength + 4;//lets close the request gracefully
}
if(data == '\r'){
_multiParseState = EXPECT_FEED2;
} else if(data == '-' && _contentLength == (_parsedLength + 4)){
_multiParseState = PARSING_FINISHED;
} else {
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
_parseMultipartPostByte(data, last);
}
} else if(_multiParseState == EXPECT_FEED2){
if(data == '\n'){
_multiParseState = PARSE_HEADERS;
_itemIsFile = false;
} else {
_multiParseState = WAIT_FOR_RETURN1;
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
}
}
}
void AsyncWebServerRequest::_parseLine(){
if(_parseState == PARSE_REQ_START){
if(!_temp.length()){
_parseState = PARSE_REQ_FAIL;
_client->close();
} else {
_parseReqHead();
_parseState = PARSE_REQ_HEADERS;
}
return;
}
if(_parseState == PARSE_REQ_HEADERS){
if(!_temp.length()){
//end of headers
_server->_rewriteRequest(this);
_server->_attachHandler(this);
_removeNotInterestingHeaders();
if(_expectingContinue){
String response = F("HTTP/1.1 100 Continue\r\n\r\n");
_client->write(response.c_str(), response.length());
}
//check handler for authentication
if(_contentLength){
_parseState = PARSE_REQ_BODY;
} else {
_parseState = PARSE_REQ_END;
if(_handler) _handler->handleRequest(this);
else send(501);
}
} else _parseReqHeader();
}
}
size_t AsyncWebServerRequest::headers() const{
return _headers.size();
}
bool AsyncWebServerRequest::hasHeader(const String& name) const {
for(const auto& h: _headers){
if(h.name().equalsIgnoreCase(name)){
return true;
}
}
return false;
}
bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const {
return hasHeader(String(data));
}
AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) {
auto iter = std::find_if(std::begin(_headers), std::end(_headers),
[&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); });
if (iter == std::end(_headers))
return nullptr;
return &(*iter);
}
const AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers),
[&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); });
if (iter == std::end(_headers))
return nullptr;
return &(*iter);
}
AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) {
PGM_P p = reinterpret_cast<PGM_P>(data);
size_t n = strlen_P(p);
char * name = (char*) malloc(n+1);
if (name) {
strcpy_P(name, p);
AsyncWebHeader* result = getHeader( String(name));
free(name);
return result;
} else {
return nullptr;
}
}
const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const {
PGM_P p = reinterpret_cast<PGM_P>(data);
size_t n = strlen_P(p);
char * name = (char*) malloc(n+1);
if (name) {
strcpy_P(name, p);
const AsyncWebHeader* result = getHeader( String(name));
free(name);
return result;
} else {
return nullptr;
}
}
AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) {
if (num >= _headers.size())
return nullptr;
return &(*std::next(std::begin(_headers), num));
}
const AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
if (num >= _headers.size())
return nullptr;
return &(*std::next(std::begin(_headers), num));
}
size_t AsyncWebServerRequest::params() const {
return _params.length();
}
bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const {
for(const auto& p: _params){
if(p->name() == name && p->isPost() == post && p->isFile() == file){
return true;
}
}
return false;
}
bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const {
return hasParam(String(data).c_str(), post, file);
}
AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const {
for(const auto& p: _params){
if(p->name() == name && p->isPost() == post && p->isFile() == file){
return p;
}
}
return nullptr;
}
AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const {
return getParam(String(data).c_str(), post, file);
}
AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
auto param = _params.nth(num);
return param ? *param : nullptr;
}
void AsyncWebServerRequest::addInterestingHeader(const String& name){
if(std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders),
[&name](const String &str){ return str.equalsIgnoreCase(name); }))
_interestingHeaders.push_back(name);
}
void AsyncWebServerRequest::send(AsyncWebServerResponse *response){
_response = response;
if(_response == NULL){
_client->close(true);
_onDisconnect();
return;
}
if(!_response->_sourceValid()){
delete response;
_response = NULL;
send(500);
}
else {
_client->setRxTimeout(0);
_response->_respond(this);
}
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){
return new AsyncBasicResponse(code, contentType, content);
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
if(fs.exists(path) || (!download && fs.exists(path+F(".gz"))))
return new AsyncFileResponse(fs, path, contentType, download, callback);
return NULL;
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
if(content == true)
return new AsyncFileResponse(content, path, contentType, download, callback);
return NULL;
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
return new AsyncStreamResponse(stream, contentType, len, callback);
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
}
AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
if(_version)
return new AsyncChunkedResponse(contentType, callback, templateCallback);
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
}
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){
return new AsyncResponseStream(contentType, bufferSize);
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
return new AsyncProgmemResponse(code, contentType, content, len, callback);
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
}
void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
send(beginResponse(code, contentType, content));
}
void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){
send(beginResponse(fs, path, contentType, download, callback));
} else send(404);
}
void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
if(content == true){
send(beginResponse(content, path, contentType, download, callback));
} else send(404);
}
void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
send(beginResponse(stream, contentType, len, callback));
}
void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
send(beginResponse(contentType, len, callback, templateCallback));
}
void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
send(beginChunkedResponse(contentType, callback, templateCallback));
}
void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
send(beginResponse_P(code, contentType, content, len, callback));
}
void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
send(beginResponse_P(code, contentType, content, callback));
}
void AsyncWebServerRequest::redirect(const String& url){
AsyncWebServerResponse * response = beginResponse(302);
response->addHeader(F("Location"), url);
send(response);
}
bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){
if(_authorization.length()){
if(_isDigest)
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
else if(!passwordIsHash)
return checkBasicAuthentication(_authorization.c_str(), username, password);
else
return _authorization.equals(password);
}
return false;
}
bool AsyncWebServerRequest::authenticate(const char * hash){
if(!_authorization.length() || hash == NULL)
return false;
if(_isDigest){
String hStr = String(hash);
int separator = hStr.indexOf(':');
if(separator <= 0)
return false;
String username = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1);
separator = hStr.indexOf(':');
if(separator <= 0)
return false;
String realm = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1);
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
}
return (_authorization.equals(hash));
}
void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){
AsyncWebServerResponse * r = beginResponse(401);
if(!isDigest && realm == NULL){
r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\""));
} else if(!isDigest){
String header = F("Basic realm=\"");
header.concat(realm);
header += '"';
r->addHeader(F("WWW-Authenticate"), header);
} else {
String header = F("Digest ");
header.concat(requestDigestAuthentication(realm));
r->addHeader(F("WWW-Authenticate"), header);
}
send(r);
}
bool AsyncWebServerRequest::hasArg(const char* name) const {
for(const auto& arg: _params){
if(arg->name() == name){
return true;
}
}
return false;
}
bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const {
return hasArg(String(data).c_str());
}
const String& AsyncWebServerRequest::arg(const String& name) const {
for(const auto& arg: _params){
if(arg->name() == name){
return arg->value();
}
}
return emptyString;
}
const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const {
return arg(String(data).c_str());
}
const String& AsyncWebServerRequest::arg(size_t i) const {
return getParam(i)->value();
}
const String& AsyncWebServerRequest::argName(size_t i) const {
return getParam(i)->name();
}
const String& AsyncWebServerRequest::pathArg(size_t i) const {
return i < _pathParams.size() ? _pathParams[i] : emptyString;
}
const String& AsyncWebServerRequest::header(const char* name) const {
const AsyncWebHeader* h = getHeader(String(name));
return h ? h->value() : emptyString;
}
const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const {
return header(String(data).c_str());
};
const String& AsyncWebServerRequest::header(size_t i) const {
const AsyncWebHeader* h = getHeader(i);
return h ? h->value() : emptyString;
}
const String& AsyncWebServerRequest::headerName(size_t i) const {
const AsyncWebHeader* h = getHeader(i);
return h ? h->name() : emptyString;
}
String AsyncWebServerRequest::urlDecode(const String& text) const {
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;
String decoded = String();
decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
while (i < len){
char decodedChar;
char encodedChar = text.charAt(i++);
if ((encodedChar == '%') && (i + 1 < len)){
temp[2] = text.charAt(i++);
temp[3] = text.charAt(i++);
decodedChar = strtol(temp, NULL, 16);
} else if (encodedChar == '+') {
decodedChar = ' ';
} else {
decodedChar = encodedChar; // normal ascii char
}
decoded.concat(decodedChar);
}
return decoded;
}
const __FlashStringHelper *AsyncWebServerRequest::methodToString() const {
if(_method == HTTP_ANY) return F("ANY");
else if(_method & HTTP_GET) return F("GET");
else if(_method & HTTP_POST) return F("POST");
else if(_method & HTTP_DELETE) return F("DELETE");
else if(_method & HTTP_PUT) return F("PUT");
else if(_method & HTTP_PATCH) return F("PATCH");
else if(_method & HTTP_HEAD) return F("HEAD");
else if(_method & HTTP_OPTIONS) return F("OPTIONS");
return F("UNKNOWN");
}
const __FlashStringHelper *AsyncWebServerRequest::requestedConnTypeToString() const {
switch (_reqconntype) {
case RCT_NOT_USED: return F("RCT_NOT_USED");
case RCT_DEFAULT: return F("RCT_DEFAULT");
case RCT_HTTP: return F("RCT_HTTP");
case RCT_WS: return F("RCT_WS");
case RCT_EVENT: return F("RCT_EVENT");
default: return F("ERROR");
}
}
bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) {
bool res = false;
if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true;
if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true;
if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true;
return res;
}

View File

@@ -0,0 +1,138 @@
/*
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 ASYNCWEBSERVERRESPONSEIMPL_H_
#define ASYNCWEBSERVERRESPONSEIMPL_H_
#ifdef Arduino_h
// arduino is not compatible with std::vector
#undef min
#undef max
#endif
#include <vector>
#include <memory>
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
class AsyncBasicResponse: public AsyncWebServerResponse {
private:
String _content;
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);
bool _sourceValid() const { return true; }
};
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,
// 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);
bool _sourceValid() const { return false; }
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
};
#ifndef TEMPLATE_PLACEHOLDER
#define TEMPLATE_PLACEHOLDER '%'
#endif
#define TEMPLATE_PARAM_NAME_LENGTH 32
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();
bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
};
class AsyncStreamResponse: public AsyncAbstractResponse {
private:
Stream *_content;
public:
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;
};
class AsyncCallbackResponse: public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
size_t _filledLength;
public:
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;
};
class AsyncChunkedResponse: public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
size_t _filledLength;
public:
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;
};
class AsyncProgmemResponse: public AsyncAbstractResponse {
private:
const uint8_t * _content;
size_t _readLength;
public:
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
bool _sourceValid() const { return true; }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
};
class cbuf;
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);
size_t write(uint8_t data);
using Print::write;
};
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */

View File

@@ -0,0 +1,704 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebResponseImpl.h"
#include "cbuf.h"
// Since ESP8266 does not link memchr by default, here's its implementation.
void* memchr(void* ptr, int ch, size_t count)
{
unsigned char* p = static_cast<unsigned char*>(ptr);
while(count--)
if(*p++ == static_cast<unsigned char>(ch))
return --p;
return nullptr;
}
/*
* Abstract Response
* */
const char* AsyncWebServerResponse::_responseCodeToString(int code) {
return reinterpret_cast<const char *>(responseCodeToString(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("");
}
}
AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0)
, _contentType()
, _contentLength(0)
, _sendContentLength(true)
, _chunked(false)
, _headLength(0)
, _sentLength(0)
, _ackedLength(0)
, _writtenLength(0)
, _state(RESPONSE_SETUP)
{
for(const auto &header: DefaultHeaders::Instance()) {
_headers.emplace_back(header);
}
}
AsyncWebServerResponse::~AsyncWebServerResponse() = default;
void AsyncWebServerResponse::setCode(int code){
if(_state == RESPONSE_SETUP)
_code = code;
}
void AsyncWebServerResponse::setContentLength(size_t len){
if(_state == RESPONSE_SETUP)
_contentLength = len;
}
void AsyncWebServerResponse::setContentType(const String& type){
if(_state == RESPONSE_SETUP)
_contentType = type;
}
void AsyncWebServerResponse::addHeader(const String& name, const String& value){
_headers.emplace_back(name, value);
}
String AsyncWebServerResponse::_assembleHead(uint8_t version){
if(version){
addHeader(F("Accept-Ranges"), F("none"));
if(_chunked)
addHeader(F("Transfer-Encoding"), F("chunked"));
}
String out = String();
int bufSize = 300;
char buf[bufSize];
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code));
out.concat(buf);
if(_sendContentLength) {
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
out.concat(buf);
}
if(_contentType.length()) {
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
out.concat(buf);
}
for(const auto& header: _headers){
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
out.concat(buf);
}
_headers.clear();
out.concat(F("\r\n"));
_headLength = out.length();
return out;
}
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
bool AsyncWebServerResponse::_sourceValid() const { return false; }
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); }
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; }
/*
* String/Code Response
* */
AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){
_code = code;
_content = content;
_contentType = contentType;
if(_content.length()){
_contentLength = _content.length();
if(!_contentType.length())
_contentType = F("text/plain");
}
addHeader(F("Connection"), F("close"));
}
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
_state = RESPONSE_HEADERS;
String out = _assembleHead(request->version());
size_t outLen = out.length();
size_t space = request->client()->space();
if(!_contentLength && space >= outLen){
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK;
} else if(_contentLength && space >= outLen + _contentLength){
out += _content;
outLen += _contentLength;
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK;
} else if(space && space < outLen){
String partial = out.substring(0, space);
_content = out.substring(space) + _content;
_contentLength += outLen - space;
_writtenLength += request->client()->write(partial.c_str(), partial.length());
_state = RESPONSE_CONTENT;
} else if(space > outLen && space < (outLen + _contentLength)){
size_t shift = space - outLen;
outLen += shift;
_sentLength += shift;
out += _content.substring(0, shift);
_content = _content.substring(shift);
_writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_CONTENT;
} else {
_content = out + _content;
_contentLength += outLen;
_state = RESPONSE_CONTENT;
}
}
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
(void)time;
_ackedLength += len;
if(_state == RESPONSE_CONTENT){
size_t available = _contentLength - _sentLength;
size_t space = request->client()->space();
//we can fit in this packet
if(space > available){
_writtenLength += request->client()->write(_content.c_str(), available);
_content = String();
_state = RESPONSE_WAIT_ACK;
return available;
}
//send some data, the rest on ack
String out = _content.substring(0, space);
_content = _content.substring(space);
_sentLength += space;
_writtenLength += request->client()->write(out.c_str(), space);
return space;
} else if(_state == RESPONSE_WAIT_ACK){
if(_ackedLength >= _writtenLength){
_state = RESPONSE_END;
}
}
return 0;
}
/*
* Abstract Response
* */
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback)
{
// In case of template processing, we're unable to determine real response size
if(callback) {
_contentLength = 0;
_sendContentLength = false;
_chunked = true;
}
}
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
addHeader(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){
(void)time;
if(!_sourceValid()){
_state = RESPONSE_FAILED;
request->client()->close();
return 0;
}
_ackedLength += len;
size_t space = request->client()->space();
size_t headLen = _head.length();
if(_state == RESPONSE_HEADERS){
if(space >= headLen){
_state = RESPONSE_CONTENT;
space -= headLen;
} else {
String out = _head.substring(0, space);
_head = _head.substring(space);
_writtenLength += request->client()->write(out.c_str(), out.length());
return out.length();
}
}
if(_state == RESPONSE_CONTENT){
size_t outLen;
if(_chunked){
if(space <= 8){
return 0;
}
outLen = space;
} else if(!_sendContentLength){
outLen = space;
} else {
outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength);
}
uint8_t *buf = (uint8_t *)malloc(outLen+headLen);
if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen);
return 0;
}
if(headLen){
memcpy(buf, _head.c_str(), _head.length());
}
size_t readLen = 0;
if(_chunked){
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
// See RFC2616 sections 2, 3.6.1.
readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8);
if(readLen == RESPONSE_TRY_AGAIN){
free(buf);
return 0;
}
outLen = sprintf_P((char*)buf+headLen, PSTR("%x"), readLen) + headLen;
while(outLen < headLen + 4) buf[outLen++] = ' ';
buf[outLen++] = '\r';
buf[outLen++] = '\n';
outLen += readLen;
buf[outLen++] = '\r';
buf[outLen++] = '\n';
} else {
readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen);
if(readLen == RESPONSE_TRY_AGAIN){
free(buf);
return 0;
}
outLen = readLen + headLen;
}
if(headLen){
_head = String();
}
if(outLen){
_writtenLength += request->client()->write((const char*)buf, outLen);
}
if(_chunked){
_sentLength += readLen;
} else {
_sentLength += outLen - headLen;
}
free(buf);
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){
_state = RESPONSE_WAIT_ACK;
}
return outLen;
} else if(_state == RESPONSE_WAIT_ACK){
if(!_sendContentLength || _ackedLength >= _writtenLength){
_state = RESPONSE_END;
if(!_chunked && !_sendContentLength)
request->client()->close(true);
}
}
return 0;
}
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len)
{
// If we have something in cache, copy it to buffer
const size_t readFromCache = std::min(len, _cache.size());
if(readFromCache) {
memcpy(data, _cache.data(), readFromCache);
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
}
// If we need to read more...
const size_t needFromFile = len - readFromCache;
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
return readFromCache + readFromContent;
}
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len)
{
if(!_callback)
return _fillBuffer(data, len);
const size_t originalLen = len;
len = _readDataFromCacheOrContent(data, len);
// Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders
uint8_t* pTemplateStart = data;
while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
// temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName;
// If closing placeholder is found:
if(pTemplateEnd) {
// prepare argument to callback
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
if(paramNameLength) {
memcpy(buf, pTemplateStart + 1, paramNameLength);
buf[paramNameLength] = 0;
paramName = String(reinterpret_cast<char*>(buf));
} else { // double percent sign encountered, this is single percent sign escaped.
// remove the 2nd percent sign
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
++pTemplateStart;
}
} else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
if(readFromCacheOrContent) {
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
if(pTemplateEnd) {
// prepare argument to callback
*pTemplateEnd = 0;
paramName = String(reinterpret_cast<char*>(buf));
// Copy remaining read-ahead data into cache
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
pTemplateEnd = &data[len - 1];
}
else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
{
// but first, store read file data in cache
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
++pTemplateStart;
}
}
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
}
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
if(paramName.length()) {
// call callback and replace with result.
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
// Data after pTemplateEnd may need to be moved.
// The first byte of data after placeholder is located at pTemplateEnd + 1.
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
const String paramValue(_callback(paramName));
const char* pvstr = paramValue.c_str();
const unsigned int pvlen = paramValue.length();
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
// make room for param value
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
} else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
//2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
// Move the entire data after the placeholder
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
// 3. replace placeholder with actual value
memcpy(pTemplateStart, pvstr, numBytesCopied);
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
if(numBytesCopied < pvlen) {
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
} else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
// there is some free room, fill it from cache
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
const size_t totalFreeRoom = originalLen - len + roomFreed;
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
} else { // result is copied fully; it is longer than placeholder text
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
len = std::min(len + roomTaken, originalLen);
}
}
} // while(pTemplateStart)
return len;
}
/*
* File Response
* */
AsyncFileResponse::~AsyncFileResponse(){
if(_content)
_content.close();
}
void AsyncFileResponse::_setContentType(const String& path){
#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION
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");
#endif
}
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"))){
_path = _path + F(".gz");
addHeader(F("Content-Encoding"), F("gzip"));
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
}
_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();
if(contentType.length() == 0)
_setContentType(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
// set filename and force download
snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename);
} else {
// set filename and force rendering
snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename);
}
addHeader(F("Content-Disposition"), buf);
}
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"))){
addHeader(F("Content-Encoding"), F("gzip"));
_callback = nullptr; // Unable to process gzipped templates
_sendContentLength = true;
_chunked = false;
}
_content = content;
_contentLength = _content.size();
if(contentType.length() == 0)
_setContentType(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename);
} else {
snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename);
}
addHeader(F("Content-Disposition"), buf);
}
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
return _content.read(data, len);
}
/*
* Stream Response
* */
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 available = _content->available();
size_t outLen = (available > len)?len:available;
size_t i;
for(i=0;i<outLen;i++)
data[i] = _content->read();
return outLen;
}
/*
* Callback Response
* */
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) {
_code = 200;
_content = callback;
_contentLength = len;
if(!len)
_sendContentLength = false;
_contentType = contentType;
_filledLength = 0;
}
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
size_t ret = _content(data, len, _filledLength);
if(ret != RESPONSE_TRY_AGAIN){
_filledLength += ret;
}
return ret;
}
/*
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
_code = 200;
_content = callback;
_contentLength = 0;
_contentType = contentType;
_sendContentLength = false;
_chunked = true;
_filledLength = 0;
}
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
size_t ret = _content(data, len, _filledLength);
if(ret != RESPONSE_TRY_AGAIN){
_filledLength += ret;
}
return ret;
}
/*
* Progmem Response
* */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
_code = code;
_content = content;
_contentType = contentType;
_contentLength = len;
_readLength = 0;
}
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
size_t left = _contentLength - _readLength;
if (left > len) {
memcpy_P(data, _content + _readLength, len);
_readLength += len;
return len;
}
memcpy_P(data, _content + _readLength, left);
_readLength += left;
return left;
}
/*
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
* */
AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize)
{
_code = 200;
_contentLength = 0;
_contentType = contentType;
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); //std::make_unique<cbuf>(bufferSize);
}
AsyncResponseStream::~AsyncResponseStream() = default;
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){
return _content->read((char*)buf, maxLen);
}
size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
if(_started())
return 0;
if(len > _content->room()){
size_t needed = len - _content->room();
_content->resizeAdd(needed);
}
size_t written = _content->write((const char*)data, len);
_contentLength += written;
return written;
}
size_t AsyncResponseStream::write(uint8_t data){
return write(&data, 1);
}

View File

@@ -0,0 +1,198 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
return WiFi.localIP() == request->client()->localIP();
}
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";
#endif
AsyncWebServer::AsyncWebServer(uint16_t port)
: _server(port)
, _rewrites(LinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; }))
, _handlers(LinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; }))
{
_catchAllHandler = new AsyncCallbackWebHandler();
if(_catchAllHandler == NULL)
return;
_server.onClient([](void *s, AsyncClient* c){
if(c == NULL)
return;
c->setRxTimeout(3);
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
if(r == NULL){
c->close(true);
c->free();
delete c;
}
}, this);
}
AsyncWebServer::~AsyncWebServer(){
reset();
end();
if(_catchAllHandler) delete _catchAllHandler;
}
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){
_rewrites.add(rewrite);
return *rewrite;
}
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){
return _rewrites.remove(rewrite);
}
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
return addRewrite(new AsyncWebRewrite(from, to));
}
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
_handlers.add(handler);
return *handler;
}
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){
return _handlers.remove(handler);
}
void AsyncWebServer::begin(){
_server.setNoDelay(true);
_server.begin();
}
void AsyncWebServer::end(){
_server.end();
}
#if ASYNC_TCP_SSL_ENABLED
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){
_server.onSslFileRequest(cb, arg);
}
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){
_server.beginSecure(cert, key, password);
}
#endif
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){
delete 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);
return;
}
}
request->addInterestingHeader(F("ANY"));
request->setHandler(_catchAllHandler);
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
handler->onRequest(onRequest);
handler->onUpload(onUpload);
handler->onBody(onBody);
addHandler(handler);
return *handler;
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
handler->onRequest(onRequest);
handler->onUpload(onUpload);
addHandler(handler);
return *handler;
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
handler->onRequest(onRequest);
addHandler(handler);
return *handler;
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->onRequest(onRequest);
addHandler(handler);
return *handler;
}
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){
_catchAllHandler->onRequest(fn);
}
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){
_catchAllHandler->onUpload(fn);
}
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){
_catchAllHandler->onBody(fn);
}
void AsyncWebServer::reset(){
_rewrites.free();
_handlers.free();
if (_catchAllHandler != NULL){
_catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL);
}
}