928 lines
37 KiB
C++
928 lines
37 KiB
C++
/*
|
|
Asynchronous WebServer library for Espressif MCUs
|
|
|
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
This file is part of the esp8266 core for Arduino environment.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
#ifndef _ESPAsyncWebServer_H_
|
|
#define _ESPAsyncWebServer_H_
|
|
|
|
#include "Arduino.h"
|
|
|
|
#include "FS.h"
|
|
#include <algorithm>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <list>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#ifdef ESP32
|
|
#include <AsyncTCP.h>
|
|
#include <WiFi.h>
|
|
#elif defined(ESP8266)
|
|
#include <ESP8266WiFi.h>
|
|
#include <ESPAsyncTCP.h>
|
|
#elif defined(TARGET_RP2040)
|
|
#include <AsyncTCP_RP2040W.h>
|
|
#include <HTTP_Method.h>
|
|
#include <WiFi.h>
|
|
#include <http_parser.h>
|
|
#else
|
|
#error Platform not supported
|
|
#endif
|
|
|
|
#include "literals.h"
|
|
|
|
#define ASYNCWEBSERVER_VERSION "3.4.5"
|
|
#define ASYNCWEBSERVER_VERSION_MAJOR 3
|
|
#define ASYNCWEBSERVER_VERSION_MINOR 4
|
|
#define ASYNCWEBSERVER_VERSION_REVISION 5
|
|
#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;
|
|
class AsyncMiddlewareChain;
|
|
|
|
#if defined(TARGET_RP2040)
|
|
typedef enum http_method WebRequestMethod;
|
|
#else
|
|
#ifndef WEBSERVER_H
|
|
typedef enum {
|
|
HTTP_GET = 0b00000001,
|
|
HTTP_POST = 0b00000010,
|
|
HTTP_DELETE = 0b00000100,
|
|
HTTP_PUT = 0b00001000,
|
|
HTTP_PATCH = 0b00010000,
|
|
HTTP_HEAD = 0b00100000,
|
|
HTTP_OPTIONS = 0b01000000,
|
|
HTTP_ANY = 0b01111111,
|
|
} WebRequestMethod;
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef HAVE_FS_FILE_OPEN_MODE
|
|
namespace fs {
|
|
class FileOpenMode {
|
|
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
|
|
#define RESPONSE_STREAM_BUFFER_SIZE 1460
|
|
|
|
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(const AsyncWebHeader&) = default;
|
|
AsyncWebHeader(const char* name, const char* value) : _name(name), _value(value) {}
|
|
AsyncWebHeader(const String& name, const String& value) : _name(name), _value(value) {}
|
|
AsyncWebHeader(const String& data);
|
|
|
|
AsyncWebHeader& operator=(const AsyncWebHeader&) = default;
|
|
|
|
const String& name() const { return _name; }
|
|
const String& value() const { return _value; }
|
|
String toString() const;
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
|
|
// this enum is similar to Arduino WebServer's AsyncAuthType and PsychicHttp
|
|
typedef enum {
|
|
AUTH_NONE = 0, // always allow
|
|
AUTH_BASIC = 1,
|
|
AUTH_DIGEST = 2,
|
|
AUTH_BEARER = 3,
|
|
AUTH_OTHER = 4,
|
|
AUTH_DENIED = 255, // always returns 401
|
|
} AsyncAuthType;
|
|
|
|
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;
|
|
ArDisconnectHandler _onDisconnectfn;
|
|
|
|
// response is sent
|
|
bool _sent = false;
|
|
|
|
String _temp;
|
|
uint8_t _parseState;
|
|
|
|
uint8_t _version;
|
|
WebRequestMethodComposite _method;
|
|
String _url;
|
|
String _host;
|
|
String _contentType;
|
|
String _boundary;
|
|
String _authorization;
|
|
RequestedConnectionType _reqconntype;
|
|
AsyncAuthType _authMethod = AsyncAuthType::AUTH_NONE;
|
|
bool _isMultipart;
|
|
bool _isPlainPost;
|
|
bool _expectingContinue;
|
|
size_t _contentLength;
|
|
size_t _parsedLength;
|
|
|
|
std::list<AsyncWebHeader> _headers;
|
|
std::list<AsyncWebParameter> _params;
|
|
std::vector<String> _pathParams;
|
|
|
|
std::unordered_map<const char*, String, std::hash<const char*>, std::equal_to<const char*>> _attributes;
|
|
|
|
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 _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 char* methodToString() const;
|
|
const char* requestedConnTypeToString() const;
|
|
|
|
RequestedConnectionType requestedConnType() const { return _reqconntype; }
|
|
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED) const;
|
|
bool isWebSocketUpgrade() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS); }
|
|
bool isSSE() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); }
|
|
bool isHTTP() const { return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP); }
|
|
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) const;
|
|
bool authenticate(const char* username, const char* credentials, const char* realm = NULL, bool isHash = false) const;
|
|
void requestAuthentication(const char* realm = nullptr, bool isDigest = true) { requestAuthentication(isDigest ? AsyncAuthType::AUTH_DIGEST : AsyncAuthType::AUTH_BASIC, realm); }
|
|
void requestAuthentication(AsyncAuthType method, const char* realm = nullptr, const char* _authFailMsg = nullptr);
|
|
|
|
void setHandler(AsyncWebHandler* handler) { _handler = handler; }
|
|
|
|
#ifndef ESP8266
|
|
[[deprecated("All headers are now collected. Use removeHeader(name) or HeaderFreeMiddleware if you really need to free some headers.")]]
|
|
#endif
|
|
void addInterestingHeader(__unused const char* name) {
|
|
}
|
|
#ifndef ESP8266
|
|
[[deprecated("All headers are now collected. Use removeHeader(name) or HeaderFreeMiddleware if you really need to free some headers.")]]
|
|
#endif
|
|
void addInterestingHeader(__unused const String& name) {
|
|
}
|
|
|
|
/**
|
|
* @brief issue HTTP redirect responce with Location header
|
|
*
|
|
* @param url - url to redirect to
|
|
* @param code - responce code, default is 302 : temporary redirect
|
|
*/
|
|
void redirect(const char* url, int code = 302);
|
|
void redirect(const String& url, int code = 302) { return redirect(url.c_str(), code); };
|
|
|
|
void send(AsyncWebServerResponse* response);
|
|
AsyncWebServerResponse* getResponse() const { return _response; }
|
|
|
|
void send(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); }
|
|
void send(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content, callback)); }
|
|
void send(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content.c_str(), callback)); }
|
|
|
|
void send(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); }
|
|
void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); }
|
|
|
|
void send(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
|
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
|
|
send(beginResponse(fs, path, contentType, download, callback));
|
|
} else
|
|
send(404);
|
|
}
|
|
void send(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(fs, path, contentType.c_str(), download, callback); }
|
|
|
|
void send(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
|
if (content) {
|
|
send(beginResponse(content, path, contentType, download, callback));
|
|
} else
|
|
send(404);
|
|
}
|
|
void send(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(content, path, contentType.c_str(), download, callback); }
|
|
|
|
void send(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); }
|
|
void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); }
|
|
|
|
void send(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); }
|
|
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); }
|
|
|
|
void sendChunked(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); }
|
|
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); }
|
|
|
|
#ifndef ESP8266
|
|
[[deprecated("Replaced by send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
|
|
#endif
|
|
void send_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
|
|
send(code, contentType, content, len, callback);
|
|
}
|
|
#ifndef ESP8266
|
|
[[deprecated("Replaced by send(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)")]]
|
|
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
|
|
send(code, contentType, content, callback);
|
|
}
|
|
#else
|
|
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
|
|
send(beginResponse_P(code, contentType, content, callback));
|
|
}
|
|
#endif
|
|
|
|
AsyncWebServerResponse* beginResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr);
|
|
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, callback); }
|
|
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content.c_str(), callback); }
|
|
|
|
AsyncWebServerResponse* beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
|
|
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, len, callback); }
|
|
|
|
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
|
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(fs, path, contentType.c_str(), download, callback); }
|
|
|
|
AsyncWebServerResponse* beginResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
|
AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(content, path, contentType.c_str(), download, callback); }
|
|
|
|
AsyncWebServerResponse* beginResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
|
AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(stream, contentType.c_str(), len, callback); }
|
|
|
|
AsyncWebServerResponse* beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
|
AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { return beginResponse(contentType.c_str(), len, callback, templateCallback); }
|
|
|
|
AsyncWebServerResponse* beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
|
AsyncWebServerResponse* beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
|
|
|
AsyncResponseStream* beginResponseStream(const char* contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
|
|
AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) { return beginResponseStream(contentType.c_str(), bufferSize); }
|
|
|
|
#ifndef ESP8266
|
|
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
|
|
#endif
|
|
AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
|
|
return beginResponse(code, contentType.c_str(), content, len, callback);
|
|
}
|
|
#ifndef ESP8266
|
|
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)")]]
|
|
#endif
|
|
AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
|
|
|
|
/**
|
|
* @brief Get the Request parameter by name
|
|
*
|
|
* @param name
|
|
* @param post
|
|
* @param file
|
|
* @return const AsyncWebParameter*
|
|
*/
|
|
const AsyncWebParameter* getParam(const char* name, bool post = false, bool file = false) const;
|
|
|
|
const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); };
|
|
#ifdef ESP8266
|
|
const AsyncWebParameter* getParam(const __FlashStringHelper* data, bool post, bool file) const;
|
|
#endif
|
|
|
|
/**
|
|
* @brief Get request parameter by number
|
|
* i.e., n-th parameter
|
|
* @param num
|
|
* @return const AsyncWebParameter*
|
|
*/
|
|
const AsyncWebParameter* getParam(size_t num) const;
|
|
|
|
size_t args() const { return params(); } // get arguments count
|
|
|
|
// get request argument value by name
|
|
const String& arg(const char* name) const;
|
|
// get request argument value by name
|
|
const String& arg(const String& name) const { return arg(name.c_str()); };
|
|
#ifdef ESP8266
|
|
const String& arg(const __FlashStringHelper* data) const; // get request argument value by F(name)
|
|
#endif
|
|
const String& arg(size_t i) const; // get request argument value by number
|
|
const String& argName(size_t i) const; // get request argument name by number
|
|
bool hasArg(const char* name) const; // check if argument exists
|
|
bool hasArg(const String& name) const { return hasArg(name.c_str()); };
|
|
#ifdef ESP8266
|
|
bool hasArg(const __FlashStringHelper* data) const; // check if F(argument) exists
|
|
#endif
|
|
|
|
const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
|
|
|
// get request header value by name
|
|
const String& header(const char* name) const;
|
|
const String& header(const String& name) const { return header(name.c_str()); };
|
|
|
|
#ifdef ESP8266
|
|
const String& header(const __FlashStringHelper* data) const; // get request header value by F(name)
|
|
#endif
|
|
|
|
const String& header(size_t i) const; // get request header value by number
|
|
const String& headerName(size_t i) const; // get request header name by number
|
|
|
|
size_t headers() const; // get header count
|
|
|
|
// check if header exists
|
|
bool hasHeader(const char* name) const;
|
|
bool hasHeader(const String& name) const { return hasHeader(name.c_str()); };
|
|
#ifdef ESP8266
|
|
bool hasHeader(const __FlashStringHelper* data) const; // check if header exists
|
|
#endif
|
|
|
|
const AsyncWebHeader* getHeader(const char* name) const;
|
|
const AsyncWebHeader* getHeader(const String& name) const { return getHeader(name.c_str()); };
|
|
#ifdef ESP8266
|
|
const AsyncWebHeader* getHeader(const __FlashStringHelper* data) const;
|
|
#endif
|
|
|
|
const AsyncWebHeader* getHeader(size_t num) const;
|
|
|
|
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; }
|
|
|
|
size_t getHeaderNames(std::vector<const char*>& names) const;
|
|
|
|
// Remove a header from the request.
|
|
// It will free the memory and prevent the header to be seen during request processing.
|
|
bool removeHeader(const char* name);
|
|
// Remove all request headers.
|
|
void removeHeaders() { _headers.clear(); }
|
|
|
|
size_t params() const; // get arguments count
|
|
bool hasParam(const char* name, bool post = false, bool file = false) const;
|
|
bool hasParam(const String& name, bool post = false, bool file = false) const { return hasParam(name.c_str(), post, file); };
|
|
#ifdef ESP8266
|
|
bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const { return hasParam(String(data).c_str(), post, file); };
|
|
#endif
|
|
|
|
// REQUEST ATTRIBUTES
|
|
|
|
void setAttribute(const char* name, const char* value) { _attributes[name] = value; }
|
|
void setAttribute(const char* name, bool value) { _attributes[name] = value ? "1" : emptyString; }
|
|
void setAttribute(const char* name, long value) { _attributes[name] = String(value); }
|
|
void setAttribute(const char* name, float value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); }
|
|
void setAttribute(const char* name, double value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); }
|
|
|
|
bool hasAttribute(const char* name) const { return _attributes.find(name) != _attributes.end(); }
|
|
|
|
const String& getAttribute(const char* name, const String& defaultValue = emptyString) const;
|
|
bool getAttribute(const char* name, bool defaultValue) const;
|
|
long getAttribute(const char* name, long defaultValue) const;
|
|
float getAttribute(const char* name, float defaultValue) const;
|
|
double getAttribute(const char* name, double defaultValue) const;
|
|
|
|
String urlDecode(const String& text) const;
|
|
};
|
|
|
|
/*
|
|
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
|
|
* */
|
|
|
|
using ArRequestFilterFunction = std::function<bool(AsyncWebServerRequest* request)>;
|
|
|
|
bool ON_STA_FILTER(AsyncWebServerRequest* request);
|
|
|
|
bool ON_AP_FILTER(AsyncWebServerRequest* request);
|
|
|
|
/*
|
|
* MIDDLEWARE :: Request interceptor, assigned to a AsyncWebHandler (or the server), which can be used:
|
|
* 1. to run some code before the final handler is executed (e.g. check authentication)
|
|
* 2. decide whether to proceed or not with the next handler
|
|
* */
|
|
|
|
using ArMiddlewareNext = std::function<void(void)>;
|
|
using ArMiddlewareCallback = std::function<void(AsyncWebServerRequest* request, ArMiddlewareNext next)>;
|
|
|
|
// Middleware is a base class for all middleware
|
|
class AsyncMiddleware {
|
|
public:
|
|
virtual ~AsyncMiddleware() {}
|
|
virtual void run(__unused AsyncWebServerRequest* request, __unused ArMiddlewareNext next) { return next(); };
|
|
|
|
private:
|
|
friend class AsyncWebHandler;
|
|
friend class AsyncEventSource;
|
|
friend class AsyncMiddlewareChain;
|
|
bool _freeOnRemoval = false;
|
|
};
|
|
|
|
// Create a custom middleware by providing an anonymous callback function
|
|
class AsyncMiddlewareFunction : public AsyncMiddleware {
|
|
public:
|
|
AsyncMiddlewareFunction(ArMiddlewareCallback fn) : _fn(fn) {}
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) override { return _fn(request, next); };
|
|
|
|
private:
|
|
ArMiddlewareCallback _fn;
|
|
};
|
|
|
|
// For internal use only: super class to add/remove middleware to server or handlers
|
|
class AsyncMiddlewareChain {
|
|
public:
|
|
~AsyncMiddlewareChain();
|
|
|
|
void addMiddleware(ArMiddlewareCallback fn);
|
|
void addMiddleware(AsyncMiddleware* middleware);
|
|
void addMiddlewares(std::vector<AsyncMiddleware*> middlewares);
|
|
bool removeMiddleware(AsyncMiddleware* middleware);
|
|
|
|
// For internal use only
|
|
void _runChain(AsyncWebServerRequest* request, ArMiddlewareNext finalizer);
|
|
|
|
protected:
|
|
std::list<AsyncMiddleware*> _middlewares;
|
|
};
|
|
|
|
// AuthenticationMiddleware is a middleware that checks if the request is authenticated
|
|
class AuthenticationMiddleware : public AsyncMiddleware {
|
|
public:
|
|
void setUsername(const char* username);
|
|
void setPassword(const char* password);
|
|
void setPasswordHash(const char* hash);
|
|
|
|
void setRealm(const char* realm) { _realm = realm; }
|
|
void setAuthFailureMessage(const char* message) { _authFailMsg = message; }
|
|
|
|
// set the authentication method to use
|
|
// default is AUTH_NONE: no authentication required
|
|
// AUTH_BASIC: basic authentication
|
|
// AUTH_DIGEST: digest authentication
|
|
// AUTH_BEARER: bearer token authentication
|
|
// AUTH_OTHER: other authentication method
|
|
// AUTH_DENIED: always return 401 Unauthorized
|
|
// if a method is set but no username or password is set, authentication will be ignored
|
|
void setAuthType(AsyncAuthType authMethod) { _authMethod = authMethod; }
|
|
|
|
// precompute and store the hash value based on the username, password, realm.
|
|
// can be used for DIGEST and BASIC to avoid recomputing the hash for each request.
|
|
// returns true if the hash was successfully generated and replaced
|
|
bool generateHash();
|
|
|
|
// returns true if the username and password (or hash) are set
|
|
bool hasCredentials() const { return _hasCreds; }
|
|
|
|
bool allowed(AsyncWebServerRequest* request) const;
|
|
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
|
|
|
private:
|
|
String _username;
|
|
String _credentials;
|
|
bool _hash = false;
|
|
|
|
String _realm = asyncsrv::T_LOGIN_REQ;
|
|
AsyncAuthType _authMethod = AsyncAuthType::AUTH_NONE;
|
|
String _authFailMsg;
|
|
bool _hasCreds = false;
|
|
};
|
|
|
|
using ArAuthorizeFunction = std::function<bool(AsyncWebServerRequest* request)>;
|
|
// AuthorizationMiddleware is a middleware that checks if the request is authorized
|
|
class AuthorizationMiddleware : public AsyncMiddleware {
|
|
public:
|
|
AuthorizationMiddleware(ArAuthorizeFunction authorizeConnectHandler) : _code(403), _authz(authorizeConnectHandler) {}
|
|
AuthorizationMiddleware(int code, ArAuthorizeFunction authorizeConnectHandler) : _code(code), _authz(authorizeConnectHandler) {}
|
|
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) { return _authz && !_authz(request) ? request->send(_code) : next(); }
|
|
|
|
private:
|
|
int _code;
|
|
ArAuthorizeFunction _authz;
|
|
};
|
|
|
|
// remove all headers from the incoming request except the ones provided in the constructor
|
|
class HeaderFreeMiddleware : public AsyncMiddleware {
|
|
public:
|
|
void keep(const char* name) { _toKeep.push_back(name); }
|
|
void unKeep(const char* name) { _toKeep.erase(std::remove(_toKeep.begin(), _toKeep.end(), name), _toKeep.end()); }
|
|
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
|
|
|
private:
|
|
std::vector<const char*> _toKeep;
|
|
};
|
|
|
|
// filter out specific headers from the incoming request
|
|
class HeaderFilterMiddleware : public AsyncMiddleware {
|
|
public:
|
|
void filter(const char* name) { _toRemove.push_back(name); }
|
|
void unFilter(const char* name) { _toRemove.erase(std::remove(_toRemove.begin(), _toRemove.end(), name), _toRemove.end()); }
|
|
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
|
|
|
private:
|
|
std::vector<const char*> _toRemove;
|
|
};
|
|
|
|
// curl-like logging of incoming requests
|
|
class LoggingMiddleware : public AsyncMiddleware {
|
|
public:
|
|
void setOutput(Print& output) { _out = &output; }
|
|
void setEnabled(bool enabled) { _enabled = enabled; }
|
|
bool isEnabled() const { return _enabled && _out; }
|
|
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
|
|
|
private:
|
|
Print* _out = nullptr;
|
|
bool _enabled = true;
|
|
};
|
|
|
|
// CORS Middleware
|
|
class CorsMiddleware : public AsyncMiddleware {
|
|
public:
|
|
void setOrigin(const char* origin) { _origin = origin; }
|
|
void setMethods(const char* methods) { _methods = methods; }
|
|
void setHeaders(const char* headers) { _headers = headers; }
|
|
void setAllowCredentials(bool credentials) { _credentials = credentials; }
|
|
void setMaxAge(uint32_t seconds) { _maxAge = seconds; }
|
|
|
|
void addCORSHeaders(AsyncWebServerResponse* response);
|
|
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
|
|
|
private:
|
|
String _origin = "*";
|
|
String _methods = "*";
|
|
String _headers = "*";
|
|
bool _credentials = true;
|
|
uint32_t _maxAge = 86400;
|
|
};
|
|
|
|
// Rate limit Middleware
|
|
class RateLimitMiddleware : public AsyncMiddleware {
|
|
public:
|
|
void setMaxRequests(size_t maxRequests) { _maxRequests = maxRequests; }
|
|
void setWindowSize(uint32_t seconds) { _windowSizeMillis = seconds * 1000; }
|
|
|
|
bool isRequestAllowed(uint32_t& retryAfterSeconds);
|
|
|
|
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
|
|
|
private:
|
|
size_t _maxRequests = 0;
|
|
uint32_t _windowSizeMillis = 0;
|
|
std::list<uint32_t> _requestTimes;
|
|
};
|
|
|
|
/*
|
|
* REWRITE :: One instance can be handle any Request (done by the Server)
|
|
* */
|
|
|
|
class AsyncWebRewrite {
|
|
protected:
|
|
String _from;
|
|
String _toUrl;
|
|
String _params;
|
|
ArRequestFilterFunction _filter{nullptr};
|
|
|
|
public:
|
|
AsyncWebRewrite(const char* from, const char* to) : _from(from), _toUrl(to) {
|
|
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 : public AsyncMiddlewareChain {
|
|
protected:
|
|
ArRequestFilterFunction _filter = nullptr;
|
|
AuthenticationMiddleware* _authMiddleware = nullptr;
|
|
|
|
public:
|
|
AsyncWebHandler() {}
|
|
virtual ~AsyncWebHandler() {}
|
|
AsyncWebHandler& setFilter(ArRequestFilterFunction fn);
|
|
AsyncWebHandler& setAuthentication(const char* username, const char* password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST);
|
|
AsyncWebHandler& setAuthentication(const String& username, const String& password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST) { return setAuthentication(username.c_str(), password.c_str(), authMethod); };
|
|
bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); }
|
|
virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) const { return false; }
|
|
virtual void handleRequest(__unused AsyncWebServerRequest* request) {}
|
|
virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) {}
|
|
virtual void handleBody(__unused AsyncWebServerRequest* request, __unused uint8_t* data, __unused size_t len, __unused size_t index, __unused size_t total) {}
|
|
virtual bool isRequestHandlerTrivial() const { 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;
|
|
|
|
public:
|
|
static const char* responseCodeToString(int code);
|
|
|
|
public:
|
|
AsyncWebServerResponse();
|
|
virtual ~AsyncWebServerResponse() {}
|
|
void setCode(int code);
|
|
int code() const { return _code; }
|
|
void setContentLength(size_t len);
|
|
void setContentType(const String& type) { setContentType(type.c_str()); }
|
|
void setContentType(const char* type);
|
|
bool addHeader(const char* name, const char* value, bool replaceExisting = true);
|
|
bool addHeader(const String& name, const String& value, bool replaceExisting = true) { return addHeader(name.c_str(), value.c_str(), replaceExisting); }
|
|
bool addHeader(const char* name, long value, bool replaceExisting = true) { return addHeader(name, String(value), replaceExisting); }
|
|
bool addHeader(const String& name, long value, bool replaceExisting = true) { return addHeader(name.c_str(), value, replaceExisting); }
|
|
bool removeHeader(const char* name);
|
|
const AsyncWebHeader* getHeader(const char* name) const;
|
|
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; }
|
|
|
|
#ifndef ESP8266
|
|
[[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]]
|
|
#endif
|
|
String _assembleHead(uint8_t version) {
|
|
String buffer;
|
|
_assembleHead(buffer, version);
|
|
return buffer;
|
|
}
|
|
void _assembleHead(String& buffer, 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 : public AsyncMiddlewareChain {
|
|
protected:
|
|
AsyncServer _server;
|
|
std::list<std::shared_ptr<AsyncWebRewrite>> _rewrites;
|
|
std::list<std::unique_ptr<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);
|
|
|
|
/**
|
|
* @brief (compat) Add url rewrite rule by pointer
|
|
* a deep copy of the pointer object will be created,
|
|
* it is up to user to manage further lifetime of the object in argument
|
|
*
|
|
* @param rewrite pointer to rewrite object to copy setting from
|
|
* @return AsyncWebRewrite& reference to a newly created rewrite rule
|
|
*/
|
|
AsyncWebRewrite& addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite);
|
|
|
|
/**
|
|
* @brief add url rewrite rule
|
|
*
|
|
* @param from
|
|
* @param to
|
|
* @return AsyncWebRewrite&
|
|
*/
|
|
AsyncWebRewrite& rewrite(const char* from, const char* to);
|
|
|
|
/**
|
|
* @brief (compat) remove rewrite rule via referenced object
|
|
* this will NOT deallocate pointed object itself, internal rule with same from/to urls will be removed if any
|
|
* it's a compat method, better use `removeRewrite(const char* from, const char* to)`
|
|
* @param rewrite
|
|
* @return true
|
|
* @return false
|
|
*/
|
|
bool removeRewrite(AsyncWebRewrite* rewrite);
|
|
|
|
/**
|
|
* @brief remove rewrite rule
|
|
*
|
|
* @param from
|
|
* @param to
|
|
* @return true
|
|
* @return false
|
|
*/
|
|
bool removeRewrite(const char* from, const char* to);
|
|
|
|
AsyncWebHandler& addHandler(AsyncWebHandler* handler);
|
|
bool removeHandler(AsyncWebHandler* handler);
|
|
|
|
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest) { return on(uri, HTTP_ANY, onRequest); }
|
|
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr, ArBodyHandlerFunction onBody = nullptr);
|
|
|
|
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 "AsyncEventSource.h"
|
|
#include "AsyncWebSocket.h"
|
|
#include "WebHandlerImpl.h"
|
|
#include "WebResponseImpl.h"
|
|
|
|
#endif /* _AsyncWebServer_H_ */
|